mirror of
https://github.com/external-secrets/external-secrets.git
synced 2024-12-14 11:57:59 +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:
parent
8e0a5b96c6
commit
8c14f8aff0
7 changed files with 185 additions and 26 deletions
|
@ -21,8 +21,15 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
// Ready indicates that the client is confgured correctly
|
||||
// and can be used.
|
||||
ValidationResultReady ValidationResult = iota
|
||||
|
||||
// Unknown indicates that the client can be used
|
||||
// but information is missing and it can not be validated.
|
||||
ValidationResultUnknown
|
||||
|
||||
// Error indicates that there is a misconfiguration.
|
||||
ValidationResultError
|
||||
)
|
||||
|
||||
|
|
52
docs/spec.md
52
docs/spec.md
|
@ -1552,14 +1552,16 @@ string
|
|||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>version</code></br>
|
||||
<code>metadataPolicy</code></br>
|
||||
<em>
|
||||
string
|
||||
<a href="#external-secrets.io/v1beta1.ExternalSecretMetadataPolicy">
|
||||
ExternalSecretMetadataPolicy
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<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>
|
||||
</tr>
|
||||
<tr>
|
||||
|
@ -1576,6 +1578,18 @@ string
|
|||
</tr>
|
||||
<tr>
|
||||
<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>
|
||||
<em>
|
||||
<a href="#external-secrets.io/v1beta1.ExternalSecretConversionStrategy">
|
||||
|
@ -1695,6 +1709,27 @@ ExternalSecretConversionStrategy
|
|||
</tr>
|
||||
</tbody>
|
||||
</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>"Fetch"</p></td>
|
||||
<td></td>
|
||||
</tr><tr><td><p>"None"</p></td>
|
||||
<td></td>
|
||||
</tr></tbody>
|
||||
</table>
|
||||
<h3 id="external-secrets.io/v1beta1.ExternalSecretSpec">ExternalSecretSpec
|
||||
</h3>
|
||||
<p>
|
||||
|
@ -4075,11 +4110,16 @@ github.com/external-secrets/external-secrets/apis/meta/v1.SecretKeySelector
|
|||
</tr>
|
||||
</thead>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
</table>
|
||||
<h3 id="external-secrets.io/v1beta1.VaultAppRole">VaultAppRole
|
||||
|
|
|
@ -48,6 +48,8 @@ const (
|
|||
jwtProviderSecretName = "jwt-provider-credentials"
|
||||
jwtK8sProviderName = "jwt-k8s-provider"
|
||||
kubernetesProviderName = "kubernetes-provider"
|
||||
referentSecretName = "referent-secret"
|
||||
referentKey = "referent-secret-key"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -99,6 +101,7 @@ func (s *vaultProvider) BeforeEach() {
|
|||
s.CreateJWTStore(v, ns)
|
||||
s.CreateJWTK8sStore(v, ns)
|
||||
s.CreateKubernetesAuthStore(v, ns)
|
||||
s.CreateReferentTokenStore(v, ns)
|
||||
}
|
||||
|
||||
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) {
|
||||
By("creating a vault secret")
|
||||
clientCert := v.ClientCert
|
||||
|
@ -179,6 +190,33 @@ func (s vaultProvider) CreateTokenStore(v *addon.Vault, ns string) {
|
|||
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) {
|
||||
By("creating a vault 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)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
}
|
||||
|
||||
func referentSecretStoreName(f *framework.Framework) string {
|
||||
return "referent-provider-" + f.Namespace.Name
|
||||
}
|
||||
|
|
|
@ -25,13 +25,14 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
withTokenAuth = "with token auth"
|
||||
withCertAuth = "with cert auth"
|
||||
withApprole = "with approle auth"
|
||||
withV1 = "with v1 provider"
|
||||
withJWT = "with jwt provider"
|
||||
withJWTK8s = "with jwt k8s provider"
|
||||
withK8s = "with kubernetes provider"
|
||||
withTokenAuth = "with token auth"
|
||||
withCertAuth = "with cert auth"
|
||||
withApprole = "with approle auth"
|
||||
withV1 = "with v1 provider"
|
||||
withJWT = "with jwt provider"
|
||||
withJWTK8s = "with jwt k8s provider"
|
||||
withK8s = "with kubernetes provider"
|
||||
withReferentAuth = "with referent provider"
|
||||
)
|
||||
|
||||
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.DataPropertyDockerconfigJSON, useKubernetesProvider),
|
||||
framework.Compose(withK8s, f, common.JSONDataWithoutTargetName, useKubernetesProvider),
|
||||
// use referent auth
|
||||
framework.Compose(withReferentAuth, f, common.JSONDataFromSync, useReferentAuth),
|
||||
// vault-specific test cases
|
||||
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),
|
||||
|
@ -124,6 +127,11 @@ func useKubernetesProvider(tc *framework.TestCase) {
|
|||
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"}}}`
|
||||
|
||||
// when no property is set it should return the json-encoded at path.
|
||||
|
|
|
@ -64,6 +64,7 @@ func reconcile(ctx context.Context, req ctrl.Request, ss esapi.GenericStore, cl
|
|||
log.V(1).Info("validating")
|
||||
err := validateStore(ctx, req.Namespace, ss, cl, recorder)
|
||||
if err != nil {
|
||||
log.Error(err, "unable to validate store")
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
|
|
|
@ -277,6 +277,11 @@ func (c *connector) newClient(ctx context.Context, store esv1beta1.GenericStore,
|
|||
vStore.logical = client.Logical()
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -300,25 +305,25 @@ func (c *connector) ValidateStore(store esv1beta1.GenericStore) error {
|
|||
return fmt.Errorf(errInvalidVaultProv)
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
if p.Auth.Jwt != 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)
|
||||
}
|
||||
} 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)
|
||||
}
|
||||
} else {
|
||||
|
@ -327,23 +332,23 @@ func (c *connector) ValidateStore(store esv1beta1.GenericStore) error {
|
|||
}
|
||||
if p.Auth.Kubernetes != 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)
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -581,6 +586,32 @@ func (v *client) Close(ctx context.Context) 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)
|
||||
if err != nil {
|
||||
return esv1beta1.ValidationResultError, fmt.Errorf(errInvalidCredentials, err)
|
||||
|
|
|
@ -18,6 +18,7 @@ import (
|
|||
|
||||
// nolint:gosec
|
||||
"crypto/md5"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
|
@ -110,16 +111,33 @@ func ErrorContains(out error, want string) bool {
|
|||
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
|
||||
// depending on the secret store type.
|
||||
// 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 {
|
||||
clusterScope := store.GetObjectKind().GroupVersionKind().Kind == esv1beta1.ClusterSecretStoreKind
|
||||
if clusterScope && ref.Namespace == nil {
|
||||
return fmt.Errorf("cluster scope requires namespace")
|
||||
return errRequireNamespace
|
||||
}
|
||||
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
|
||||
}
|
||||
|
@ -130,10 +148,22 @@ func ValidateSecretSelector(store esv1beta1.GenericStore, ref esmeta.SecretKeySe
|
|||
func ValidateServiceAccountSelector(store esv1beta1.GenericStore, ref esmeta.ServiceAccountSelector) error {
|
||||
clusterScope := store.GetObjectKind().GroupVersionKind().Kind == esv1beta1.ClusterSecretStoreKind
|
||||
if clusterScope && ref.Namespace == nil {
|
||||
return fmt.Errorf("cluster scope requires namespace")
|
||||
return errRequireNamespace
|
||||
}
|
||||
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
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue