From 4db98899f49a3437030155eb20fe0e39deaa0b62 Mon Sep 17 00:00:00 2001 From: Boshi Lian Date: Sun, 20 Oct 2024 02:15:46 -0700 Subject: [PATCH] YAML Plugin add regex match group (#464) * add support for match group replace * update doc --- cmd/sshpiperd/internal/plugin/grpc.go | 2 +- e2e/yaml_test.go | 41 ++++++++++++++++++++++++++- plugin/yaml/README.md | 18 ++++++++---- plugin/yaml/yaml.go | 15 +++++++++- 4 files changed, 68 insertions(+), 8 deletions(-) diff --git a/cmd/sshpiperd/internal/plugin/grpc.go b/cmd/sshpiperd/internal/plugin/grpc.go index 791067d80..1586ae21f 100644 --- a/cmd/sshpiperd/internal/plugin/grpc.go +++ b/cmd/sshpiperd/internal/plugin/grpc.go @@ -376,7 +376,7 @@ func (g *GrpcPlugin) createUpstream(conn ssh.ConnMetadata, challengeCtx ssh.Chal config.Auth = append(config.Auth, ssh.NoneAuth()) } - log.Debugf("connecting to upstream %v with auth %v", c.RemoteAddr().String(), auth) + log.Debugf("connecting to upstream %v@%v with auth %v", config.User, c.RemoteAddr().String(), auth) return &ssh.Upstream{ Conn: c, diff --git a/e2e/yaml_test.go b/e2e/yaml_test.go index 572d5475b..7f7141584 100644 --- a/e2e/yaml_test.go +++ b/e2e/yaml_test.go @@ -22,7 +22,7 @@ pipes: username: "user" ignore_hostkey: true - from: - - username: "password_.*_regex" + - username: "^password_.*_regex$" username_regex_match: true to: host: host-password:2222 @@ -32,6 +32,13 @@ pipes: - fDF8RjRwTmVveUZHVEVHcEIyZ3A4RGE0WlE4TGNVPXxycVZYNU0rWTJoS0dteFphcVFBb0syRHp1TEE9IHNzaC1lZDI1NTE5IEFBQUFDM056YUMxbFpESTFOVEU1QUFBQUlPTXFxbmtWenJtMFNkRzZVT29xS0xzYWJnSDVDOW9rV2kwZGgybDlHS0psCg== - {{ .KnownHostsKey }} - {{ .KnownHostsPass }} +- from: + - username: "^password_(.+?)_regex_expand$" + username_regex_match: true + to: + host: host-password:2222 + username: "$1" + known_hosts_data: {{ .KnownHostsPass }} - from: - username: "publickey_simple" authorized_keys: {{ .AuthorizedKeys_Simple }} @@ -243,6 +250,38 @@ func TestYaml(t *testing.T) { checkSharedFileContent(t, targetfie, randtext) }) + t.Run("password_regex_expand", 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", + "password_user_regex_expand", + "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) + }) + t.Run("publickey_simple", func(t *testing.T) { randtext := uuid.New().String() targetfie := uuid.New().String() diff --git a/plugin/yaml/README.md b/plugin/yaml/README.md index b74d09128..3807c61b8 100644 --- a/plugin/yaml/README.md +++ b/plugin/yaml/README.md @@ -7,7 +7,11 @@ some basic idea of yaml config file: * first matched `pipe` will be used. * any `from` in `pipe` fits `downstream` authentication will be considered as the `pipe` matched. * `username_regex_match` can be used to match with regex - * `authorized_keys`, `private_key`, `known_hosts` are `path/to/target/file`, but there are also `authorized_keys_data`, `private_key_data`, `known_hosts_data` accepting base64 inline data + + * to.Username can be template of regex match groups, example: `from.username: "^password_(.*?)_regex$"` and `to.username: $1"`, will match `password_user_regex` to `user`, more sytax see + + * `authorized_keys`, `known_hosts` are array `path/to/target/file` or single string, but there are also `authorized_keys_data`, `known_hosts_data` accepting base64 inline data, file and data will be merged if both are set + * `private_key` is `path/to/target/file`, but there are also `private_key_data` accepting base64 inline data, file wins if both are set * magic placeholders in path, example usage: `/path/to/$UPSTREAM_USER/file` * `DOWNSTREAM_USER`: supported in `private_key`, `known_hosts` * `UPSTREAM_USER`: supported in `authorized_keys`, `private_key`, `known_hosts` @@ -39,20 +43,24 @@ pipes: username: "user" ignore_hostkey: true - from: - - username: "password_.*_regex" + - username: "^password_(.*?)_regex$" username_regex_match: true to: host: host-password:2222 - username: "user" + username: "$1" ignore_hostkey: true - from: - username: "publickey_simple" - authorized_keys: /path/to/publickey_simple/authorized_keys + authorized_keys: + - /path/to/publickey_simple/authorized_keys + - /path/to/publickey_simple/authorized_keys2 to: host: host-publickey:2222 username: "user" private_key: /path/to/host-publickey/id_rsa - known_hosts_data: "base64_known_hosts_data" + known_hosts_data: + - "base64_known_hosts_data" + - "base64_known_hosts_data2" - from: - username: ".*" # catch all username_regex_match: true diff --git a/plugin/yaml/yaml.go b/plugin/yaml/yaml.go index b41563eb1..2cb7a52ec 100644 --- a/plugin/yaml/yaml.go +++ b/plugin/yaml/yaml.go @@ -290,8 +290,21 @@ func (p *plugin) findAndCreateUpstream(conn libplugin.ConnMetadata, password str for _, from := range pipe.From { matched := from.Username == user + if pipe.To.Username == "" { + pipe.To.Username = user + } + if from.UsernameRegexMatch { - matched, _ = regexp.MatchString(from.Username, user) + re, err := regexp.Compile(from.Username) + if err != nil { + return nil, err + } + + matched = re.MatchString(user) + + if matched { + pipe.To.Username = re.ReplaceAllString(user, pipe.To.Username) + } } if !matched {