From 085ba59c58361e847e5f91939891be74fab06fb1 Mon Sep 17 00:00:00 2001 From: Boshi Lian Date: Sun, 10 Nov 2024 03:01:52 -0800 Subject: [PATCH] support cross auth type mapping (#483) * add testcase * add fix * Refactor skelpipe wrappers to separate password and private key handling --- e2e/k8sworkload.yaml | 18 ++++++++++- e2e/kubernetes_test.go | 4 +++ libplugin/skel.go | 65 +++++++++++++++++++-------------------- plugin/docker/skel.go | 30 +++++++++++++++--- plugin/kubernetes/skel.go | 36 +++++++++++++++++----- plugin/workingdir/skel.go | 23 +++++++++++--- plugin/yaml/skel.go | 33 ++++++++++++++++---- 7 files changed, 152 insertions(+), 57 deletions(-) diff --git a/e2e/k8sworkload.yaml b/e2e/k8sworkload.yaml index c5c06bec..96febb27 100644 --- a/e2e/k8sworkload.yaml +++ b/e2e/k8sworkload.yaml @@ -274,4 +274,20 @@ spec: username: "client_123" private_key_secret: name: host-publickey-key-ca - ignore_hostkey: true \ No newline at end of file + ignore_hostkey: true +--- +apiVersion: sshpiper.com/v1beta1 +kind: Pipe +metadata: + name: pipe-keytopass +spec: + from: + - username: "keytopass" + authorized_keys_file: /files/authorized_keys + to: + host: host-password:2222 + username: "user" + password_secret: + name: host-pass-secret + ignore_hostkey: true +--- \ No newline at end of file diff --git a/e2e/kubernetes_test.go b/e2e/kubernetes_test.go index 456c3f38..d900100f 100644 --- a/e2e/kubernetes_test.go +++ b/e2e/kubernetes_test.go @@ -36,6 +36,10 @@ func TestKubernetes(t *testing.T) { title: "key_public_ca", user: "hostcapublickey", }, + { + title: "key_to_pass", + user: "keytopass", + }, } for _, testcase := range pubkeycases { diff --git a/libplugin/skel.go b/libplugin/skel.go index eaa0cc97..a04db0a6 100644 --- a/libplugin/skel.go +++ b/libplugin/skel.go @@ -168,29 +168,12 @@ func (p *SkelPlugin) PasswordCallback(conn ConnMetadata, password []byte) (*Upst return nil, err } - u, err := p.createUpstream(conn, to) + u, err := p.createUpstream(conn, to, password) if err != nil { return nil, err } - toPass, ok := to.(SkelPipeToPassword) - if !ok { - return nil, fmt.Errorf("pipe to does not support password") - } - - overridepassword, err := toPass.OverridePassword(conn) - if err != nil { - return nil, err - } - - if overridepassword != nil { - u.Auth = CreatePasswordAuth(overridepassword) - } else { - u.Auth = CreatePasswordAuth(password) - } - return u, nil - } func (p *SkelPlugin) PublicKeyCallback(conn ConnMetadata, publicKey []byte) (*Upstream, error) { @@ -270,27 +253,15 @@ func (p *SkelPlugin) PublicKeyCallback(conn ConnMetadata, publicKey []byte) (*Up return nil, err } - u, err := p.createUpstream(conn, to) + u, err := p.createUpstream(conn, to, nil) if err != nil { return nil, err } - toPrivateKey, ok := to.(SkelPipeToPrivateKey) - if !ok { - return nil, fmt.Errorf("pipe to does not support private key") - } - - priv, cert, err := toPrivateKey.PrivateKey(conn) - if err != nil { - return nil, err - } - - u.Auth = CreatePrivateKeyAuth(priv, cert) - return u, nil } -func (p *SkelPlugin) createUpstream(conn ConnMetadata, to SkelPipeTo) (*Upstream, error) { +func (p *SkelPlugin) createUpstream(conn ConnMetadata, to SkelPipeTo, originalPassword []byte) (*Upstream, error) { host, port, err := SplitHostPortForSSH(to.Host(conn)) if err != nil { return nil, err @@ -303,10 +274,36 @@ func (p *SkelPlugin) createUpstream(conn ConnMetadata, to SkelPipeTo) (*Upstream p.cache.SetDefault(conn.UniqueID(), to) - return &Upstream{ + u := &Upstream{ Host: host, Port: int32(port), // port is already checked to be within int32 range in SplitHostPortForSSH UserName: user, IgnoreHostKey: to.IgnoreHostKey(conn), - }, err + } + + switch to := to.(type) { + case SkelPipeToPassword: + overridepassword, err := to.OverridePassword(conn) + if err != nil { + return nil, err + } + + if overridepassword != nil { + u.Auth = CreatePasswordAuth(overridepassword) + } else { + u.Auth = CreatePasswordAuth(originalPassword) + } + + case SkelPipeToPrivateKey: + priv, cert, err := to.PrivateKey(conn) + if err != nil { + return nil, err + } + + u.Auth = CreatePrivateKeyAuth(priv, cert) + default: + return nil, fmt.Errorf("pipe to does not support any auth method") + } + + return u, err } diff --git a/plugin/docker/skel.go b/plugin/docker/skel.go index 762e8aa5..bd4f3b09 100644 --- a/plugin/docker/skel.go +++ b/plugin/docker/skel.go @@ -32,6 +32,14 @@ type skelpipeToWrapper struct { username string } +type skelpipeToPasswordWrapper struct { + skelpipeToWrapper +} + +type skelpipeToPrivateKeyWrapper struct { + skelpipeToWrapper +} + func (s *skelpipeWrapper) From() []libplugin.SkelPipeFrom { w := skelpipeFromWrapper{ @@ -76,9 +84,21 @@ func (s *skelpipeFromWrapper) MatchConn(conn libplugin.ConnMetadata) (libplugin. } if matched { - return &skelpipeToWrapper{ - skelpipeWrapper: s.skelpipeWrapper, - username: targetuser, + + if s.pipe.PrivateKey != "" { + return &skelpipeToPrivateKeyWrapper{ + skelpipeToWrapper: skelpipeToWrapper{ + skelpipeWrapper: s.skelpipeWrapper, + username: targetuser, + }, + }, nil + } + + return &skelpipeToPasswordWrapper{ + skelpipeToWrapper: skelpipeToWrapper{ + skelpipeWrapper: s.skelpipeWrapper, + username: targetuser, + }, }, nil } @@ -97,7 +117,7 @@ func (s *skelpipePublicKeyWrapper) TrustedUserCAKeys(conn libplugin.ConnMetadata return nil, nil // TODO support this } -func (s *skelpipeToWrapper) PrivateKey(conn libplugin.ConnMetadata) ([]byte, []byte, error) { +func (s *skelpipeToPrivateKeyWrapper) PrivateKey(conn libplugin.ConnMetadata) ([]byte, []byte, error) { k, err := base64.StdEncoding.DecodeString(s.pipe.PrivateKey) if err != nil { return nil, nil, err @@ -106,7 +126,7 @@ func (s *skelpipeToWrapper) PrivateKey(conn libplugin.ConnMetadata) ([]byte, []b return k, nil, nil } -func (s *skelpipeToWrapper) OverridePassword(conn libplugin.ConnMetadata) ([]byte, error) { +func (s *skelpipeToPasswordWrapper) OverridePassword(conn libplugin.ConnMetadata) ([]byte, error) { return nil, nil } diff --git a/plugin/kubernetes/skel.go b/plugin/kubernetes/skel.go index b315fd73..8e61ccbb 100644 --- a/plugin/kubernetes/skel.go +++ b/plugin/kubernetes/skel.go @@ -44,6 +44,14 @@ type skelpipeToWrapper struct { to *piperv1beta1.ToSpec } +type skelpipeToPasswordWrapper struct { + skelpipeToWrapper +} + +type skelpipeToPrivateKeyWrapper struct { + skelpipeToWrapper +} + func (s *skelpipeWrapper) From() []libplugin.SkelPipeFrom { var froms []libplugin.SkelPipeFrom for _, f := range s.pipe.Spec.From { @@ -108,11 +116,25 @@ func (s *skelpipeFromWrapper) MatchConn(conn libplugin.ConnMetadata) (libplugin. } if matched { - return &skelpipeToWrapper{ - plugin: s.plugin, - pipe: s.pipe, - username: targetuser, - to: s.to, + + if s.to.PrivateKeySecret.Name != "" { + return &skelpipeToPrivateKeyWrapper{ + skelpipeToWrapper: skelpipeToWrapper{ + plugin: s.plugin, + pipe: s.pipe, + username: targetuser, + to: s.to, + }, + }, nil + } + + return &skelpipeToPasswordWrapper{ + skelpipeToWrapper: skelpipeToWrapper{ + plugin: s.plugin, + pipe: s.pipe, + username: targetuser, + to: s.to, + }, }, nil } @@ -156,7 +178,7 @@ func (s *skelpipePublicKeyWrapper) TrustedUserCAKeys(conn libplugin.ConnMetadata return nil, nil // TODO support trusted_user_ca_keys } -func (s *skelpipeToWrapper) PrivateKey(conn libplugin.ConnMetadata) ([]byte, []byte, error) { +func (s *skelpipeToPrivateKeyWrapper) PrivateKey(conn libplugin.ConnMetadata) ([]byte, []byte, error) { log.Debugf("mapping to %v private key using secret %v", s.to.Host, s.to.PrivateKeySecret.Name) secret, err := s.plugin.k8sclient.Secrets(s.pipe.Namespace).Get(context.Background(), s.to.PrivateKeySecret.Name, metav1.GetOptions{}) @@ -189,7 +211,7 @@ func (s *skelpipeToWrapper) PrivateKey(conn libplugin.ConnMetadata) ([]byte, []b return privateKey, publicKey, nil } -func (s *skelpipeToWrapper) OverridePassword(conn libplugin.ConnMetadata) ([]byte, error) { +func (s *skelpipeToPasswordWrapper) OverridePassword(conn libplugin.ConnMetadata) ([]byte, error) { if s.to.PasswordSecret.Name != "" { log.Debugf("mapping to %v password using secret %v", s.to.Host, s.to.PasswordSecret.Name) secret, err := s.plugin.k8sclient.Secrets(s.pipe.Namespace).Get(context.Background(), s.to.PasswordSecret.Name, metav1.GetOptions{}) diff --git a/plugin/workingdir/skel.go b/plugin/workingdir/skel.go index 2951e6fd..a6bfbc53 100644 --- a/plugin/workingdir/skel.go +++ b/plugin/workingdir/skel.go @@ -42,6 +42,14 @@ type skelpipeToWrapper struct { skelpipeWrapper } +type skelpipeToPasswordWrapper struct { + skelpipeToWrapper +} + +type skelpipeToPrivateKeyWrapper struct { + skelpipeToWrapper +} + func (s *skelpipeWrapper) From() []libplugin.SkelPipeFrom { w := skelpipeFromWrapper{ @@ -76,8 +84,15 @@ func (s *skelpipeToWrapper) KnownHosts(conn libplugin.ConnMetadata) ([]byte, err } func (s *skelpipeFromWrapper) MatchConn(conn libplugin.ConnMetadata) (libplugin.SkelPipeTo, error) { - return &skelpipeToWrapper{ - skelpipeWrapper: s.skelpipeWrapper, + + if s.dir.Exists(userKeyFile) { + return &skelpipeToPrivateKeyWrapper{ + skelpipeToWrapper: skelpipeToWrapper(*s), + }, nil + } + + return &skelpipeToPasswordWrapper{ + skelpipeToWrapper: skelpipeToWrapper(*s), }, nil } @@ -93,7 +108,7 @@ func (s *skelpipePublicKeyWrapper) TrustedUserCAKeys(conn libplugin.ConnMetadata return nil, nil // TODO support this } -func (s *skelpipeToWrapper) PrivateKey(conn libplugin.ConnMetadata) ([]byte, []byte, error) { +func (s *skelpipeToPrivateKeyWrapper) PrivateKey(conn libplugin.ConnMetadata) ([]byte, []byte, error) { k, err := s.dir.Readfile(userKeyFile) if err != nil { return nil, nil, err @@ -102,7 +117,7 @@ func (s *skelpipeToWrapper) PrivateKey(conn libplugin.ConnMetadata) ([]byte, []b return k, nil, nil } -func (s *skelpipeToWrapper) OverridePassword(conn libplugin.ConnMetadata) ([]byte, error) { +func (s *skelpipeToPasswordWrapper) OverridePassword(conn libplugin.ConnMetadata) ([]byte, error) { return nil, nil } diff --git a/plugin/yaml/skel.go b/plugin/yaml/skel.go index 442d82f7..7dada294 100644 --- a/plugin/yaml/skel.go +++ b/plugin/yaml/skel.go @@ -33,6 +33,14 @@ type skelpipeToWrapper struct { to *yamlPipeTo } +type skelpipeToPasswordWrapper struct { + skelpipeToWrapper +} + +type skelpipeToPrivateKeyWrapper struct { + skelpipeToWrapper +} + func (s *skelpipeWrapper) From() []libplugin.SkelPipeFrom { var froms []libplugin.SkelPipeFrom for _, f := range s.pipe.From { @@ -99,10 +107,23 @@ func (s *skelpipeFromWrapper) MatchConn(conn libplugin.ConnMetadata) (libplugin. } if matched { - return &skelpipeToWrapper{ - config: s.config, - username: targetuser, - to: s.to, + + if s.to.PrivateKey != "" || s.to.PrivateKeyData != "" { + return &skelpipeToPrivateKeyWrapper{ + skelpipeToWrapper: skelpipeToWrapper{ + config: s.config, + username: targetuser, + to: s.to, + }, + }, nil + } + + return &skelpipeToPasswordWrapper{ + skelpipeToWrapper: skelpipeToWrapper{ + config: s.config, + username: targetuser, + to: s.to, + }, }, nil } @@ -125,7 +146,7 @@ func (s *skelpipePublicKeyWrapper) TrustedUserCAKeys(conn libplugin.ConnMetadata }) } -func (s *skelpipeToWrapper) PrivateKey(conn libplugin.ConnMetadata) ([]byte, []byte, error) { +func (s *skelpipeToPrivateKeyWrapper) PrivateKey(conn libplugin.ConnMetadata) ([]byte, []byte, error) { p, err := s.config.loadFileOrDecode(s.to.PrivateKey, s.to.PrivateKeyData, map[string]string{ "DOWNSTREAM_USER": conn.User(), "UPSTREAM_USER": s.username, @@ -138,7 +159,7 @@ func (s *skelpipeToWrapper) PrivateKey(conn libplugin.ConnMetadata) ([]byte, []b return p, nil, nil } -func (s *skelpipeToWrapper) OverridePassword(conn libplugin.ConnMetadata) ([]byte, error) { +func (s *skelpipeToPasswordWrapper) OverridePassword(conn libplugin.ConnMetadata) ([]byte, error) { return nil, nil }