YAML Plugin add regex match group (#464)

* add support for match group replace

* update doc
This commit is contained in:
Boshi Lian 2024-10-20 02:15:46 -07:00 • committed by GitHub
parent 39f5eef98b
commit 4db98899f4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 68 additions and 8 deletions

View file

@ -376,7 +376,7 @@ func (g *GrpcPlugin) createUpstream(conn ssh.ConnMetadata, challengeCtx ssh.Chal
config.Auth = append(config.Auth, ssh.NoneAuth()) 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{ return &ssh.Upstream{
Conn: c, Conn: c,

View file

@ -22,7 +22,7 @@ pipes:
username: "user" username: "user"
ignore_hostkey: true ignore_hostkey: true
- from: - from:
- username: "password_.*_regex" - username: "^password_.*_regex$"
username_regex_match: true username_regex_match: true
to: to:
host: host-password:2222 host: host-password:2222
@ -32,6 +32,13 @@ pipes:
- fDF8RjRwTmVveUZHVEVHcEIyZ3A4RGE0WlE4TGNVPXxycVZYNU0rWTJoS0dteFphcVFBb0syRHp1TEE9IHNzaC1lZDI1NTE5IEFBQUFDM056YUMxbFpESTFOVEU1QUFBQUlPTXFxbmtWenJtMFNkRzZVT29xS0xzYWJnSDVDOW9rV2kwZGgybDlHS0psCg== - fDF8RjRwTmVveUZHVEVHcEIyZ3A4RGE0WlE4TGNVPXxycVZYNU0rWTJoS0dteFphcVFBb0syRHp1TEE9IHNzaC1lZDI1NTE5IEFBQUFDM056YUMxbFpESTFOVEU1QUFBQUlPTXFxbmtWenJtMFNkRzZVT29xS0xzYWJnSDVDOW9rV2kwZGgybDlHS0psCg==
- {{ .KnownHostsKey }} - {{ .KnownHostsKey }}
- {{ .KnownHostsPass }} - {{ .KnownHostsPass }}
- from:
- username: "^password_(.+?)_regex_expand$"
username_regex_match: true
to:
host: host-password:2222
username: "$1"
known_hosts_data: {{ .KnownHostsPass }}
- from: - from:
- username: "publickey_simple" - username: "publickey_simple"
authorized_keys: {{ .AuthorizedKeys_Simple }} authorized_keys: {{ .AuthorizedKeys_Simple }}
@ -243,6 +250,38 @@ func TestYaml(t *testing.T) {
checkSharedFileContent(t, targetfie, randtext) 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) { t.Run("publickey_simple", func(t *testing.T) {
randtext := uuid.New().String() randtext := uuid.New().String()
targetfie := uuid.New().String() targetfie := uuid.New().String()

View file

@ -7,7 +7,11 @@ some basic idea of yaml config file:
* first matched `pipe` will be used. * first matched `pipe` will be used.
* any `from` in `pipe` fits `downstream` authentication will be considered as the `pipe` matched. * any `from` in `pipe` fits `downstream` authentication will be considered as the `pipe` matched.
* `username_regex_match` can be used to match with regex * `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 <https://pkg.go.dev/regexp#Regexp.Expand>
* `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` * magic placeholders in path, example usage: `/path/to/$UPSTREAM_USER/file`
* `DOWNSTREAM_USER`: supported in `private_key`, `known_hosts` * `DOWNSTREAM_USER`: supported in `private_key`, `known_hosts`
* `UPSTREAM_USER`: supported in `authorized_keys`, `private_key`, `known_hosts` * `UPSTREAM_USER`: supported in `authorized_keys`, `private_key`, `known_hosts`
@ -39,20 +43,24 @@ pipes:
username: "user" username: "user"
ignore_hostkey: true ignore_hostkey: true
- from: - from:
- username: "password_.*_regex" - username: "^password_(.*?)_regex$"
username_regex_match: true username_regex_match: true
to: to:
host: host-password:2222 host: host-password:2222
username: "user" username: "$1"
ignore_hostkey: true ignore_hostkey: true
- from: - from:
- username: "publickey_simple" - 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: to:
host: host-publickey:2222 host: host-publickey:2222
username: "user" username: "user"
private_key: /path/to/host-publickey/id_rsa 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: - from:
- username: ".*" # catch all - username: ".*" # catch all
username_regex_match: true username_regex_match: true

View file

@ -290,8 +290,21 @@ func (p *plugin) findAndCreateUpstream(conn libplugin.ConnMetadata, password str
for _, from := range pipe.From { for _, from := range pipe.From {
matched := from.Username == user matched := from.Username == user
if pipe.To.Username == "" {
pipe.To.Username = user
}
if from.UsernameRegexMatch { 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 { if !matched {