diff --git a/e2e/e2eentry.sh b/e2e/e2eentry.sh index 0aeff65c..c3a165c2 100755 --- a/e2e/e2eentry.sh +++ b/e2e/e2eentry.sh @@ -4,10 +4,13 @@ set -x # use entrypoint.sh to generate the ssh_host_ed25519_key PLUGIN="dummy_badname/" bash /sshpiperd/entrypoint.sh 2>/dev/null +groupadd -f testgroup && \ +useradd -m -G testgroup testgroupuser + if [ "${SSHPIPERD_DEBUG}" == "1" ]; then echo "enter debug on hold mode" echo "run [docker exec -ti e2e_testrunner_1 bash] to run to attach" sleep infinity; else go test -v; -fi \ No newline at end of file +fi diff --git a/e2e/yaml_test.go b/e2e/yaml_test.go index 7c32cfac..97bc0b09 100644 --- a/e2e/yaml_test.go +++ b/e2e/yaml_test.go @@ -48,19 +48,33 @@ pipes: private_key: {{ .PrivateKey }} known_hosts_data: {{ .KnownHostsKey }} - from: - - username: ".*" - username_regex_match: true - authorized_keys: - - {{ .AuthorizedKeys_Simple }} - - {{ .AuthorizedKeys_Catchall }} + - username: "cert" + trusted_user_ca_keys: {{ .TrustedUserCAKeys }} to: host: host-publickey:2222 username: "user" ignore_hostkey: true private_key: {{ .PrivateKey }} - from: - - username: "cert" - trusted_user_ca_keys: {{ .TrustedUserCAKeys }} + - groupname: "testgroup" + authorized_keys: {{ .AuthorizedKeys_Simple }} + to: + host: host-publickey:2222 + username: "user" + private_key: {{ .PrivateKey }} + known_hosts_data: {{ .KnownHostsKey }} +- from: + - groupname: "testgroup" + to: + host: host-password:2222 + username: "user" + ignore_hostkey: true +- from: + - username: ".*" + username_regex_match: true + authorized_keys: + - {{ .AuthorizedKeys_Simple }} + - {{ .AuthorizedKeys_Catchall }} to: host: host-publickey:2222 username: "user" @@ -479,4 +493,68 @@ func TestYaml(t *testing.T) { checkSharedFileContent(t, targetfie, randtext) }) + + t.Run("group_routing_key", func(t *testing.T) { + randtext := uuid.New().String() + targetfie := uuid.New().String() + + c, _, _, err := runCmd( + "ssh", + "-v", + "-o", + "StrictHostKeyChecking=no", + "-o", + "UserKnownHostsFile=/dev/null", + "-p", + piperport, + "-l", + "testuser", + "-i", + path.Join(yamldir, "id_rsa_simple"), + "127.0.0.1", + fmt.Sprintf(`sh -c "echo -n %v > /shared/%v"`, randtext, targetfie), + ) + + if err != nil { + t.Errorf("failed to ssh to piper, %v", err) + } + + defer killCmd(c) + + time.Sleep(time.Second) // wait for file flush + + checkSharedFileContent(t, targetfie, randtext) + }) + + t.Run("group_routing_password", func(t *testing.T) { + randtext := uuid.New().String() + targetfie := uuid.New().String() + + c, stdin, stdout, err := runCmd( + "ssh", + "-v", + "-o", + "StrictHostKeyChecking=no", + "-o", + "UserKnownHostsFile=/dev/null", + "-p", + piperport, + "-l", + "testgroupuser", + "127.0.0.1", + fmt.Sprintf(`sh -c "echo -n %v > /shared/%v"`, randtext, targetfie), + ) + + if err != nil { + t.Errorf("failed to ssh to piper, %v", err) + } + + defer killCmd(c) + + enterPassword(stdin, stdout, "pass") + + time.Sleep(time.Second) // wait for file flush + + checkSharedFileContent(t, targetfie, randtext) + }) } diff --git a/plugin/yaml/schema.json b/plugin/yaml/schema.json index d30780c8..1bba4826 100644 --- a/plugin/yaml/schema.json +++ b/plugin/yaml/schema.json @@ -50,6 +50,9 @@ "username_regex_match": { "type": "boolean" }, + "groupname": { + "type": "string" + }, "authorized_keys": { "oneOf": [ { @@ -161,4 +164,4 @@ ] } } -} \ No newline at end of file +} diff --git a/plugin/yaml/skel.go b/plugin/yaml/skel.go index 7dada294..439948c0 100644 --- a/plugin/yaml/skel.go +++ b/plugin/yaml/skel.go @@ -3,7 +3,11 @@ package main import ( + "errors" + log "github.com/sirupsen/logrus" + "os/user" "regexp" + "slices" "github.com/tg123/sshpiper/libplugin" ) @@ -84,26 +88,46 @@ func (s *skelpipeToWrapper) KnownHosts(conn libplugin.ConnMetadata) ([]byte, err } func (s *skelpipeFromWrapper) MatchConn(conn libplugin.ConnMetadata) (libplugin.SkelPipeTo, error) { - user := conn.User() + username := conn.User() - matched := s.from.Username == user targetuser := s.to.Username - if targetuser == "" { - targetuser = user - } + var matched bool + if s.from.Username != "" { + matched = s.from.Username == username + if s.from.UsernameRegexMatch { + re, err := regexp.Compile(s.from.Username) + if err != nil { + return nil, err + } - if s.from.UsernameRegexMatch { - re, err := regexp.Compile(s.from.Username) + matched = re.MatchString(username) + + if matched { + targetuser = re.ReplaceAllString(username, s.to.Username) + } + } + } else if s.from.Groupname != "" { + // check user is known to the system before grouplookup + usr, err := user.Lookup(username) + if err != nil { + var unknownUser user.UnknownUserError + if errors.As(err, &unknownUser) { + return nil, nil + } + log.Errorf("[ERROR] Matchconn(): Failure looking up user %q: %T - %v", username, err, err) + return nil, err + } + userGroups, err := getUserGroups(usr) if err != nil { return nil, err } + fromPipeGroup := s.from.Groupname + matched = slices.Contains(userGroups, fromPipeGroup) + } - matched = re.MatchString(user) - - if matched { - targetuser = re.ReplaceAllString(user, s.to.Username) - } + if targetuser == "" { + targetuser = username } if matched { @@ -183,3 +207,23 @@ func (p *plugin) listPipe(_ libplugin.ConnMetadata) ([]libplugin.SkelPipe, error return pipes, nil } + +func getUserGroups(usr *user.User) ([]string, error) { + groupIds, err := usr.GroupIds() + if err != nil { + log.Errorf("[ERROR] getUserGroups(): Failure retrieving group IDs for %q: %T - %v", usr.Username, err, err) + return nil, err + } + + var groups []string + for _, groupId := range groupIds { + grp, err := user.LookupGroupId(groupId) + if err != nil { + log.Errorf("[ERROR] getUserGroups(): Failure retrieving group name for %q: %T - %v", usr.Username, err, err) + return nil, err + } + groups = append(groups, grp.Name) + } + + return groups, nil +} diff --git a/plugin/yaml/yaml.go b/plugin/yaml/yaml.go index a1a751d7..fc191b96 100644 --- a/plugin/yaml/yaml.go +++ b/plugin/yaml/yaml.go @@ -14,7 +14,8 @@ import ( ) type yamlPipeFrom struct { - Username string `yaml:"username"` + Username string `yaml:"username,omitempty"` + Groupname string `yaml:"groupname,omitempty"` UsernameRegexMatch bool `yaml:"username_regex_match,omitempty"` AuthorizedKeys listOrString `yaml:"authorized_keys,omitempty"` AuthorizedKeysData listOrString `yaml:"authorized_keys_data,omitempty"`