mirror of
https://github.com/external-secrets/external-secrets.git
synced 2024-12-14 11:57:59 +00:00
Add service account selector to vault provider to look up the sa token
This commit is contained in:
parent
3387fae91a
commit
f2d77e0324
9 changed files with 160 additions and 7 deletions
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"`
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -24,6 +24,14 @@ rules:
|
|||
verbs:
|
||||
- "update"
|
||||
- "patch"
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- "serviceaccounts"
|
||||
verbs:
|
||||
- "get"
|
||||
- "list"
|
||||
- "watch"
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
}),
|
||||
|
|
Loading…
Reference in a new issue