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

fix: loosen validation to enable referent auth.

also adding tests for vault. this is the only provider that supports
that as of now.

Signed-off-by: Moritz Johner <beller.moritz@googlemail.com>
This commit is contained in:
Moritz Johner 2022-04-21 14:47:58 +02:00
parent 8e0a5b96c6
commit 8c14f8aff0
7 changed files with 185 additions and 26 deletions

View file

@ -21,8 +21,15 @@ import (
) )
const ( const (
// Ready indicates that the client is confgured correctly
// and can be used.
ValidationResultReady ValidationResult = iota ValidationResultReady ValidationResult = iota
// Unknown indicates that the client can be used
// but information is missing and it can not be validated.
ValidationResultUnknown ValidationResultUnknown
// Error indicates that there is a misconfiguration.
ValidationResultError ValidationResultError
) )

View file

@ -1552,14 +1552,16 @@ string
</tr> </tr>
<tr> <tr>
<td> <td>
<code>version</code></br> <code>metadataPolicy</code></br>
<em> <em>
string <a href="#external-secrets.io/v1beta1.ExternalSecretMetadataPolicy">
ExternalSecretMetadataPolicy
</a>
</em> </em>
</td> </td>
<td> <td>
<em>(Optional)</em> <em>(Optional)</em>
<p>Used to select a specific version of the Provider value, if supported</p> <p>Policy for fetching tags/labels from provider secrets, possible options are Fetch, None. Defaults to None</p>
</td> </td>
</tr> </tr>
<tr> <tr>
@ -1576,6 +1578,18 @@ string
</tr> </tr>
<tr> <tr>
<td> <td>
<code>version</code></br>
<em>
string
</em>
</td>
<td>
<em>(Optional)</em>
<p>Used to select a specific version of the Provider value, if supported</p>
</td>
</tr>
<tr>
<td>
<code>conversionStrategy</code></br> <code>conversionStrategy</code></br>
<em> <em>
<a href="#external-secrets.io/v1beta1.ExternalSecretConversionStrategy"> <a href="#external-secrets.io/v1beta1.ExternalSecretConversionStrategy">
@ -1695,6 +1709,27 @@ ExternalSecretConversionStrategy
</tr> </tr>
</tbody> </tbody>
</table> </table>
<h3 id="external-secrets.io/v1beta1.ExternalSecretMetadataPolicy">ExternalSecretMetadataPolicy
(<code>string</code> alias)</p></h3>
<p>
(<em>Appears on:</em>
<a href="#external-secrets.io/v1beta1.ExternalSecretDataRemoteRef">ExternalSecretDataRemoteRef</a>)
</p>
<p>
</p>
<table>
<thead>
<tr>
<th>Value</th>
<th>Description</th>
</tr>
</thead>
<tbody><tr><td><p>&#34;Fetch&#34;</p></td>
<td></td>
</tr><tr><td><p>&#34;None&#34;</p></td>
<td></td>
</tr></tbody>
</table>
<h3 id="external-secrets.io/v1beta1.ExternalSecretSpec">ExternalSecretSpec <h3 id="external-secrets.io/v1beta1.ExternalSecretSpec">ExternalSecretSpec
</h3> </h3>
<p> <p>
@ -4075,11 +4110,16 @@ github.com/external-secrets/external-secrets/apis/meta/v1.SecretKeySelector
</tr> </tr>
</thead> </thead>
<tbody><tr><td><p>2</p></td> <tbody><tr><td><p>2</p></td>
<td></td> <td><p>Error indicates that there is a misconfiguration.</p>
</td>
</tr><tr><td><p>0</p></td> </tr><tr><td><p>0</p></td>
<td></td> <td><p>Ready indicates that the client is confgured correctly
and can be used.</p>
</td>
</tr><tr><td><p>1</p></td> </tr><tr><td><p>1</p></td>
<td></td> <td><p>Unknown indicates that the client can be used
but information is missing and it can not be validated.</p>
</td>
</tr></tbody> </tr></tbody>
</table> </table>
<h3 id="external-secrets.io/v1beta1.VaultAppRole">VaultAppRole <h3 id="external-secrets.io/v1beta1.VaultAppRole">VaultAppRole

View file

@ -48,6 +48,8 @@ const (
jwtProviderSecretName = "jwt-provider-credentials" jwtProviderSecretName = "jwt-provider-credentials"
jwtK8sProviderName = "jwt-k8s-provider" jwtK8sProviderName = "jwt-k8s-provider"
kubernetesProviderName = "kubernetes-provider" kubernetesProviderName = "kubernetes-provider"
referentSecretName = "referent-secret"
referentKey = "referent-secret-key"
) )
var ( var (
@ -99,6 +101,7 @@ func (s *vaultProvider) BeforeEach() {
s.CreateJWTStore(v, ns) s.CreateJWTStore(v, ns)
s.CreateJWTK8sStore(v, ns) s.CreateJWTK8sStore(v, ns)
s.CreateKubernetesAuthStore(v, ns) s.CreateKubernetesAuthStore(v, ns)
s.CreateReferentTokenStore(v, ns)
} }
func makeStore(name, ns string, v *addon.Vault) *esv1beta1.SecretStore { func makeStore(name, ns string, v *addon.Vault) *esv1beta1.SecretStore {
@ -120,6 +123,14 @@ func makeStore(name, ns string, v *addon.Vault) *esv1beta1.SecretStore {
} }
} }
func makeClusterStore(name, ns string, v *addon.Vault) *esv1beta1.ClusterSecretStore {
store := makeStore(name, ns, v)
return &esv1beta1.ClusterSecretStore{
ObjectMeta: store.ObjectMeta,
Spec: store.Spec,
}
}
func (s *vaultProvider) CreateCertStore(v *addon.Vault, ns string) { func (s *vaultProvider) CreateCertStore(v *addon.Vault, ns string) {
By("creating a vault secret") By("creating a vault secret")
clientCert := v.ClientCert clientCert := v.ClientCert
@ -179,6 +190,33 @@ func (s vaultProvider) CreateTokenStore(v *addon.Vault, ns string) {
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
} }
// CreateReferentTokenStore creates a secret in the ExternalSecrets
// namespace and creates a ClusterSecretStore with an empty namespace
// that can be used to test the referent namespace feature.
func (s vaultProvider) CreateReferentTokenStore(v *addon.Vault, ns string) {
referentSecret := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: referentSecretName,
Namespace: s.framework.Namespace.Name,
},
Data: map[string][]byte{
referentKey: []byte(v.RootToken),
},
}
_, err := s.framework.KubeClientSet.CoreV1().Secrets(s.framework.Namespace.Name).Create(context.Background(), referentSecret, metav1.CreateOptions{})
Expect(err).ToNot(HaveOccurred())
secretStore := makeClusterStore(referentSecretStoreName(s.framework), ns, v)
secretStore.Spec.Provider.Vault.Auth = esv1beta1.VaultAuth{
TokenSecretRef: &esmeta.SecretKeySelector{
Name: referentSecretName,
Key: referentKey,
},
}
err = s.framework.CRClient.Create(context.Background(), secretStore)
Expect(err).ToNot(HaveOccurred())
}
func (s vaultProvider) CreateAppRoleStore(v *addon.Vault, ns string) { func (s vaultProvider) CreateAppRoleStore(v *addon.Vault, ns string) {
By("creating a vault secret") By("creating a vault secret")
vaultCreds := &v1.Secret{ vaultCreds := &v1.Secret{
@ -296,3 +334,7 @@ func (s vaultProvider) CreateKubernetesAuthStore(v *addon.Vault, ns string) {
err := s.framework.CRClient.Create(context.Background(), secretStore) err := s.framework.CRClient.Create(context.Background(), secretStore)
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
} }
func referentSecretStoreName(f *framework.Framework) string {
return "referent-provider-" + f.Namespace.Name
}

View file

@ -25,13 +25,14 @@ import (
) )
const ( const (
withTokenAuth = "with token auth" withTokenAuth = "with token auth"
withCertAuth = "with cert auth" withCertAuth = "with cert auth"
withApprole = "with approle auth" withApprole = "with approle auth"
withV1 = "with v1 provider" withV1 = "with v1 provider"
withJWT = "with jwt provider" withJWT = "with jwt provider"
withJWTK8s = "with jwt k8s provider" withJWTK8s = "with jwt k8s provider"
withK8s = "with kubernetes provider" withK8s = "with kubernetes provider"
withReferentAuth = "with referent provider"
) )
var _ = Describe("[vault]", Label("vault"), func() { var _ = Describe("[vault]", Label("vault"), func() {
@ -88,6 +89,8 @@ var _ = Describe("[vault]", Label("vault"), func() {
framework.Compose(withK8s, f, common.JSONDataWithTemplate, useKubernetesProvider), framework.Compose(withK8s, f, common.JSONDataWithTemplate, useKubernetesProvider),
framework.Compose(withK8s, f, common.DataPropertyDockerconfigJSON, useKubernetesProvider), framework.Compose(withK8s, f, common.DataPropertyDockerconfigJSON, useKubernetesProvider),
framework.Compose(withK8s, f, common.JSONDataWithoutTargetName, useKubernetesProvider), framework.Compose(withK8s, f, common.JSONDataWithoutTargetName, useKubernetesProvider),
// use referent auth
framework.Compose(withReferentAuth, f, common.JSONDataFromSync, useReferentAuth),
// vault-specific test cases // vault-specific test cases
Entry("secret value via data without property should return json-encoded string", Label("json"), testJSONWithoutProperty), Entry("secret value via data without property should return json-encoded string", Label("json"), testJSONWithoutProperty),
Entry("secret value via data with property should return json-encoded string", Label("json"), testJSONWithProperty), Entry("secret value via data with property should return json-encoded string", Label("json"), testJSONWithProperty),
@ -124,6 +127,11 @@ func useKubernetesProvider(tc *framework.TestCase) {
tc.ExternalSecret.Spec.SecretStoreRef.Name = kubernetesProviderName tc.ExternalSecret.Spec.SecretStoreRef.Name = kubernetesProviderName
} }
func useReferentAuth(tc *framework.TestCase) {
tc.ExternalSecret.Spec.SecretStoreRef.Name = referentSecretStoreName(tc.Framework)
tc.ExternalSecret.Spec.SecretStoreRef.Kind = esapi.ClusterSecretStoreKind
}
const jsonVal = `{"foo":{"nested":{"bar":"mysecret","baz":"bang"}}}` const jsonVal = `{"foo":{"nested":{"bar":"mysecret","baz":"bang"}}}`
// when no property is set it should return the json-encoded at path. // when no property is set it should return the json-encoded at path.

View file

@ -64,6 +64,7 @@ func reconcile(ctx context.Context, req ctrl.Request, ss esapi.GenericStore, cl
log.V(1).Info("validating") log.V(1).Info("validating")
err := validateStore(ctx, req.Namespace, ss, cl, recorder) err := validateStore(ctx, req.Namespace, ss, cl, recorder)
if err != nil { if err != nil {
log.Error(err, "unable to validate store")
return ctrl.Result{}, err return ctrl.Result{}, err
} }

View file

@ -277,6 +277,11 @@ func (c *connector) newClient(ctx context.Context, store esv1beta1.GenericStore,
vStore.logical = client.Logical() vStore.logical = client.Logical()
vStore.token = client.AuthToken() vStore.token = client.AuthToken()
// allow SecretStore controller validation to pass
// when using referent namespace.
if vStore.storeKind == esv1beta1.ClusterSecretStoreKind && vStore.namespace == "" {
return vStore, nil
}
if err := vStore.setAuth(ctx, cfg); err != nil { if err := vStore.setAuth(ctx, cfg); err != nil {
return nil, err return nil, err
} }
@ -300,25 +305,25 @@ func (c *connector) ValidateStore(store esv1beta1.GenericStore) error {
return fmt.Errorf(errInvalidVaultProv) return fmt.Errorf(errInvalidVaultProv)
} }
if p.Auth.AppRole != nil { if p.Auth.AppRole != nil {
if err := utils.ValidateSecretSelector(store, p.Auth.AppRole.SecretRef); err != nil { if err := utils.ValidateReferentSecretSelector(store, p.Auth.AppRole.SecretRef); err != nil {
return fmt.Errorf(errInvalidAppRoleSec, err) return fmt.Errorf(errInvalidAppRoleSec, err)
} }
} }
if p.Auth.Cert != nil { if p.Auth.Cert != nil {
if err := utils.ValidateSecretSelector(store, p.Auth.Cert.ClientCert); err != nil { if err := utils.ValidateReferentSecretSelector(store, p.Auth.Cert.ClientCert); err != nil {
return fmt.Errorf(errInvalidClientCert, err) return fmt.Errorf(errInvalidClientCert, err)
} }
if err := utils.ValidateSecretSelector(store, p.Auth.Cert.SecretRef); err != nil { if err := utils.ValidateReferentSecretSelector(store, p.Auth.Cert.SecretRef); err != nil {
return fmt.Errorf(errInvalidCertSec, err) return fmt.Errorf(errInvalidCertSec, err)
} }
} }
if p.Auth.Jwt != nil { if p.Auth.Jwt != nil {
if p.Auth.Jwt.SecretRef != nil { if p.Auth.Jwt.SecretRef != nil {
if err := utils.ValidateSecretSelector(store, *p.Auth.Jwt.SecretRef); err != nil { if err := utils.ValidateReferentSecretSelector(store, *p.Auth.Jwt.SecretRef); err != nil {
return fmt.Errorf(errInvalidJwtSec, err) return fmt.Errorf(errInvalidJwtSec, err)
} }
} else if p.Auth.Jwt.KubernetesServiceAccountToken != nil { } else if p.Auth.Jwt.KubernetesServiceAccountToken != nil {
if err := utils.ValidateServiceAccountSelector(store, p.Auth.Jwt.KubernetesServiceAccountToken.ServiceAccountRef); err != nil { if err := utils.ValidateReferentServiceAccountSelector(store, p.Auth.Jwt.KubernetesServiceAccountToken.ServiceAccountRef); err != nil {
return fmt.Errorf(errInvalidJwtK8sSA, err) return fmt.Errorf(errInvalidJwtK8sSA, err)
} }
} else { } else {
@ -327,23 +332,23 @@ func (c *connector) ValidateStore(store esv1beta1.GenericStore) error {
} }
if p.Auth.Kubernetes != nil { if p.Auth.Kubernetes != nil {
if p.Auth.Kubernetes.ServiceAccountRef != nil { if p.Auth.Kubernetes.ServiceAccountRef != nil {
if err := utils.ValidateServiceAccountSelector(store, *p.Auth.Kubernetes.ServiceAccountRef); err != nil { if err := utils.ValidateReferentServiceAccountSelector(store, *p.Auth.Kubernetes.ServiceAccountRef); err != nil {
return fmt.Errorf(errInvalidKubeSA, err) return fmt.Errorf(errInvalidKubeSA, err)
} }
} }
if p.Auth.Kubernetes.SecretRef != nil { if p.Auth.Kubernetes.SecretRef != nil {
if err := utils.ValidateSecretSelector(store, *p.Auth.Kubernetes.SecretRef); err != nil { if err := utils.ValidateReferentSecretSelector(store, *p.Auth.Kubernetes.SecretRef); err != nil {
return fmt.Errorf(errInvalidKubeSec, err) return fmt.Errorf(errInvalidKubeSec, err)
} }
} }
} }
if p.Auth.Ldap != nil { if p.Auth.Ldap != nil {
if err := utils.ValidateSecretSelector(store, p.Auth.Ldap.SecretRef); err != nil { if err := utils.ValidateReferentSecretSelector(store, p.Auth.Ldap.SecretRef); err != nil {
return fmt.Errorf(errInvalidLdapSec, err) return fmt.Errorf(errInvalidLdapSec, err)
} }
} }
if p.Auth.TokenSecretRef != nil { if p.Auth.TokenSecretRef != nil {
if err := utils.ValidateSecretSelector(store, *p.Auth.TokenSecretRef); err != nil { if err := utils.ValidateReferentSecretSelector(store, *p.Auth.TokenSecretRef); err != nil {
return fmt.Errorf(errInvalidTokenRef, err) return fmt.Errorf(errInvalidTokenRef, err)
} }
} }
@ -581,6 +586,32 @@ func (v *client) Close(ctx context.Context) error {
} }
func (v *client) Validate() (esv1beta1.ValidationResult, error) { func (v *client) Validate() (esv1beta1.ValidationResult, error) {
// when using referent namespace we can not validate the token
// because the namespace is not known yet when Validate() is called
// from the SecretStore controller.
if v.storeKind == esv1beta1.ClusterSecretStoreKind {
if v.store.Auth.TokenSecretRef != nil && v.store.Auth.TokenSecretRef.Namespace == nil {
return esv1beta1.ValidationResultUnknown, nil
}
if v.store.Auth.AppRole != nil && v.store.Auth.AppRole.SecretRef.Namespace == nil {
return esv1beta1.ValidationResultUnknown, nil
}
if v.store.Auth.Kubernetes != nil && v.store.Auth.Kubernetes.SecretRef != nil && v.store.Auth.Kubernetes.SecretRef.Namespace == nil {
return esv1beta1.ValidationResultUnknown, nil
}
if v.store.Auth.Kubernetes != nil && v.store.Auth.Kubernetes.ServiceAccountRef != nil && v.store.Auth.Kubernetes.ServiceAccountRef.Namespace == nil {
return esv1beta1.ValidationResultUnknown, nil
}
if v.store.Auth.Ldap != nil && v.store.Auth.Ldap.SecretRef.Namespace == nil {
return esv1beta1.ValidationResultUnknown, nil
}
if v.store.Auth.Jwt != nil && v.store.Auth.Jwt.SecretRef.Namespace == nil {
return esv1beta1.ValidationResultUnknown, nil
}
if v.store.Auth.Cert != nil && v.store.Auth.Cert.SecretRef.Namespace == nil {
return esv1beta1.ValidationResultUnknown, nil
}
}
_, err := checkToken(context.Background(), v) _, err := checkToken(context.Background(), v)
if err != nil { if err != nil {
return esv1beta1.ValidationResultError, fmt.Errorf(errInvalidCredentials, err) return esv1beta1.ValidationResultError, fmt.Errorf(errInvalidCredentials, err)

View file

@ -18,6 +18,7 @@ import (
// nolint:gosec // nolint:gosec
"crypto/md5" "crypto/md5"
"errors"
"fmt" "fmt"
"net" "net"
"net/url" "net/url"
@ -110,16 +111,33 @@ func ErrorContains(out error, want string) bool {
return strings.Contains(out.Error(), want) return strings.Contains(out.Error(), want)
} }
var (
errNamespaceNotAllowed = errors.New("namespace not allowed with namespaced SecretStore")
errRequireNamespace = errors.New("cluster scope requires namespace")
)
// ValidateSecretSelector just checks if the namespace field is present/absent // ValidateSecretSelector just checks if the namespace field is present/absent
// depending on the secret store type. // depending on the secret store type.
// We MUST NOT check the name or key property here. It MAY be defaulted by the provider. // We MUST NOT check the name or key property here. It MAY be defaulted by the provider.
func ValidateSecretSelector(store esv1beta1.GenericStore, ref esmeta.SecretKeySelector) error { func ValidateSecretSelector(store esv1beta1.GenericStore, ref esmeta.SecretKeySelector) error {
clusterScope := store.GetObjectKind().GroupVersionKind().Kind == esv1beta1.ClusterSecretStoreKind clusterScope := store.GetObjectKind().GroupVersionKind().Kind == esv1beta1.ClusterSecretStoreKind
if clusterScope && ref.Namespace == nil { if clusterScope && ref.Namespace == nil {
return fmt.Errorf("cluster scope requires namespace") return errRequireNamespace
} }
if !clusterScope && ref.Namespace != nil { if !clusterScope && ref.Namespace != nil {
return fmt.Errorf("namespace not allowed with namespaced SecretStore") return errNamespaceNotAllowed
}
return nil
}
// ValidateReferentSecretSelector allows
// cluster scoped store without namespace
// this should replace above ValidateServiceAccountSelector once all providers
// support referent auth.
func ValidateReferentSecretSelector(store esv1beta1.GenericStore, ref esmeta.SecretKeySelector) error {
clusterScope := store.GetObjectKind().GroupVersionKind().Kind == esv1beta1.ClusterSecretStoreKind
if !clusterScope && ref.Namespace != nil {
return errNamespaceNotAllowed
} }
return nil return nil
} }
@ -130,10 +148,22 @@ func ValidateSecretSelector(store esv1beta1.GenericStore, ref esmeta.SecretKeySe
func ValidateServiceAccountSelector(store esv1beta1.GenericStore, ref esmeta.ServiceAccountSelector) error { func ValidateServiceAccountSelector(store esv1beta1.GenericStore, ref esmeta.ServiceAccountSelector) error {
clusterScope := store.GetObjectKind().GroupVersionKind().Kind == esv1beta1.ClusterSecretStoreKind clusterScope := store.GetObjectKind().GroupVersionKind().Kind == esv1beta1.ClusterSecretStoreKind
if clusterScope && ref.Namespace == nil { if clusterScope && ref.Namespace == nil {
return fmt.Errorf("cluster scope requires namespace") return errRequireNamespace
} }
if !clusterScope && ref.Namespace != nil { if !clusterScope && ref.Namespace != nil {
return fmt.Errorf("namespace not allowed with namespaced SecretStore") return errNamespaceNotAllowed
}
return nil
}
// ValidateReferentServiceAccountSelector allows
// cluster scoped store without namespace
// this should replace above ValidateServiceAccountSelector once all providers
// support referent auth.
func ValidateReferentServiceAccountSelector(store esv1beta1.GenericStore, ref esmeta.ServiceAccountSelector) error {
clusterScope := store.GetObjectKind().GroupVersionKind().Kind == esv1beta1.ClusterSecretStoreKind
if !clusterScope && ref.Namespace != nil {
return errNamespaceNotAllowed
} }
return nil return nil
} }