Skip to content

Commit

Permalink
Add possibility to set environment variables for specific services
Browse files Browse the repository at this point in the history
Add possibility of persistent connections
  • Loading branch information
cyrilst committed Mar 22, 2022
1 parent 28e1372 commit 7ca0082
Show file tree
Hide file tree
Showing 13 changed files with 415 additions and 41 deletions.
1 change: 1 addition & 0 deletions .cirrus.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ task:
VERSION: 1.13
VERSION: 1.14
VERSION: 1.15
VERSION: 1.16

container:
image: golang:$VERSION
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ bashcompdir ?= /etc/bash_completion.d
GO ?= go

ASCIIDOC_OPTS = -asshproxy_version=$(SSHPROXY_VERSION)
GO_OPTS = $(GO_OPTS_EXTRA) -ldflags "-X main.SshproxyVersion=$(SSHPROXY_VERSION)"
GO_OPTS = $(GO_OPTS_EXTRA) -mod=vendor -ldflags "-X main.SshproxyVersion=$(SSHPROXY_VERSION)"

SSHPROXY_SRC = $(wildcard cmd/sshproxy/*.go)
SSHPROXY_DUMPD_SRC = $(wildcard cmd/sshproxy-dumpd/*.go)
Expand Down
21 changes: 12 additions & 9 deletions cmd/sshproxy/sshproxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,23 +93,24 @@ func (c *etcdChecker) doCheck(hostport string) utils.State {
// findDestination finds a reachable destination for the sshd server according
// to the etcd database if available or the routes and route_select algorithm.
// It returns a string with the service name, a string with host:port, a string
// containing ForceCommand value and a bool containing CommandMustMatch; a
// containing ForceCommand value, a bool containing CommandMustMatch, an int64
// with etcd_keyttl and a map of strings with the environment variables; a
// string with the service name and an empty string if no destination is found
// or an error if any.
func findDestination(cli *utils.Client, username string, routes map[string]*utils.RouteConfig, sshdHostport string, checkInterval utils.Duration) (string, string, string, bool, error) {
func findDestination(cli *utils.Client, username string, routes map[string]*utils.RouteConfig, sshdHostport string, checkInterval utils.Duration) (string, string, string, bool, int64, map[string]string, error) {
checker := &etcdChecker{
checkInterval: checkInterval,
cli: cli,
}

service, err := findService(routes, sshdHostport)
if err != nil {
return "", "", "", false, err
return "", "", "", false, 0, nil, err
}
key := fmt.Sprintf("%s@%s", username, service)

if routes[service].Mode == "sticky" && cli != nil && cli.IsAlive() {
dest, err := cli.GetDestination(key)
dest, err := cli.GetDestination(key, routes[service].EtcdKeyTTL)
if err != nil {
if err != utils.ErrKeyNotFound {
log.Errorf("problem with etcd: %v", err)
Expand All @@ -118,7 +119,7 @@ func findDestination(cli *utils.Client, username string, routes map[string]*util
if utils.IsDestinationInRoutes(dest, routes[service].Dest) {
if checker.Check(dest) {
log.Debugf("found destination in etcd: %s", dest)
return service, dest, routes[service].ForceCommand, routes[service].CommandMustMatch, nil
return service, dest, routes[service].ForceCommand, routes[service].CommandMustMatch, routes[service].EtcdKeyTTL, routes[service].Environment, nil
}
log.Infof("cannot connect %s to already existing connection(s) to %s: host %s", key, dest, checker.LastState)
} else {
Expand All @@ -129,10 +130,10 @@ func findDestination(cli *utils.Client, username string, routes map[string]*util

if len(routes[service].Dest) > 0 {
selected, err := utils.SelectRoute(routes[service].RouteSelect, routes[service].Dest, checker, cli, key)
return service, selected, routes[service].ForceCommand, routes[service].CommandMustMatch, err
return service, selected, routes[service].ForceCommand, routes[service].CommandMustMatch, routes[service].EtcdKeyTTL, routes[service].Environment, err
}

return service, "", "", false, fmt.Errorf("no destination set for service %s", service)
return service, "", "", false, 0, nil, fmt.Errorf("no destination set for service %s", service)
}

// findService finds the first service containing a suitable source in the conf,
Expand Down Expand Up @@ -326,7 +327,7 @@ func mainExitCode() int {
log.Errorf("Cannot contact etcd cluster to update state: %v", err)
}

service, hostport, forceCommand, commandMustMatch, err := findDestination(cli, username, config.Routes, sshInfos.Dst(), config.CheckInterval)
service, hostport, forceCommand, commandMustMatch, etcdKeyTTL, environment, err := findDestination(cli, username, config.Routes, sshInfos.Dst(), config.CheckInterval)
switch {
case err != nil:
log.Fatalf("Finding destination: %s", err)
Expand All @@ -345,6 +346,8 @@ func mainExitCode() int {
log.Fatalf("Invalid destination '%s': %s", hostport, err)
}

setEnvironment(environment)

// waitgroup and channel to stop our background command when exiting.
var wg sync.WaitGroup
ctx, cancel := context.WithCancel(context.Background())
Expand All @@ -366,7 +369,7 @@ func mainExitCode() int {
// Register destination in etcd and keep it alive while running.
if cli != nil && cli.IsAlive() {
key := fmt.Sprintf("%s@%s", username, service)
keepAliveChan, eP, err := cli.SetDestination(ctx, key, sshInfos.Dst(), hostport)
keepAliveChan, eP, err := cli.SetDestination(ctx, key, sshInfos.Dst(), hostport, etcdKeyTTL)
etcdPath = eP
if err != nil {
log.Warningf("setting destination in etcd: %v", err)
Expand Down
20 changes: 13 additions & 7 deletions config/sshproxy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -129,13 +129,16 @@
# the selection is random. For "bandwidth", it's the same as "connections", but
# based on the bandwidth used, with a rollback on connections (which is
# frequent for new simultaneous connections). The mode value defines the
# stickiness of a connection. It can be "sticky" or "balanced". If "sticky",
# then all connections of a user will be made on the same destination host. If
# "balanced", the route_select algorithm will be used for every connection.
# Finally, the force_command can be set to override the command asked by the
# user. If command_must_match is set to true, then the connection is closed if
# the original command is not the same as the force_command. command_must_match
# defaults to false.
# stickiness of a connection. It can be "sticky" or "balanced" (defaults to
# sticky). If "sticky", then all connections of a user will be made on the same
# destination host. If "balanced", the route_select algorithm will be used for
# every connection. Finally, the force_command can be set to override the
# command asked by the user. If command_must_match is set to true, then the
# connection is closed if the original command is not the same as the
# force_command. command_must_match defaults to false. etcd_keyttl defauts to
# 0. If a value is set (in seconds), the chosen backen will be remembered for
# this amount of time. Environment variables can be set if needed. The '{user}'
# pattern will be replaced with the user login.
#routes:
# service1:
# source: ["192.168.0.1"]
Expand All @@ -147,6 +150,9 @@
# mode: balanced
# force_command: "internal-sftp"
# command_must_match: true
# etcd_keyttl: 3600
# environment:
# XAUTHORITY: /dev/shm/.Xauthority_{user}
# default:
# dest: ["host5:4222"]

Expand Down
41 changes: 21 additions & 20 deletions doc/sshproxy.yaml.txt
Original file line number Diff line number Diff line change
Expand Up @@ -102,11 +102,14 @@ executed by the ssh forked by sshproxy. *translate_commands* is an associative
array which keys are strings containing the exact user command. The value is
an associative array containing:

*ssh_args*::
an optional list of options that will be passed to ssh.
An associative array *ssh* specifies the SSH options:

*exe*::
path or command to use for the SSH client ('ssh' by default).

*command*::
a mandatory string, the actual executed command.
*args*::
a list of arguments for the SSH client. Its default value is: '["-q",
"-Y"]'.

*disable_dump*::
false by default. If true, no dumps will be done for this command.
Expand Down Expand Up @@ -178,6 +181,9 @@ listening IP address of the SSH daemon:
mode: balanced
force_command: "internal-sftp"
command_must_match: true
etcd_keyttl: 3600
environment:
XAUTHORITY: /dev/shm/.Xauthority_{user}
default:
dest: ["host5:4222"]

Expand All @@ -195,28 +201,23 @@ priority, then the hosts with less global connections, and in case of a draw,
the selection is random. For 'bandwidth', it's the same as 'connections', but
based on the bandwidth used, with a rollback on connections (which is
frequent for new simultaneous connections). The mode value defines the
stickiness of a connection. It can be 'sticky' or 'balanced'. If 'sticky',
then all connections of a user will be made on the same destination host. If
'balanced', the route_select algorithm will be used for every connections.
Finally, the force_command can be set to override the command asked by the
user. If command_must_match is set to true, then the connection is closed if
the original command is not the same as the force_command. command_must_match
defaults to false.
stickiness of a connection. It can be 'sticky' or 'balanced' (defaults to
'sticky'). If 'sticky', then all connections of a user will be made on the
same destination host. If 'balanced', the route_select algorithm will be used
for every connections. Finally, the force_command can be set to override the
command asked by the user. If command_must_match is set to true, then the
connection is closed if the original command is not the same as the
force_command. command_must_match defaults to false. etcd_keyttl defauts to 0.
If a value is set (in seconds), the chosen backen will be remembered for this
amount of time. An associative array *environment* can be used to set
environment variables. The pattern '\{user}' will be replaced with the user
login.

In the previous example, a client connected to '192.168.0.1' will be proxied
to 'host1' and, if the host is not reachable, to 'host2'. If a client does not
connect to '192.168.0.1' or '192.168.0.2' it will be proxied to the sshd
daemon listening on port 4222 on 'host5'.

An associative array *ssh* specifies the SSH options:

*exe*::
path or command to use for the SSH client ('ssh' by default).

*args*::
a list of arguments for the SSH client. Its default value is: '["-q",
"-Y"]'.

Each of the previous parameters can be overridden for a group thanks to the
*groups* associative array.

Expand Down
21 changes: 21 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
module github.com/cea-hpc/sshproxy

go 1.14

require (
github.com/BurntSushi/toml v1.0.0 // indirect
github.com/coreos/etcd v2.3.8+incompatible // indirect
github.com/docker/docker v20.10.13+incompatible
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/kr/pty v1.1.8
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
github.com/olekukonko/tablewriter v0.0.5
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
go.etcd.io/etcd v2.3.8+incompatible
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8 // indirect
golang.org/x/tools v0.1.10 // indirect
gopkg.in/yaml.v2 v2.4.0
honnef.co/go/tools v0.2.2 // indirect
)
Loading

0 comments on commit 7ca0082

Please sign in to comment.