diff --git a/e2e/framework/addon/vault.go b/e2e/framework/addon/vault.go index 320000d64..3d4dcbcff 100644 --- a/e2e/framework/addon/vault.go +++ b/e2e/framework/addon/vault.go @@ -251,7 +251,7 @@ func (l *Vault) configureVault() error { // configure appRole l.AppRolePath = "myapprole" req := l.VaultClient.NewRequest(http.MethodGet, fmt.Sprintf("/v1/auth/%s/role/eso-e2e-role/role-id", l.AppRolePath)) - res, err := l.VaultClient.RawRequest(req) + res, err := l.VaultClient.RawRequest(req) //nolint:staticcheck if err != nil { return err } @@ -265,7 +265,7 @@ func (l *Vault) configureVault() error { // parse role id req = l.VaultClient.NewRequest(http.MethodPost, fmt.Sprintf("/v1/auth/%s/role/eso-e2e-role/secret-id", l.AppRolePath)) - res, err = l.VaultClient.RawRequest(req) + res, err = l.VaultClient.RawRequest(req) //nolint:staticcheck if err != nil { return err } diff --git a/e2e/suite/vault/provider.go b/e2e/suite/vault/provider.go index 32c6b9975..c089a9d69 100644 --- a/e2e/suite/vault/provider.go +++ b/e2e/suite/vault/provider.go @@ -65,22 +65,22 @@ func newVaultProvider(f *framework.Framework) *vaultProvider { func (s *vaultProvider) CreateSecret(key string, val framework.SecretEntry) { req := s.client.NewRequest(http.MethodPost, fmt.Sprintf("/v1/secret/data/%s", key)) req.BodyBytes = []byte(fmt.Sprintf(`{"data": %s}`, val.Value)) - _, err := s.client.RawRequestWithContext(context.Background(), req) + _, err := s.client.RawRequestWithContext(context.Background(), req) //nolint:staticcheck Expect(err).ToNot(HaveOccurred()) req = s.client.NewRequest(http.MethodPost, fmt.Sprintf("/v1/secret_v1/%s", key)) req.BodyBytes = []byte(val.Value) - _, err = s.client.RawRequestWithContext(context.Background(), req) + _, err = s.client.RawRequestWithContext(context.Background(), req) //nolint:staticcheck Expect(err).ToNot(HaveOccurred()) } func (s *vaultProvider) DeleteSecret(key string) { req := s.client.NewRequest(http.MethodDelete, fmt.Sprintf("/v1/secret/data/%s", key)) - _, err := s.client.RawRequestWithContext(context.Background(), req) + _, err := s.client.RawRequestWithContext(context.Background(), req) //nolint:staticcheck Expect(err).ToNot(HaveOccurred()) req = s.client.NewRequest(http.MethodDelete, fmt.Sprintf("/v1/secret_v1/%s", key)) - _, err = s.client.RawRequestWithContext(context.Background(), req) + _, err = s.client.RawRequestWithContext(context.Background(), req) //nolint:staticcheck Expect(err).ToNot(HaveOccurred()) } diff --git a/go.mod b/go.mod index e1977055d..af46ea832 100644 --- a/go.mod +++ b/go.mod @@ -57,7 +57,10 @@ require ( github.com/google/go-cmp v0.5.7 github.com/google/uuid v1.3.0 github.com/googleapis/gax-go/v2 v2.2.0 - github.com/hashicorp/vault/api v1.4.1 + github.com/hashicorp/vault/api v1.5.0 + github.com/hashicorp/vault/api/auth/approle v0.1.1 + github.com/hashicorp/vault/api/auth/kubernetes v0.1.0 + github.com/hashicorp/vault/api/auth/ldap v0.1.0 github.com/huandu/xstrings v1.3.2 // indirect github.com/lestrrat-go/jwx v1.2.22 github.com/onsi/ginkgo/v2 v2.1.3 diff --git a/go.sum b/go.sum index b313b25ef..dae4e0d72 100644 --- a/go.sum +++ b/go.sum @@ -489,8 +489,17 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/hashicorp/vault/api v1.4.1 h1:mWLfPT0RhxBitjKr6swieCEP2v5pp/M//t70S3kMLRo= -github.com/hashicorp/vault/api v1.4.1/go.mod h1:LkMdrZnWNrFaQyYYazWVn7KshilfDidgVBq6YiTq/bM= +github.com/hashicorp/vault/api v1.3.0/go.mod h1:EabNQLI0VWbWoGlA+oBLC8PXmR9D60aUVgQGvangFWQ= +github.com/hashicorp/vault/api v1.3.1/go.mod h1:QeJoWxMFt+MsuWcYhmwRLwKEXrjwAFFywzhptMsTIUw= +github.com/hashicorp/vault/api v1.5.0 h1:Bp6yc2bn7CWkOrVIzFT/Qurzx528bdavF3nz590eu28= +github.com/hashicorp/vault/api v1.5.0/go.mod h1:LkMdrZnWNrFaQyYYazWVn7KshilfDidgVBq6YiTq/bM= +github.com/hashicorp/vault/api/auth/approle v0.1.1 h1:R5yA+xcNvw1ix6bDuWOaLOq2L4L77zDCVsethNw97xQ= +github.com/hashicorp/vault/api/auth/approle v0.1.1/go.mod h1:mHOLgh//xDx4dpqXoq6tS8Ob0FoCFWLU2ibJ26Lfmag= +github.com/hashicorp/vault/api/auth/kubernetes v0.1.0 h1:6BtyahbF4aQp8gg3ww0A/oIoqzbhpNP1spXU3nHE0n0= +github.com/hashicorp/vault/api/auth/kubernetes v0.1.0/go.mod h1:Pdgk78uIs0mgDOLvc3a+h/vYIT9rznw2sz+ucuH9024= +github.com/hashicorp/vault/api/auth/ldap v0.1.0 h1:runn+BIRU6/QcGirhstoJIqO+plVuTN/zf401tbB5H0= +github.com/hashicorp/vault/api/auth/ldap v0.1.0/go.mod h1:vl3YZyt+bRtTHvVqKWeOTCI5I40t31t0S48efipZq64= +github.com/hashicorp/vault/sdk v0.3.0/go.mod h1:aZ3fNuL5VNydQk8GcLJ2TV8YCRVvyaakYkhZRoVuhj0= github.com/hashicorp/vault/sdk v0.4.1 h1:3SaHOJY687jY1fnB61PtL0cOkKItphrbLmux7T92HBo= github.com/hashicorp/vault/sdk v0.4.1/go.mod h1:aZ3fNuL5VNydQk8GcLJ2TV8YCRVvyaakYkhZRoVuhj0= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= diff --git a/pkg/provider/vault/fake/vault.go b/pkg/provider/vault/fake/vault.go index 1201c17ad..a81ca87ec 100644 --- a/pkg/provider/vault/fake/vault.go +++ b/pkg/provider/vault/fake/vault.go @@ -16,15 +16,62 @@ package fake import ( "context" - "net/url" - "strings" vault "github.com/hashicorp/vault/api" ) -type MockNewRequestFn func(method, requestPath string) *vault.Request +type LoginFn func(ctx context.Context, authMethod vault.AuthMethod) (*vault.Secret, error) +type Auth struct { + LoginFn LoginFn +} -type MockRawRequestWithContextFn func(ctx context.Context, r *vault.Request) (*vault.Response, error) +func (f Auth) Login(ctx context.Context, authMethod vault.AuthMethod) (*vault.Secret, error) { + return f.LoginFn(ctx, authMethod) +} + +type ReadWithDataWithContextFn func(ctx context.Context, path string, data map[string][]string) (*vault.Secret, error) +type ListWithContextFn func(ctx context.Context, path string) (*vault.Secret, error) +type WriteWithContextFn func(ctx context.Context, path string, data map[string]interface{}) (*vault.Secret, error) + +type Logical struct { + ReadWithDataWithContextFn ReadWithDataWithContextFn + ListWithContextFn ListWithContextFn + WriteWithContextFn WriteWithContextFn +} + +func NewReadWithContextFn(secret map[string]interface{}, err error) ReadWithDataWithContextFn { + return func(ctx context.Context, path string, data map[string][]string) (*vault.Secret, error) { + vault := &vault.Secret{ + Data: secret, + } + return vault, err + } +} + +func (f Logical) ReadWithDataWithContext(ctx context.Context, path string, data map[string][]string) (*vault.Secret, error) { + return f.ReadWithDataWithContextFn(ctx, path, data) +} +func (f Logical) ListWithContext(ctx context.Context, path string) (*vault.Secret, error) { + return f.ListWithContextFn(ctx, path) +} +func (f Logical) WriteWithContext(ctx context.Context, path string, data map[string]interface{}) (*vault.Secret, error) { + return f.WriteWithContextFn(ctx, path, data) +} + +type RevokeSelfWithContextFn func(ctx context.Context, token string) error +type LookupSelfWithContextFn func(ctx context.Context) (*vault.Secret, error) + +type Token struct { + RevokeSelfWithContextFn RevokeSelfWithContextFn + LookupSelfWithContextFn LookupSelfWithContextFn +} + +func (f Token) RevokeSelfWithContext(ctx context.Context, token string) error { + return f.RevokeSelfWithContextFn(ctx, token) +} +func (f Token) LookupSelfWithContext(ctx context.Context) (*vault.Secret, error) { + return f.LookupSelfWithContextFn(ctx) +} type MockSetTokenFn func(v string) @@ -36,59 +83,11 @@ type MockSetNamespaceFn func(namespace string) type MockAddHeaderFn func(key, value string) -func NewMockNewRequestFn(req *vault.Request) MockNewRequestFn { - return func(method, requestPath string) *vault.Request { - return req - } -} - -func NewMockNewRequestListFn(req *vault.Request) MockNewRequestFn { - return func(method, requestPath string) *vault.Request { - urlPath := url.URL{ - Path: requestPath, - } - req.URL = &urlPath - req.Params = make(url.Values) - return req - } -} - -// An RequestFn operates on the supplied Request. You might use an RequestFn to -// test or update the contents of an Request. -type RequestFn func(req *vault.Request) error - -func NewMockRawRequestWithContextFn(res *vault.Response, err error, ofn ...RequestFn) MockRawRequestWithContextFn { - return func(_ context.Context, r *vault.Request) (*vault.Response, error) { - for _, fn := range ofn { - if err := fn(r); err != nil { - return res, err - } - } - return res, err - } -} - type VaultListResponse struct { Metadata *vault.Response Data *vault.Response } -func NewMockRawRequestListWithContextFn(res map[string]VaultListResponse, err error) MockRawRequestWithContextFn { - return func(_ context.Context, r *vault.Request) (*vault.Response, error) { - pathList := strings.Split(r.URL.Path, "/") - path := "default" - if pathList[4] != "" { - path = strings.Join(pathList[4:], "/") - } - if strings.Contains(r.URL.Path, "metadata") { - resp := res[path].Metadata - return resp, err - } - resp := res[path].Data - return resp, err - } -} - func NewSetTokenFn(ofn ...func(v string)) MockSetTokenFn { return func(v string) { for _, fn := range ofn { @@ -116,21 +115,48 @@ func NewAddHeaderFn() MockAddHeaderFn { } type VaultClient struct { - MockNewRequest MockNewRequestFn - MockRawRequestWithContext MockRawRequestWithContextFn - MockSetToken MockSetTokenFn - MockToken MockTokenFn - MockClearToken MockClearTokenFn - MockSetNamespace MockSetNamespaceFn - MockAddHeader MockAddHeaderFn + MockLogical Logical + MockAuth Auth + MockAuthToken Token + MockSetToken MockSetTokenFn + MockToken MockTokenFn + MockClearToken MockClearTokenFn + MockSetNamespace MockSetNamespaceFn + MockAddHeader MockAddHeaderFn } -func (c *VaultClient) NewRequest(method, requestPath string) *vault.Request { - return c.MockNewRequest(method, requestPath) +func (c *VaultClient) Logical() Logical { + return c.MockLogical } -func (c *VaultClient) RawRequestWithContext(ctx context.Context, r *vault.Request) (*vault.Response, error) { - return c.MockRawRequestWithContext(ctx, r) +func NewVaultLogical() Logical { + logical := Logical{ + ReadWithDataWithContextFn: func(ctx context.Context, path string, data map[string][]string) (*vault.Secret, error) { + return nil, nil + }, + ListWithContextFn: func(ctx context.Context, path string) (*vault.Secret, error) { + return nil, nil + }, + WriteWithContextFn: func(ctx context.Context, path string, data map[string]interface{}) (*vault.Secret, error) { + return nil, nil + }, + } + return logical +} +func (c *VaultClient) Auth() Auth { + return c.MockAuth +} + +func NewVaultAuth() Auth { + auth := Auth{ + LoginFn: func(ctx context.Context, authMethod vault.AuthMethod) (*vault.Secret, error) { + return nil, nil + }, + } + return auth +} +func (c *VaultClient) AuthToken() Token { + return c.MockAuthToken } func (c *VaultClient) SetToken(v string) { diff --git a/pkg/provider/vault/vault.go b/pkg/provider/vault/vault.go index fd0f2d5f2..57312f2d5 100644 --- a/pkg/provider/vault/vault.go +++ b/pkg/provider/vault/vault.go @@ -28,6 +28,9 @@ import ( "github.com/go-logr/logr" vault "github.com/hashicorp/vault/api" + approle "github.com/hashicorp/vault/api/auth/approle" + authkubernetes "github.com/hashicorp/vault/api/auth/kubernetes" + authldap "github.com/hashicorp/vault/api/auth/ldap" "github.com/tidwall/gjson" authenticationv1 "k8s.io/api/authentication/v1" corev1 "k8s.io/api/core/v1" @@ -65,9 +68,7 @@ const ( errSecretFormat = "secret data not in expected format" errUnexpectedKey = "unexpected key in data: %s" errVaultToken = "cannot parse Vault authentication token: %w" - errVaultReqParams = "cannot set Vault request parameters: %w" errVaultRequest = "error from Vault request: %w" - errVaultResponse = "cannot parse Vault response: %w" errServiceAccount = "cannot read Kubernetes service account token from file system: %w" errJwtNoTokenSource = "neither `secretRef` nor `kubernetesServiceAccountToken` was supplied as token source for jwt authentication" errUnsupportedKvVersion = "cannot perform find operations with kv version v1" @@ -107,22 +108,84 @@ const ( var _ esv1beta1.SecretsClient = &client{} var _ esv1beta1.Provider = &connector{} +type Auth interface { + Login(ctx context.Context, authMethod vault.AuthMethod) (*vault.Secret, error) +} + +type Token interface { + RevokeSelfWithContext(ctx context.Context, token string) error + LookupSelfWithContext(ctx context.Context) (*vault.Secret, error) +} + +type Logical interface { + ReadWithDataWithContext(ctx context.Context, path string, data map[string][]string) (*vault.Secret, error) + ListWithContext(ctx context.Context, path string) (*vault.Secret, error) + WriteWithContext(ctx context.Context, path string, data map[string]interface{}) (*vault.Secret, error) +} + type Client interface { - NewRequest(method, requestPath string) *vault.Request - RawRequestWithContext(ctx context.Context, r *vault.Request) (*vault.Response, error) SetToken(v string) Token() string ClearToken() + Auth() Auth + Logical() Logical + AuthToken() Token SetNamespace(namespace string) AddHeader(key, value string) } +type VClient struct { + setToken func(v string) + token func() string + clearToken func() + auth Auth + logical Logical + authToken Token + setNamespace func(namespace string) + addHeader func(key, value string) +} + +func (v VClient) AddHeader(key, value string) { + v.addHeader(key, value) +} + +func (v VClient) SetNamespace(namespace string) { + v.setNamespace(namespace) +} + +func (v VClient) ClearToken() { + v.clearToken() +} + +func (v VClient) Token() string { + return v.token() +} + +func (v VClient) SetToken(token string) { + v.setToken(token) +} + +func (v VClient) Auth() Auth { + return v.auth +} + +func (v VClient) AuthToken() Token { + return v.authToken +} + +func (v VClient) Logical() Logical { + return v.logical +} + type client struct { kube kclient.Client - corev1 typedcorev1.CoreV1Interface store *esv1beta1.VaultProvider log logr.Logger + corev1 typedcorev1.CoreV1Interface client Client + auth Auth + logical Logical + token Token namespace string storeKind string } @@ -136,7 +199,24 @@ func init() { } func newVaultClient(c *vault.Config) (Client, error) { - return vault.NewClient(c) + cl, err := vault.NewClient(c) + if err != nil { + return nil, err + } + auth := cl.Auth() + logical := cl.Logical() + token := cl.Auth().Token() + out := VClient{ + setToken: cl.SetToken, + token: cl.Token, + clearToken: cl.ClearToken, + auth: auth, + authToken: token, + logical: logical, + setNamespace: cl.SetNamespace, + addHeader: cl.AddHeader, + } + return out, nil } type connector struct { @@ -191,13 +271,15 @@ func (c *connector) newClient(ctx context.Context, store esv1beta1.GenericStore, if vaultSpec.ReadYourWrites && vaultSpec.ForwardInconsistent { client.AddHeader("X-Vault-Inconsistent", "forward-active-node") } + vStore.client = client + vStore.auth = client.Auth() + vStore.logical = client.Logical() + vStore.token = client.AuthToken() - if err := vStore.setAuth(ctx, client, cfg); err != nil { + if err := vStore.setAuth(ctx, cfg); err != nil { return nil, err } - vStore.client = client - return vStore, nil } @@ -340,16 +422,10 @@ func (v *client) listSecrets(ctx context.Context, path string) ([]string, error) if err != nil { return nil, err } - r := v.client.NewRequest(http.MethodGet, url) - r.Params.Set("list", "true") - resp, err := v.client.RawRequestWithContext(ctx, r) + secret, err := v.logical.ListWithContext(ctx, url) if err != nil { return nil, fmt.Errorf(errReadSecret, err) } - secret, parseErr := vault.ParseSecret(resp.Body) - if parseErr != nil { - return nil, parseErr - } t, ok := secret.Data["keys"] if !ok { return nil, nil @@ -381,15 +457,10 @@ func (v *client) readSecretMetadata(ctx context.Context, path string) (map[strin if err != nil { return nil, err } - r := v.client.NewRequest(http.MethodGet, url) - resp, err := v.client.RawRequestWithContext(ctx, r) + secret, err := v.logical.ReadWithDataWithContext(ctx, url, nil) if err != nil { return nil, fmt.Errorf(errReadSecret, err) } - secret, parseErr := vault.ParseSecret(resp.Body) - if parseErr != nil { - return nil, parseErr - } t, ok := secret.Data["custom_metadata"] if !ok { return nil, nil @@ -490,18 +561,23 @@ func getTypedKey(data map[string]interface{}, key string) ([]byte, error) { func (v *client) Close(ctx context.Context) error { // Revoke the token if we have one set and it wasn't sourced from a TokenSecretRef if v.client.Token() != "" && v.store.Auth.TokenSecretRef == nil { - req := v.client.NewRequest(http.MethodPost, "/v1/auth/token/revoke-self") - _, err := v.client.RawRequestWithContext(ctx, req) + revoke, err := checkToken(ctx, v) if err != nil { return fmt.Errorf(errVaultRevokeToken, err) } - v.client.ClearToken() + if revoke { + err = v.token.RevokeSelfWithContext(ctx, v.client.Token()) + if err != nil { + return fmt.Errorf(errVaultRevokeToken, err) + } + v.client.ClearToken() + } } return nil } func (v *client) Validate() error { - err := checkToken(context.Background(), v) + _, err := checkToken(context.Background(), v) if err != nil { return fmt.Errorf(errInvalidCredentials, err) } @@ -515,9 +591,9 @@ func (v *client) buildMetadataPath(path string) (string, error) { } if v.store.Path == nil { path = strings.Replace(path, "data", "metadata", 1) - url = fmt.Sprintf("/v1/%s", path) + url = path } else { - url = fmt.Sprintf("/v1/%s/metadata/%s", *v.store.Path, path) + url = fmt.Sprintf("%s/metadata/%s", *v.store.Path, path) } return url, nil } @@ -554,21 +630,15 @@ func (v *client) readSecret(ctx context.Context, path, version string) (map[stri // path formated according to vault docs for v1 and v2 API // v1: https://www.vaultproject.io/api-docs/secret/kv/kv-v1#read-secret // v2: https://www.vaultproject.io/api/secret/kv/kv-v2#read-secret-version - req := v.client.NewRequest(http.MethodGet, fmt.Sprintf("/v1/%s", dataPath)) + var params map[string][]string if version != "" { - req.Params.Set("version", version) + params = make(map[string][]string) + params["version"] = []string{version} } - - resp, err := v.client.RawRequestWithContext(ctx, req) + vaultSecret, err := v.logical.ReadWithDataWithContext(ctx, dataPath, params) if err != nil { return nil, fmt.Errorf(errReadSecret, err) } - - vaultSecret, err := vault.ParseSecret(resp.Body) - if err != nil { - return nil, err - } - secretData := vaultSecret.Data if v.store.Version == esv1beta1.VaultKVStoreV2 { // Vault KV2 has data embedded within sub-field @@ -686,33 +756,33 @@ func getCertFromConfigMap(v *client) ([]byte, error) { return []byte(val), nil } -func (v *client) setAuth(ctx context.Context, client Client, cfg *vault.Config) error { - tokenExists, err := setSecretKeyToken(ctx, v, client) +func (v *client) setAuth(ctx context.Context, cfg *vault.Config) error { + tokenExists, err := setSecretKeyToken(ctx, v) if tokenExists { return err } - tokenExists, err = setAppRoleToken(ctx, v, client) + tokenExists, err = setAppRoleToken(ctx, v) if tokenExists { return err } - tokenExists, err = setKubernetesAuthToken(ctx, v, client) + tokenExists, err = setKubernetesAuthToken(ctx, v) if tokenExists { return err } - tokenExists, err = setLdapAuthToken(ctx, v, client) + tokenExists, err = setLdapAuthToken(ctx, v) if tokenExists { return err } - tokenExists, err = setJwtAuthToken(ctx, v, client) + tokenExists, err = setJwtAuthToken(ctx, v) if tokenExists { return err } - tokenExists, err = setCertAuthToken(ctx, v, client, cfg) + tokenExists, err = setCertAuthToken(ctx, v, cfg) if tokenExists { return err } @@ -720,79 +790,74 @@ func (v *client) setAuth(ctx context.Context, client Client, cfg *vault.Config) return errors.New(errAuthFormat) } -func setAppRoleToken(ctx context.Context, v *client, client Client) (bool, error) { +func setAppRoleToken(ctx context.Context, v *client) (bool, error) { tokenRef := v.store.Auth.TokenSecretRef if tokenRef != nil { token, err := v.secretKeyRef(ctx, tokenRef) if err != nil { return true, err } - client.SetToken(token) + v.client.SetToken(token) return true, nil } return false, nil } -func setSecretKeyToken(ctx context.Context, v *client, client Client) (bool, error) { +func setSecretKeyToken(ctx context.Context, v *client) (bool, error) { appRole := v.store.Auth.AppRole if appRole != nil { - token, err := v.requestTokenWithAppRoleRef(ctx, client, appRole) + err := v.requestTokenWithAppRoleRef(ctx, appRole) if err != nil { return true, err } - client.SetToken(token) return true, nil } return false, nil } -func setKubernetesAuthToken(ctx context.Context, v *client, client Client) (bool, error) { +func setKubernetesAuthToken(ctx context.Context, v *client) (bool, error) { kubernetesAuth := v.store.Auth.Kubernetes if kubernetesAuth != nil { - token, err := v.requestTokenWithKubernetesAuth(ctx, client, kubernetesAuth) + err := v.requestTokenWithKubernetesAuth(ctx, kubernetesAuth) if err != nil { return true, err } - client.SetToken(token) return true, nil } return false, nil } -func setLdapAuthToken(ctx context.Context, v *client, client Client) (bool, error) { +func setLdapAuthToken(ctx context.Context, v *client) (bool, error) { ldapAuth := v.store.Auth.Ldap if ldapAuth != nil { - token, err := v.requestTokenWithLdapAuth(ctx, client, ldapAuth) + err := v.requestTokenWithLdapAuth(ctx, ldapAuth) if err != nil { return true, err } - client.SetToken(token) return true, nil } return false, nil } -func setJwtAuthToken(ctx context.Context, v *client, client Client) (bool, error) { +func setJwtAuthToken(ctx context.Context, v *client) (bool, error) { jwtAuth := v.store.Auth.Jwt if jwtAuth != nil { - token, err := v.requestTokenWithJwtAuth(ctx, client, jwtAuth) + err := v.requestTokenWithJwtAuth(ctx, jwtAuth) if err != nil { return true, err } - client.SetToken(token) return true, nil } return false, nil } -func setCertAuthToken(ctx context.Context, v *client, client Client, cfg *vault.Config) (bool, error) { +func setCertAuthToken(ctx context.Context, v *client, cfg *vault.Config) (bool, error) { certAuth := v.store.Auth.Cert if certAuth != nil { - token, err := v.requestTokenWithCertAuth(ctx, client, certAuth, cfg) + err := v.requestTokenWithCertAuth(ctx, certAuth, cfg) if err != nil { return true, err } - client.SetToken(token) return true, nil } return false, nil @@ -878,101 +943,56 @@ func (v *client) serviceAccountToken(ctx context.Context, serviceAccountRef esme } // checkToken does a lookup and checks if the provided token exists. -func checkToken(ctx context.Context, vStore *client) error { +func checkToken(ctx context.Context, vStore *client) (bool, error) { // https://www.vaultproject.io/api-docs/auth/token#lookup-a-token-self - req := vStore.client.NewRequest("GET", "/v1/auth/token/lookup-self") - _, err := vStore.client.RawRequestWithContext(ctx, req) - return err -} - -// appRoleParameters creates the required body for Vault AppRole Auth. -// Reference - https://www.vaultproject.io/api-docs/auth/approle#login-with-approle -func appRoleParameters(role, secret string) map[string]string { - return map[string]string{ - "role_id": role, - "secret_id": secret, + resp, err := vStore.token.LookupSelfWithContext(ctx) + if err != nil { + return false, err } + t, ok := resp.Data["type"] + if !ok { + return false, fmt.Errorf("could not assert token type") + } + tokenType := t.(string) + if tokenType == "batch" { + return false, nil + } + return true, nil } -func (v *client) requestTokenWithAppRoleRef(ctx context.Context, client Client, appRole *esv1beta1.VaultAppRole) (string, error) { +func (v *client) requestTokenWithAppRoleRef(ctx context.Context, appRole *esv1beta1.VaultAppRole) error { roleID := strings.TrimSpace(appRole.RoleID) secretID, err := v.secretKeyRef(ctx, &appRole.SecretRef) if err != nil { - return "", err + return err } - - parameters := appRoleParameters(roleID, secretID) - url := strings.Join([]string{"/v1", "auth", appRole.Path, "login"}, "/") - request := client.NewRequest("POST", url) - - err = request.SetJSONBody(parameters) + secret := approle.SecretID{FromString: secretID} + appRoleClient, err := approle.NewAppRoleAuth(roleID, &secret, approle.WithMountPath(appRole.Path)) if err != nil { - return "", fmt.Errorf(errVaultReqParams, err) + return err } - - resp, err := client.RawRequestWithContext(ctx, request) + _, err = v.auth.Login(ctx, appRoleClient) if err != nil { - return "", fmt.Errorf(errVaultRequest, err) + return err } - - defer resp.Body.Close() - - vaultResult := vault.Secret{} - if err = resp.DecodeJSON(&vaultResult); err != nil { - return "", fmt.Errorf(errVaultResponse, err) - } - - token, err := vaultResult.TokenID() - if err != nil { - return "", fmt.Errorf(errVaultToken, err) - } - - return token, nil + return nil } -// kubeParameters creates the required body for Vault Kubernetes auth. -// Reference - https://www.vaultproject.io/api/auth/kubernetes#login -func kubeParameters(role, jwt string) map[string]string { - return map[string]string{ - "role": role, - "jwt": jwt, - } -} - -func (v *client) requestTokenWithKubernetesAuth(ctx context.Context, client Client, kubernetesAuth *esv1beta1.VaultKubernetesAuth) (string, error) { +func (v *client) requestTokenWithKubernetesAuth(ctx context.Context, kubernetesAuth *esv1beta1.VaultKubernetesAuth) error { jwtString, err := getJwtString(ctx, v, kubernetesAuth) if err != nil { - return "", err + return err } - - parameters := kubeParameters(kubernetesAuth.Role, jwtString) - url := strings.Join([]string{"/v1", "auth", kubernetesAuth.Path, "login"}, "/") - request := client.NewRequest("POST", url) - - err = request.SetJSONBody(parameters) + k, err := authkubernetes.NewKubernetesAuth(kubernetesAuth.Role, authkubernetes.WithServiceAccountToken(jwtString), authkubernetes.WithMountPath(kubernetesAuth.Path)) if err != nil { - return "", fmt.Errorf(errVaultReqParams, err) + return err } - - resp, err := client.RawRequestWithContext(ctx, request) + _, err = v.auth.Login(ctx, k) if err != nil { - return "", fmt.Errorf(errVaultRequest, err) + return err } - - defer resp.Body.Close() - vaultResult := vault.Secret{} - err = resp.DecodeJSON(&vaultResult) - if err != nil { - return "", fmt.Errorf(errVaultResponse, err) - } - - token, err := vaultResult.TokenID() - if err != nil { - return "", fmt.Errorf(errVaultToken, err) - } - - return token, nil + return nil } func getJwtString(ctx context.Context, v *client, kubernetesAuth *esv1beta1.VaultKubernetesAuth) (string, error) { @@ -1008,48 +1028,27 @@ func getJwtString(ctx context.Context, v *client, kubernetesAuth *esv1beta1.Vaul } } -func (v *client) requestTokenWithLdapAuth(ctx context.Context, client Client, ldapAuth *esv1beta1.VaultLdapAuth) (string, error) { +func (v *client) requestTokenWithLdapAuth(ctx context.Context, ldapAuth *esv1beta1.VaultLdapAuth) error { username := strings.TrimSpace(ldapAuth.Username) password, err := v.secretKeyRef(ctx, &ldapAuth.SecretRef) if err != nil { - return "", err + return err } - - parameters := map[string]string{ - "password": password, - } - url := strings.Join([]string{"/v1", "auth", ldapAuth.Path, "login", username}, "/") - request := client.NewRequest("POST", url) - - err = request.SetJSONBody(parameters) + pass := authldap.Password{FromString: password} + l, err := authldap.NewLDAPAuth(username, &pass, authldap.WithMountPath(ldapAuth.Path)) if err != nil { - return "", fmt.Errorf(errVaultReqParams, err) + return err } - - resp, err := client.RawRequestWithContext(ctx, request) + _, err = v.auth.Login(ctx, l) if err != nil { - return "", fmt.Errorf(errVaultRequest, err) + return err } - - defer resp.Body.Close() - - vaultResult := vault.Secret{} - if err = resp.DecodeJSON(&vaultResult); err != nil { - return "", fmt.Errorf(errVaultResponse, err) - } - - token, err := vaultResult.TokenID() - if err != nil { - return "", fmt.Errorf(errVaultToken, err) - } - - return token, nil + return nil } -func (v *client) requestTokenWithJwtAuth(ctx context.Context, client Client, jwtAuth *esv1beta1.VaultJwtAuth) (string, error) { +func (v *client) requestTokenWithJwtAuth(ctx context.Context, jwtAuth *esv1beta1.VaultJwtAuth) error { role := strings.TrimSpace(jwtAuth.Role) - var jwt string var err error if jwtAuth.SecretRef != nil { @@ -1069,80 +1068,56 @@ func (v *client) requestTokenWithJwtAuth(ctx context.Context, client Client, jwt err = fmt.Errorf(errJwtNoTokenSource) } if err != nil { - return "", err + return err } - parameters := map[string]string{ + parameters := map[string]interface{}{ "role": role, "jwt": jwt, } - url := strings.Join([]string{"/v1", "auth", jwtAuth.Path, "login"}, "/") - request := client.NewRequest("POST", url) - - err = request.SetJSONBody(parameters) + url := strings.Join([]string{"auth", jwtAuth.Path, "login"}, "/") + vaultResult, err := v.logical.WriteWithContext(ctx, url, parameters) if err != nil { - return "", fmt.Errorf(errVaultReqParams, err) - } - - resp, err := client.RawRequestWithContext(ctx, request) - if err != nil { - return "", fmt.Errorf(errVaultRequest, err) - } - - defer resp.Body.Close() - - vaultResult := vault.Secret{} - if err = resp.DecodeJSON(&vaultResult); err != nil { - return "", fmt.Errorf(errVaultResponse, err) + return err } token, err := vaultResult.TokenID() if err != nil { - return "", fmt.Errorf(errVaultToken, err) + return fmt.Errorf(errVaultToken, err) } - - return token, nil + v.client.SetToken(token) + return nil } -func (v *client) requestTokenWithCertAuth(ctx context.Context, client Client, certAuth *esv1beta1.VaultCertAuth, cfg *vault.Config) (string, error) { +func (v *client) requestTokenWithCertAuth(ctx context.Context, certAuth *esv1beta1.VaultCertAuth, cfg *vault.Config) error { clientKey, err := v.secretKeyRef(ctx, &certAuth.SecretRef) if err != nil { - return "", err + return err } clientCert, err := v.secretKeyRef(ctx, &certAuth.ClientCert) if err != nil { - return "", err + return err } cert, err := tls.X509KeyPair([]byte(clientCert), []byte(clientKey)) if err != nil { - return "", fmt.Errorf(errClientTLSAuth, err) + return fmt.Errorf(errClientTLSAuth, err) } if transport, ok := cfg.HttpClient.Transport.(*http.Transport); ok { transport.TLSClientConfig.Certificates = []tls.Certificate{cert} } - url := strings.Join([]string{"/v1", "auth", "cert", "login"}, "/") - request := client.NewRequest("POST", url) - - resp, err := client.RawRequestWithContext(ctx, request) + url := strings.Join([]string{"auth", "cert", "login"}, "/") + vaultResult, err := v.logical.WriteWithContext(ctx, url, nil) if err != nil { - return "", fmt.Errorf(errVaultRequest, err) + return fmt.Errorf(errVaultRequest, err) } - - defer resp.Body.Close() - - vaultResult := vault.Secret{} - if err = resp.DecodeJSON(&vaultResult); err != nil { - return "", fmt.Errorf(errVaultResponse, err) - } - token, err := vaultResult.TokenID() if err != nil { - return "", fmt.Errorf(errVaultToken, err) + return fmt.Errorf(errVaultToken, err) } - - return token, nil + v.client.SetToken(token) + return nil } diff --git a/pkg/provider/vault/vault_test.go b/pkg/provider/vault/vault_test.go index 0c9d53a4f..cbeed6499 100644 --- a/pkg/provider/vault/vault_test.go +++ b/pkg/provider/vault/vault_test.go @@ -15,13 +15,10 @@ limitations under the License. package vault import ( - "bytes" "context" - "encoding/json" "errors" "fmt" - "io" - "net/http" + "strings" "testing" "github.com/crossplane/crossplane-runtime/pkg/test" @@ -171,45 +168,6 @@ func makeSecretStore(tweaks ...secretStoreTweakFn) *esv1beta1.SecretStore { return store } -func newVaultResponse(data *vault.Secret) *vault.Response { - jsonData, _ := json.Marshal(data) - return &vault.Response{ - Response: &http.Response{ - Body: io.NopCloser(bytes.NewReader(jsonData)), - }, - } -} - -func newVaultResponseWithData(data map[string]interface{}) *vault.Response { - return newVaultResponse(&vault.Secret{ - Data: data, - }) -} - -func newVaultResponseWithMetadata(content map[string]interface{}) map[string]fake.VaultListResponse { - ans := make(map[string]fake.VaultListResponse) - for k, v := range content { - t := v.(map[string]interface{}) - m := t["metadata"].(map[string]interface{}) - listResponse := fake.VaultListResponse{ - Data: newVaultResponse(&vault.Secret{ - Data: t, - }), - Metadata: newVaultResponse(&vault.Secret{ - Data: m, - }), - } - ans[k] = listResponse - } - return ans -} - -func newVaultTokenIDResponse(token string) *vault.Response { - return newVaultResponseWithData(map[string]interface{}{ - "id": token, - }) -} - type args struct { newClientFunc func(c *vault.Config) (Client, error) store esv1beta1.GenericStore @@ -228,12 +186,25 @@ type testCase struct { } func clientWithLoginMock(c *vault.Config) (Client, error) { - return &fake.VaultClient{ - MockNewRequest: fake.NewMockNewRequestFn(&vault.Request{}), - MockRawRequestWithContext: fake.NewMockRawRequestWithContextFn( - newVaultTokenIDResponse("test-token"), nil, func(got *vault.Request) error { return nil }), + cl := fake.VaultClient{ MockSetToken: fake.NewSetTokenFn(), - }, nil + MockAuth: fake.NewVaultAuth(), + MockLogical: fake.NewVaultLogical(), + } + auth := cl.Auth() + token := cl.AuthToken() + logical := cl.Logical() + out := VClient{ + setToken: cl.SetToken, + token: cl.Token, + clearToken: cl.ClearToken, + auth: auth, + authToken: token, + logical: logical, + setNamespace: cl.SetNamespace, + addHeader: cl.AddHeader, + } + return out, nil } func kubeMockWithSecretTokenAndServiceAcc(obj kclient.Object) error { @@ -327,36 +298,6 @@ MIICsTCCAZkCFEJJ4daz5sxkFlzq9n1djLEuG7bmMA0GCSqGSIb3DQEBCwUAMBMxETAPBgNVBAMMCHZh err: fmt.Errorf(errGetKubeSecret, "vault-secret", errBoom), }, }, - "SuccessfulVaultStore": { - reason: "Should return a Vault provider successfully", - args: args{ - store: makeSecretStore(), - kube: &test.MockClient{ - MockGet: test.NewMockGetFn(nil, kubeMockWithSecretTokenAndServiceAcc), - }, - newClientFunc: func(c *vault.Config) (Client, error) { - return &fake.VaultClient{ - MockNewRequest: fake.NewMockNewRequestFn(&vault.Request{}), - MockRawRequestWithContext: fake.NewMockRawRequestWithContextFn( - newVaultTokenIDResponse("test-token"), nil, func(got *vault.Request) error { - kubeRole := makeValidSecretStore().Spec.Provider.Vault.Auth.Kubernetes.Role - want := kubeParameters(kubeRole, string(secretData)) - if diff := cmp.Diff(want, got.Obj); diff != "" { - t.Errorf("RawRequestWithContext(...): -want, +got:\n%s", diff) - } - - return nil - }), - MockSetToken: fake.NewSetTokenFn(), - MockToken: fake.NewTokenFn(""), - MockClearToken: fake.NewClearTokenFn(), - }, nil - }, - }, - want: want{ - err: nil, - }, - }, "SuccessfulVaultStoreWithCertAuth": { reason: "Should return a Vault provider successfully", args: args{ @@ -592,11 +533,11 @@ func TestGetSecret(t *testing.T) { } type args struct { - store *esv1beta1.VaultProvider - kube kclient.Client - vClient Client - ns string - data esv1beta1.ExternalSecretDataRemoteRef + store *esv1beta1.VaultProvider + kube kclient.Client + vLogical Logical + ns string + data esv1beta1.ExternalSecretDataRemoteRef } type want struct { @@ -616,11 +557,8 @@ func TestGetSecret(t *testing.T) { data: esv1beta1.ExternalSecretDataRemoteRef{ Property: "access_key", }, - vClient: &fake.VaultClient{ - MockNewRequest: fake.NewMockNewRequestFn(&vault.Request{}), - MockRawRequestWithContext: fake.NewMockRawRequestWithContextFn( - newVaultResponseWithData(secret), nil, - ), + vLogical: &fake.Logical{ + ReadWithDataWithContextFn: fake.NewReadWithContextFn(secret, nil), }, }, want: want{ @@ -635,11 +573,8 @@ func TestGetSecret(t *testing.T) { data: esv1beta1.ExternalSecretDataRemoteRef{ Property: "access_key", }, - vClient: &fake.VaultClient{ - MockNewRequest: fake.NewMockNewRequestFn(&vault.Request{}), - MockRawRequestWithContext: fake.NewMockRawRequestWithContextFn( - newVaultResponseWithData(secretWithNilVal), nil, - ), + vLogical: &fake.Logical{ + ReadWithDataWithContextFn: fake.NewReadWithContextFn(secretWithNilVal, nil), }, }, want: want{ @@ -652,11 +587,8 @@ func TestGetSecret(t *testing.T) { args: args{ store: makeValidSecretStoreWithVersion(esv1beta1.VaultKVStoreV1).Spec.Provider.Vault, data: esv1beta1.ExternalSecretDataRemoteRef{}, - vClient: &fake.VaultClient{ - MockNewRequest: fake.NewMockNewRequestFn(&vault.Request{}), - MockRawRequestWithContext: fake.NewMockRawRequestWithContextFn( - newVaultResponseWithData(secret), nil, - ), + vLogical: &fake.Logical{ + ReadWithDataWithContextFn: fake.NewReadWithContextFn(secret, nil), }, }, want: want{ @@ -671,11 +603,8 @@ func TestGetSecret(t *testing.T) { data: esv1beta1.ExternalSecretDataRemoteRef{ Property: "nested.foo", }, - vClient: &fake.VaultClient{ - MockNewRequest: fake.NewMockNewRequestFn(&vault.Request{}), - MockRawRequestWithContext: fake.NewMockRawRequestWithContextFn( - newVaultResponseWithData(secretWithNestedVal), nil, - ), + vLogical: &fake.Logical{ + ReadWithDataWithContextFn: fake.NewReadWithContextFn(secretWithNestedVal, nil), }, }, want: want{ @@ -691,11 +620,8 @@ func TestGetSecret(t *testing.T) { // Property: "nested.bar", }, - vClient: &fake.VaultClient{ - MockNewRequest: fake.NewMockNewRequestFn(&vault.Request{}), - MockRawRequestWithContext: fake.NewMockRawRequestWithContextFn( - newVaultResponseWithData(secretWithNestedVal), nil, - ), + vLogical: &fake.Logical{ + ReadWithDataWithContextFn: fake.NewReadWithContextFn(secretWithNestedVal, nil), }, }, want: want{ @@ -710,11 +636,8 @@ func TestGetSecret(t *testing.T) { data: esv1beta1.ExternalSecretDataRemoteRef{ Property: "nop.doesnt.exist", }, - vClient: &fake.VaultClient{ - MockNewRequest: fake.NewMockNewRequestFn(&vault.Request{}), - MockRawRequestWithContext: fake.NewMockRawRequestWithContextFn( - newVaultResponseWithData(secretWithNestedVal), nil, - ), + vLogical: &fake.Logical{ + ReadWithDataWithContextFn: fake.NewReadWithContextFn(secretWithNestedVal, nil), }, }, want: want{ @@ -725,9 +648,8 @@ func TestGetSecret(t *testing.T) { reason: "Should return error if vault client fails to read secret.", args: args{ store: makeSecretStore().Spec.Provider.Vault, - vClient: &fake.VaultClient{ - MockNewRequest: fake.NewMockNewRequestFn(&vault.Request{}), - MockRawRequestWithContext: fake.NewMockRawRequestWithContextFn(nil, errBoom), + vLogical: &fake.Logical{ + ReadWithDataWithContextFn: fake.NewReadWithContextFn(nil, errBoom), }, }, want: want{ @@ -740,7 +662,7 @@ func TestGetSecret(t *testing.T) { t.Run(name, func(t *testing.T) { vStore := &client{ kube: tc.args.kube, - client: tc.args.vClient, + logical: tc.args.vLogical, store: tc.args.store, namespace: tc.args.ns, } @@ -788,7 +710,7 @@ func TestGetSecretMap(t *testing.T) { type args struct { store *esv1beta1.VaultProvider kube kclient.Client - vClient Client + vClient Logical ns string data esv1beta1.ExternalSecretDataRemoteRef } @@ -807,11 +729,8 @@ func TestGetSecretMap(t *testing.T) { reason: "Should map the secret even if it has a nil value", args: args{ store: makeValidSecretStoreWithVersion(esv1beta1.VaultKVStoreV1).Spec.Provider.Vault, - vClient: &fake.VaultClient{ - MockNewRequest: fake.NewMockNewRequestFn(&vault.Request{}), - MockRawRequestWithContext: fake.NewMockRawRequestWithContextFn( - newVaultResponseWithData(secret), nil, - ), + vClient: &fake.Logical{ + ReadWithDataWithContextFn: fake.NewReadWithContextFn(secret, nil), }, }, want: want{ @@ -826,15 +745,10 @@ func TestGetSecretMap(t *testing.T) { reason: "Should map the secret even if it has a nil value", args: args{ store: makeValidSecretStoreWithVersion(esv1beta1.VaultKVStoreV2).Spec.Provider.Vault, - vClient: &fake.VaultClient{ - MockNewRequest: fake.NewMockNewRequestFn(&vault.Request{}), - MockRawRequestWithContext: fake.NewMockRawRequestWithContextFn( - newVaultResponseWithData( - map[string]interface{}{ - "data": secret, - }, - ), nil, - ), + vClient: &fake.Logical{ + ReadWithDataWithContextFn: fake.NewReadWithContextFn(map[string]interface{}{ + "data": secret, + }, nil), }, }, want: want{ @@ -849,11 +763,8 @@ func TestGetSecretMap(t *testing.T) { reason: "Should map the secret even if it has a nil value", args: args{ store: makeValidSecretStoreWithVersion(esv1beta1.VaultKVStoreV1).Spec.Provider.Vault, - vClient: &fake.VaultClient{ - MockNewRequest: fake.NewMockNewRequestFn(&vault.Request{}), - MockRawRequestWithContext: fake.NewMockRawRequestWithContextFn( - newVaultResponseWithData(secretWithNilVal), nil, - ), + vClient: &fake.Logical{ + ReadWithDataWithContextFn: fake.NewReadWithContextFn(secretWithNilVal, nil), }, }, want: want{ @@ -869,15 +780,9 @@ func TestGetSecretMap(t *testing.T) { reason: "Should map the secret even if it has a nil value", args: args{ store: makeValidSecretStoreWithVersion(esv1beta1.VaultKVStoreV2).Spec.Provider.Vault, - vClient: &fake.VaultClient{ - MockNewRequest: fake.NewMockNewRequestFn(&vault.Request{}), - MockRawRequestWithContext: fake.NewMockRawRequestWithContextFn( - newVaultResponseWithData( - map[string]interface{}{ - "data": secretWithNilVal, - }, - ), nil, - ), + vClient: &fake.Logical{ + ReadWithDataWithContextFn: fake.NewReadWithContextFn(map[string]interface{}{ + "data": secretWithNilVal}, nil), }, }, want: want{ @@ -893,15 +798,9 @@ func TestGetSecretMap(t *testing.T) { reason: "Should map the secret even if it has other types", args: args{ store: makeValidSecretStoreWithVersion(esv1beta1.VaultKVStoreV2).Spec.Provider.Vault, - vClient: &fake.VaultClient{ - MockNewRequest: fake.NewMockNewRequestFn(&vault.Request{}), - MockRawRequestWithContext: fake.NewMockRawRequestWithContextFn( - newVaultResponseWithData( - map[string]interface{}{ - "data": secretWithTypes, - }, - ), nil, - ), + vClient: &fake.Logical{ + ReadWithDataWithContextFn: fake.NewReadWithContextFn(map[string]interface{}{ + "data": secretWithTypes}, nil), }, }, want: want{ @@ -923,15 +822,9 @@ func TestGetSecretMap(t *testing.T) { data: esv1beta1.ExternalSecretDataRemoteRef{ Property: "nested", }, - vClient: &fake.VaultClient{ - MockNewRequest: fake.NewMockNewRequestFn(&vault.Request{}), - MockRawRequestWithContext: fake.NewMockRawRequestWithContextFn( - newVaultResponseWithData( - map[string]interface{}{ - "data": secretWithNestedVal, - }, - ), nil, - ), + vClient: &fake.Logical{ + ReadWithDataWithContextFn: fake.NewReadWithContextFn(map[string]interface{}{ + "data": secretWithNestedVal}, nil), }, }, want: want{ @@ -948,15 +841,9 @@ func TestGetSecretMap(t *testing.T) { data: esv1beta1.ExternalSecretDataRemoteRef{ Property: "nested.foo", }, - vClient: &fake.VaultClient{ - MockNewRequest: fake.NewMockNewRequestFn(&vault.Request{}), - MockRawRequestWithContext: fake.NewMockRawRequestWithContextFn( - newVaultResponseWithData( - map[string]interface{}{ - "data": secretWithNestedVal, - }, - ), nil, - ), + vClient: &fake.Logical{ + ReadWithDataWithContextFn: fake.NewReadWithContextFn(map[string]interface{}{ + "data": secretWithNestedVal}, nil), }, }, want: want{ @@ -971,9 +858,8 @@ func TestGetSecretMap(t *testing.T) { reason: "Should return error if vault client fails to read secret.", args: args{ store: makeSecretStore().Spec.Provider.Vault, - vClient: &fake.VaultClient{ - MockNewRequest: fake.NewMockNewRequestFn(&vault.Request{}), - MockRawRequestWithContext: fake.NewMockRawRequestWithContextFn(nil, errBoom), + vClient: &fake.Logical{ + ReadWithDataWithContextFn: fake.NewReadWithContextFn(nil, errBoom), }, }, want: want{ @@ -986,7 +872,7 @@ func TestGetSecretMap(t *testing.T) { t.Run(name, func(t *testing.T) { vStore := &client{ kube: tc.args.kube, - client: tc.args.vClient, + logical: tc.args.vClient, store: tc.args.store, namespace: tc.args.ns, } @@ -1001,6 +887,50 @@ func TestGetSecretMap(t *testing.T) { } } +func newListWithContextFn(secrets map[string]interface{}) func(ctx context.Context, path string) (*vault.Secret, error) { + return func(ctx context.Context, path string) (*vault.Secret, error) { + path = strings.TrimPrefix(path, "secret/metadata/") + if path == "" { + path = "default" + } + data, ok := secrets[path] + if !ok { + return nil, errors.New("Secret not found") + } + meta := data.(map[string]interface{}) + ans := meta["metadata"].(map[string]interface{}) + secret := &vault.Secret{ + Data: map[string]interface{}{ + "keys": ans["keys"], + }, + } + return secret, nil + } +} + +func newReadtWithContextFn(secrets map[string]interface{}) func(ctx context.Context, path string, data map[string][]string) (*vault.Secret, error) { + return func(ctx context.Context, path string, d map[string][]string) (*vault.Secret, error) { + path = strings.TrimPrefix(path, "secret/data/") + path = strings.TrimPrefix(path, "secret/metadata/") + if path == "" { + path = "default" + } + data, ok := secrets[path] + if !ok { + return nil, errors.New("Secret not found") + } + meta := data.(map[string]interface{}) + metadata := meta["metadata"].(map[string]interface{}) + content := map[string]interface{}{ + "data": meta["data"], + "custom_metadata": metadata["custom_metadata"], + } + secret := &vault.Secret{ + Data: content, + } + return secret, nil + } +} func TestGetAllSecrets(t *testing.T) { secret1Bytes := []byte("{\"access_key\":\"access_key\",\"access_secret\":\"access_secret\"}") secret2Bytes := []byte("{\"access_key\":\"access_key2\",\"access_secret\":\"access_secret2\"}") @@ -1010,66 +940,66 @@ func TestGetAllSecrets(t *testing.T) { path := "path" secret := map[string]interface{}{ "secret1": map[string]interface{}{ - "data": map[string]interface{}{ - "access_key": "access_key", - "access_secret": "access_secret", - }, "metadata": map[string]interface{}{ "custom_metadata": map[string]interface{}{ "foo": "bar", }, }, + "data": map[string]interface{}{ + "access_key": "access_key", + "access_secret": "access_secret", + }, }, "secret2": map[string]interface{}{ + "metadata": map[string]interface{}{ + "custom_metadata": map[string]interface{}{ + "foo": "baz", + }, + }, "data": map[string]interface{}{ "access_key": "access_key2", "access_secret": "access_secret2", }, + }, + "tag": map[string]interface{}{ "metadata": map[string]interface{}{ "custom_metadata": map[string]interface{}{ "foo": "baz", }, }, - }, - "tag": map[string]interface{}{ "data": map[string]interface{}{ "access_key": "unfetched", "access_secret": "unfetched", }, - "metadata": map[string]interface{}{ - "custom_metadata": map[string]interface{}{ - "foo": "baz", - }, - }, }, "path/1": map[string]interface{}{ + "metadata": map[string]interface{}{ + "custom_metadata": map[string]interface{}{ + "foo": "path", + }, + }, "data": map[string]interface{}{ "access_key": "path1", "access_secret": "path1", }, + }, + "path/2": map[string]interface{}{ "metadata": map[string]interface{}{ "custom_metadata": map[string]interface{}{ "foo": "path", }, }, - }, - "path/2": map[string]interface{}{ "data": map[string]interface{}{ "access_key": "path2", "access_secret": "path2", }, - "metadata": map[string]interface{}{ - "custom_metadata": map[string]interface{}{ - "foo": "path", - }, - }, }, "default": map[string]interface{}{ "data": map[string]interface{}{ "empty": "true", }, "metadata": map[string]interface{}{ - "keys": []string{"secret1", "secret2", "tag", "path/"}, + "keys": []interface{}{"secret1", "secret2", "tag", "path/"}, }, }, "path/": map[string]interface{}{ @@ -1077,16 +1007,16 @@ func TestGetAllSecrets(t *testing.T) { "empty": "true", }, "metadata": map[string]interface{}{ - "keys": []string{"1", "2"}, + "keys": []interface{}{"1", "2"}, }, }, } type args struct { - store *esv1beta1.VaultProvider - kube kclient.Client - vClient Client - ns string - data esv1beta1.ExternalSecretFind + store *esv1beta1.VaultProvider + kube kclient.Client + vLogical Logical + ns string + data esv1beta1.ExternalSecretFind } type want struct { @@ -1103,11 +1033,9 @@ func TestGetAllSecrets(t *testing.T) { reason: "should map multiple secrets matching name", args: args{ store: makeValidSecretStoreWithVersion(esv1beta1.VaultKVStoreV2).Spec.Provider.Vault, - vClient: &fake.VaultClient{ - MockNewRequest: fake.NewMockNewRequestListFn(&vault.Request{}), - MockRawRequestWithContext: fake.NewMockRawRequestListWithContextFn( - newVaultResponseWithMetadata(secret), nil, - ), + vLogical: &fake.Logical{ + ListWithContextFn: newListWithContextFn(secret), + ReadWithDataWithContextFn: newReadtWithContextFn(secret), }, data: esv1beta1.ExternalSecretFind{ Name: &esv1beta1.FindName{ @@ -1127,11 +1055,9 @@ func TestGetAllSecrets(t *testing.T) { reason: "should map multiple secrets matching tags", args: args{ store: makeValidSecretStoreWithVersion(esv1beta1.VaultKVStoreV2).Spec.Provider.Vault, - vClient: &fake.VaultClient{ - MockNewRequest: fake.NewMockNewRequestListFn(&vault.Request{}), - MockRawRequestWithContext: fake.NewMockRawRequestListWithContextFn( - newVaultResponseWithMetadata(secret), nil, - ), + vLogical: &fake.Logical{ + ListWithContextFn: newListWithContextFn(secret), + ReadWithDataWithContextFn: newReadtWithContextFn(secret), }, data: esv1beta1.ExternalSecretFind{ Tags: map[string]string{ @@ -1151,11 +1077,9 @@ func TestGetAllSecrets(t *testing.T) { reason: "should filter secrets based on path", args: args{ store: makeValidSecretStoreWithVersion(esv1beta1.VaultKVStoreV2).Spec.Provider.Vault, - vClient: &fake.VaultClient{ - MockNewRequest: fake.NewMockNewRequestListFn(&vault.Request{}), - MockRawRequestWithContext: fake.NewMockRawRequestListWithContextFn( - newVaultResponseWithMetadata(secret), nil, - ), + vLogical: &fake.Logical{ + ListWithContextFn: newListWithContextFn(secret), + ReadWithDataWithContextFn: newReadtWithContextFn(secret), }, data: esv1beta1.ExternalSecretFind{ Path: &path, @@ -1176,11 +1100,9 @@ func TestGetAllSecrets(t *testing.T) { reason: "should not work if using kv1 store", args: args{ store: makeValidSecretStoreWithVersion(esv1beta1.VaultKVStoreV1).Spec.Provider.Vault, - vClient: &fake.VaultClient{ - MockNewRequest: fake.NewMockNewRequestListFn(&vault.Request{}), - MockRawRequestWithContext: fake.NewMockRawRequestListWithContextFn( - newVaultResponseWithMetadata(secret), nil, - ), + vLogical: &fake.Logical{ + ListWithContextFn: newListWithContextFn(secret), + ReadWithDataWithContextFn: newReadtWithContextFn(secret), }, data: esv1beta1.ExternalSecretFind{ Tags: map[string]string{ @@ -1198,7 +1120,7 @@ func TestGetAllSecrets(t *testing.T) { t.Run(name, func(t *testing.T) { vStore := &client{ kube: tc.args.kube, - client: tc.args.vClient, + logical: tc.args.vLogical, store: tc.args.store, namespace: tc.args.ns, }