1
0
Fork 0
mirror of https://github.com/external-secrets/external-secrets.git synced 2024-12-15 17:51:01 +00:00

Add service account selector to vault provider to look up the sa token

This commit is contained in:
Cameron McAvoy 2021-04-08 12:11:56 -05:00
parent 3387fae91a
commit f2d77e0324
9 changed files with 160 additions and 7 deletions

View file

@ -105,6 +105,13 @@ type VaultKubernetesAuth struct {
// +kubebuilder:default=kubernetes
Path string `json:"mountPath"`
// Optional service account field containing the name of a kubernetes ServiceAccount.
// If the service account is specified, the service account secret token JWT will be used
// for authenticating with Vault. If the service account selector is not supplied,
// the secretRef will be used instead.
// +optional
ServiceAccountRef *esmeta.ServiceAccountSelector `json:"serviceAccountRef,omitempty"`
// Optional secret field containing a Kubernetes ServiceAccount JWT used
// for authenticating with Vault. If a name is specified without a key,
// `token` is the default. If one is not specified, the one bound to

View file

@ -562,6 +562,11 @@ func (in *VaultAuth) DeepCopy() *VaultAuth {
// 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
if in.ServiceAccountRef != nil {
in, out := &in.ServiceAccountRef, &out.ServiceAccountRef
*out = new(metav1.ServiceAccountSelector)
(*in).DeepCopyInto(*out)
}
if in.SecretRef != nil {
in, out := &in.SecretRef, &out.SecretRef
*out = new(metav1.SecretKeySelector)

View file

@ -28,3 +28,13 @@ type SecretKeySelector struct {
// +optional
Key string `json:"key,omitempty"`
}
// A reference to a ServiceAccount resource.
type ServiceAccountSelector struct {
// The name of the ServiceAccount resource being referred to.
Name string `json:"name"`
// Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults
// to the namespace of the referent.
// +optional
Namespace *string `json:"namespace,omitempty"`
}

View file

@ -39,3 +39,23 @@ func (in *SecretKeySelector) DeepCopy() *SecretKeySelector {
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ServiceAccountSelector) DeepCopyInto(out *ServiceAccountSelector) {
*out = *in
if in.Namespace != nil {
in, out := &in.Namespace, &out.Namespace
*out = new(string)
**out = **in
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceAccountSelector.
func (in *ServiceAccountSelector) DeepCopy() *ServiceAccountSelector {
if in == nil {
return nil
}
out := new(ServiceAccountSelector)
in.DeepCopyInto(out)
return out
}

View file

@ -24,6 +24,14 @@ rules:
verbs:
- "update"
- "patch"
- apiGroups:
- ""
resources:
- "serviceaccounts"
verbs:
- "get"
- "list"
- "watch"
- apiGroups:
- ""
resources:

View file

@ -230,6 +230,27 @@ spec:
required:
- name
type: object
serviceAccountRef:
description: Optional service account field containing
the name of a kubernetes ServiceAccount. If the
service account is specified, the service account
secret token JWT will be used for authenticating
with Vault. If the service account selector is not
supplied, the secretRef will be used instead.
properties:
name:
description: The name of the ServiceAccount 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:
- mountPath
- role

View file

@ -230,6 +230,27 @@ spec:
required:
- name
type: object
serviceAccountRef:
description: Optional service account field containing
the name of a kubernetes ServiceAccount. If the
service account is specified, the service account
secret token JWT will be used for authenticating
with Vault. If the service account selector is not
supplied, the secretRef will be used instead.
properties:
name:
description: The name of the ServiceAccount 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:
- mountPath
- role

View file

@ -57,6 +57,9 @@ const (
errVaultResponse = "cannot parse Vault response: %w"
errServiceAccount = "cannot read Kubernetes service account token from file system: %w"
errGetKubeSA = "cannot get Kubernetes service account %q: %w"
errGetKubeSASecrets = "cannot find secrets bound to service account: %q"
errGetKubeSecret = "cannot get Kubernetes secret %q: %w"
errSecretKeyFmt = "cannot find secret data for key: %q"
)
@ -257,6 +260,32 @@ func (v *client) setAuth(ctx context.Context, client Client) error {
return errors.New(errAuthFormat)
}
func (v *client) secretKeyRefForServiceAccount(ctx context.Context, serviceAccountRef *esmeta.ServiceAccountSelector) (string, error) {
serviceAccount := &corev1.ServiceAccount{}
ref := types.NamespacedName{
Namespace: v.namespace,
Name: serviceAccountRef.Name,
}
if (v.storeKind == esv1alpha1.ClusterSecretStoreKind) &&
(serviceAccountRef.Namespace != nil) {
ref.Namespace = *serviceAccountRef.Namespace
}
err := v.kube.Get(ctx, ref, serviceAccount)
if err != nil {
return "", fmt.Errorf(errGetKubeSA, ref.Name, err)
}
if len(serviceAccount.Secrets) == 0 {
return "", fmt.Errorf(errGetKubeSASecrets, ref.Name)
}
tokenRef := serviceAccount.Secrets[0]
return v.secretKeyRef(ctx, &esmeta.SecretKeySelector{
Name: tokenRef.Name,
Namespace: &ref.Namespace,
Key: "token",
})
}
func (v *client) secretKeyRef(ctx context.Context, secretRef *esmeta.SecretKeySelector) (string, error) {
secret := &corev1.Secret{}
ref := types.NamespacedName{
@ -339,7 +368,13 @@ func kubeParameters(role, jwt string) map[string]string {
func (v *client) requestTokenWithKubernetesAuth(ctx context.Context, client Client, kubernetesAuth *esv1alpha1.VaultKubernetesAuth) (string, error) {
jwtString := ""
if kubernetesAuth.SecretRef != nil {
if kubernetesAuth.ServiceAccountRef != nil {
jwt, err := v.secretKeyRefForServiceAccount(ctx, kubernetesAuth.ServiceAccountRef)
if err != nil {
return "", err
}
jwtString = jwt
} else if kubernetesAuth.SecretRef != nil {
tokenRef := kubernetesAuth.SecretRef
if tokenRef.Key == "" {
tokenRef = kubernetesAuth.SecretRef.DeepCopy()

View file

@ -52,9 +52,8 @@ func makeValidSecretStore() *esv1alpha1.SecretStore {
Kubernetes: &esv1alpha1.VaultKubernetesAuth{
Path: "kubernetes",
Role: "kubernetes-auth-role",
SecretRef: &esmeta.SecretKeySelector{
Name: "vault-secret",
Key: "key",
ServiceAccountRef: &esmeta.ServiceAccountSelector{
Name: "example-sa",
},
},
},
@ -144,7 +143,7 @@ func TestNewVault(t *testing.T) {
err: errors.New(errAuthFormat),
},
},
"GetKubeSecretError": {
"GetKubeServiceAccountError": {
reason: "Should return error if fetching kubernetes secret fails.",
args: args{
store: makeSecretStore(),
@ -153,7 +152,25 @@ func TestNewVault(t *testing.T) {
},
},
want: want{
err: fmt.Errorf(errGetKubeSecret, makeSecretStore().Spec.Provider.Vault.Auth.Kubernetes.SecretRef.Name, errBoom),
err: fmt.Errorf(errGetKubeSA, "example-sa", errBoom),
},
},
"GetKubeSecretError": {
reason: "Should return error if fetching kubernetes secret fails.",
args: args{
store: makeSecretStore(func(s *esv1alpha1.SecretStore) {
s.Spec.Provider.Vault.Auth.Kubernetes.ServiceAccountRef = nil
s.Spec.Provider.Vault.Auth.Kubernetes.SecretRef = &esmeta.SecretKeySelector{
Name: "vault-secret",
Key: "key",
}
}),
kube: &test.MockClient{
MockGet: test.NewMockGetFn(errBoom),
},
},
want: want{
err: fmt.Errorf(errGetKubeSecret, "vault-secret", errBoom),
},
},
"SuccessfulVaultStore": {
@ -162,10 +179,19 @@ func TestNewVault(t *testing.T) {
store: makeSecretStore(),
kube: &test.MockClient{
MockGet: test.NewMockGetFn(nil, func(obj kclient.Object) error {
if o, ok := obj.(*corev1.ServiceAccount); ok {
o.Secrets = []corev1.ObjectReference{
{
Name: "example-secret-token",
},
}
return nil
}
if o, ok := obj.(*corev1.Secret); ok {
o.Data = map[string][]byte{
"key": secretData,
"token": secretData,
}
return nil
}
return nil
}),