From e4d8fb404669f0fedf64787f9fdbd6050349be3c Mon Sep 17 00:00:00 2001 From: xxxbobrxxx Date: Thu, 13 May 2021 19:19:57 +0300 Subject: [PATCH] #150 Implement LDAP and JWT/OIDC auth methods --- .../v1alpha1/secretstore_vault_types.go | 19 +++- .../v1alpha1/zz_generated.deepcopy.go | 27 ++++-- ...ternal-secrets.io_clustersecretstores.yaml | 35 ++++++++ .../external-secrets.io_secretstores.yaml | 35 ++++++++ pkg/provider/vault/vault.go | 89 +++++++++++++++++++ 5 files changed, 199 insertions(+), 6 deletions(-) diff --git a/apis/externalsecrets/v1alpha1/secretstore_vault_types.go b/apis/externalsecrets/v1alpha1/secretstore_vault_types.go index ee18e262..47af791e 100644 --- a/apis/externalsecrets/v1alpha1/secretstore_vault_types.go +++ b/apis/externalsecrets/v1alpha1/secretstore_vault_types.go @@ -82,6 +82,11 @@ type VaultAuth struct { // the LDAP authentication method // +optional Ldap *VaultLdapAuth `json:"ldap,omitempty"` + + // Jwt authenticates with Vault by passing role and JWT token using the + // JWT/OIDC authentication method + // +optional + Jwt *VaultJwtAuth `json:"jwt,omitempty"` } // VaultAppRole authenticates with Vault using the App Role auth mechanism, @@ -140,5 +145,17 @@ type VaultLdapAuth struct { // SecretRef to a key in a Secret resource containing password for the LDAP // user used to authenticate with Vault using the LDAP authentication // method - SecretRef *esmeta.SecretKeySelector `json:"tokenSecretRef,omitempty"` + SecretRef esmeta.SecretKeySelector `json:"tokenSecretRef,omitempty"` +} + +// VaultJwtAuth authenticates with Vault using the JWT/OIDC authentication +// method, with the role name and token stored in a Kubernetes Secret resource. +type VaultJwtAuth struct { + // Role is a JWT role to authenticate using the JWT/OIDC Vault + // authentication method + Role string `json:"role"` + + // SecretRef to a key in a Secret resource containing JWT token to + // authenticate with Vault using the JWT/OIDC authentication method + SecretRef esmeta.SecretKeySelector `json:"tokenSecretRef,omitempty"` } diff --git a/apis/externalsecrets/v1alpha1/zz_generated.deepcopy.go b/apis/externalsecrets/v1alpha1/zz_generated.deepcopy.go index f1d34e5a..c65f2ca4 100644 --- a/apis/externalsecrets/v1alpha1/zz_generated.deepcopy.go +++ b/apis/externalsecrets/v1alpha1/zz_generated.deepcopy.go @@ -572,6 +572,11 @@ func (in *VaultAuth) DeepCopyInto(out *VaultAuth) { *out = new(VaultLdapAuth) (*in).DeepCopyInto(*out) } + if in.Jwt != nil { + in, out := &in.Jwt, &out.Jwt + *out = new(VaultJwtAuth) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VaultAuth. @@ -584,6 +589,22 @@ func (in *VaultAuth) DeepCopy() *VaultAuth { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VaultJwtAuth) DeepCopyInto(out *VaultJwtAuth) { + *out = *in + in.SecretRef.DeepCopyInto(&out.SecretRef) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VaultJwtAuth. +func (in *VaultJwtAuth) DeepCopy() *VaultJwtAuth { + if in == nil { + return nil + } + out := new(VaultJwtAuth) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *VaultKubernetesAuth) DeepCopyInto(out *VaultKubernetesAuth) { *out = *in @@ -612,11 +633,7 @@ func (in *VaultKubernetesAuth) DeepCopy() *VaultKubernetesAuth { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *VaultLdapAuth) DeepCopyInto(out *VaultLdapAuth) { *out = *in - if in.SecretRef != nil { - in, out := &in.SecretRef, &out.SecretRef - *out = new(metav1.SecretKeySelector) - (*in).DeepCopyInto(*out) - } + in.SecretRef.DeepCopyInto(&out.SecretRef) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VaultLdapAuth. diff --git a/deploy/crds/external-secrets.io_clustersecretstores.yaml b/deploy/crds/external-secrets.io_clustersecretstores.yaml index f1648948..da8aa8db 100644 --- a/deploy/crds/external-secrets.io_clustersecretstores.yaml +++ b/deploy/crds/external-secrets.io_clustersecretstores.yaml @@ -189,6 +189,41 @@ spec: - roleId - secretRef type: object + jwt: + description: Jwt authenticates with Vault by passing role + and JWT token using the JWT/OIDC authentication method + properties: + role: + description: Role is a JWT role to authenticate using + the JWT/OIDC Vault authentication method + type: string + tokenSecretRef: + description: SecretRef to a key in a Secret resource + containing JWT token to authenticate with Vault + using the LDAP authentication method + properties: + key: + description: The key of the entry in the Secret + resource's `data` field to be used. Some instances + of this field may be defaulted, in others it + may be required. + type: string + name: + description: The name of the Secret resource being + referred to. + type: string + namespace: + description: Namespace of the resource being referred + to. Ignored if referent is not cluster-scoped. + cluster-scoped defaults to the namespace of + the referent. + type: string + required: + - name + type: object + required: + - role + type: object kubernetes: description: Kubernetes authenticates with Vault by passing the ServiceAccount token stored in the named Secret diff --git a/deploy/crds/external-secrets.io_secretstores.yaml b/deploy/crds/external-secrets.io_secretstores.yaml index 54a13cfc..0450d888 100644 --- a/deploy/crds/external-secrets.io_secretstores.yaml +++ b/deploy/crds/external-secrets.io_secretstores.yaml @@ -189,6 +189,41 @@ spec: - roleId - secretRef type: object + jwt: + description: Jwt authenticates with Vault by passing role + and JWT token using the JWT/OIDC authentication method + properties: + role: + description: Role is a JWT role to authenticate using + the JWT/OIDC Vault authentication method + type: string + tokenSecretRef: + description: SecretRef to a key in a Secret resource + containing JWT token to authenticate with Vault + using the LDAP authentication method + properties: + key: + description: The key of the entry in the Secret + resource's `data` field to be used. Some instances + of this field may be defaulted, in others it + may be required. + type: string + name: + description: The name of the Secret resource being + referred to. + type: string + namespace: + description: Namespace of the resource being referred + to. Ignored if referent is not cluster-scoped. + cluster-scoped defaults to the namespace of + the referent. + type: string + required: + - name + type: object + required: + - role + type: object kubernetes: description: Kubernetes authenticates with Vault by passing the ServiceAccount token stored in the named Secret diff --git a/pkg/provider/vault/vault.go b/pkg/provider/vault/vault.go index 32be0c1e..26df7f1d 100644 --- a/pkg/provider/vault/vault.go +++ b/pkg/provider/vault/vault.go @@ -257,6 +257,16 @@ func (v *client) setAuth(ctx context.Context, client Client) error { return nil } + ldapAuth := v.store.Auth.Ldap + if ldapAuth != nil { + token, err := v.requestTokenWithLdapAuth(ctx, client, ldapAuth) + if err != nil { + return err + } + client.SetToken(token) + return nil + } + return errors.New(errAuthFormat) } @@ -427,3 +437,82 @@ func (v *client) requestTokenWithKubernetesAuth(ctx context.Context, client Clie return token, nil } + +func (v *client) requestTokenWithLdapAuth(ctx context.Context, client Client, ldapAuth *esv1alpha1.VaultLdapAuth) (string, error) { + username := strings.TrimSpace(ldapAuth.Username) + + password, err := v.secretKeyRef(ctx, &ldapAuth.SecretRef) + if err != nil { + return "", err + } + + parameters := map[string]string{ + "password": password, + } + url := strings.Join([]string{"/v1", "auth", "ldap", "login", username}, "/") + request := client.NewRequest("POST", url) + + err = request.SetJSONBody(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) + } + + token, err := vaultResult.TokenID() + if err != nil { + return "", fmt.Errorf(errVaultToken, err) + } + + return token, nil +} + +func (v *client) requestTokenWithJwtAuth(ctx context.Context, client Client, jwtAuth *esv1alpha1.VaultJwtAuth) (string, error) { + role := strings.TrimSpace(jwtAuth.Role) + + jwt, err := v.secretKeyRef(ctx, &jwtAuth.SecretRef) + if err != nil { + return "", err + } + + parameters := map[string]string{ + "role": role, + "jwt": jwt, + } + url := strings.Join([]string{"/v1", "auth", "jwt", "login"}, "/") + request := client.NewRequest("POST", url) + + err = request.SetJSONBody(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) + } + + token, err := vaultResult.TokenID() + if err != nil { + return "", fmt.Errorf(errVaultToken, err) + } + + return token, nil +}