Browse Source

Merge pull request #414 from ADustyOldMuffin/vault-ca-provider

Add the ability to specify cert for Vault from K8s Secrets
paul-the-alien[bot] 4 years ago
parent
commit
d9f30a3350

+ 29 - 0
apis/externalsecrets/v1alpha1/secretstore_vault_types.go

@@ -25,6 +25,31 @@ const (
 	VaultKVStoreV2 VaultKVStoreVersion = "v2"
 )
 
+type CAProviderType string
+
+const (
+	CAProviderTypeSecret    CAProviderType = "Secret"
+	CAProviderTypeConfigMap CAProviderType = "ConfigMap"
+)
+
+// Defines a location to fetch the cert for the vault provider from.
+type CAProvider struct {
+	// The type of provider to use such as "Secret", or "ConfigMap".
+	// +kubebuilder:validation:Enum="Secret";"ConfigMap"
+	Type CAProviderType `json:"type"`
+
+	// The name of the object located at the provider type.
+	Name string `json:"name"`
+
+	// The key the value inside of the provider type to use, only used with "Secret" type
+	// +kubebuilder:validation:Optional
+	Key string `json:"key,omitempty"`
+
+	// The namespace the Provider type is in.
+	// +kubebuilder:default:="Default"
+	Namespace string `json:"namespace"`
+}
+
 // Configures an store to sync secrets using a HashiCorp Vault
 // KV backend.
 type VaultProvider struct {
@@ -59,6 +84,10 @@ type VaultProvider struct {
 	// are used to validate the TLS connection.
 	// +optional
 	CABundle []byte `json:"caBundle,omitempty"`
+
+	// The provider for the CA bundle to use to validate Vault server certificate.
+	// +optional
+	CAProvider *CAProvider `json:"caProvider,omitempty"`
 }
 
 // VaultAuth is the configuration used to authenticate with a Vault server.

+ 20 - 0
apis/externalsecrets/v1alpha1/zz_generated.deepcopy.go

@@ -211,6 +211,21 @@ func (in *AzureKVProvider) DeepCopy() *AzureKVProvider {
 }
 
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *CAProvider) DeepCopyInto(out *CAProvider) {
+	*out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CAProvider.
+func (in *CAProvider) DeepCopy() *CAProvider {
+	if in == nil {
+		return nil
+	}
+	out := new(CAProvider)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *ClusterSecretStore) DeepCopyInto(out *ClusterSecretStore) {
 	*out = *in
 	out.TypeMeta = in.TypeMeta
@@ -1108,6 +1123,11 @@ func (in *VaultProvider) DeepCopyInto(out *VaultProvider) {
 		*out = make([]byte, len(*in))
 		copy(*out, *in)
 	}
+	if in.CAProvider != nil {
+		in, out := &in.CAProvider, &out.CAProvider
+		*out = new(CAProvider)
+		**out = **in
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VaultProvider.

+ 28 - 0
deploy/crds/external-secrets.io_clustersecretstores.yaml

@@ -732,6 +732,34 @@ spec:
                           are used to validate the TLS connection.
                         format: byte
                         type: string
+                      caProvider:
+                        description: The provider for the CA bundle to use to validate
+                          Vault server certificate.
+                        properties:
+                          key:
+                            description: The key the value inside of the provider
+                              type to use, only used with "Secret" type
+                            type: string
+                          name:
+                            description: The name of the object located at the provider
+                              type.
+                            type: string
+                          namespace:
+                            default: Default
+                            description: The namespace the Provider type is in.
+                            type: string
+                          type:
+                            description: The type of provider to use such as "Secret",
+                              or "ConfigMap".
+                            enum:
+                            - Secret
+                            - ConfigMap
+                            type: string
+                        required:
+                        - name
+                        - namespace
+                        - type
+                        type: object
                       namespace:
                         description: 'Name of the vault namespace. Namespaces is a
                           set of features within Vault Enterprise that allows Vault

+ 28 - 0
deploy/crds/external-secrets.io_secretstores.yaml

@@ -732,6 +732,34 @@ spec:
                           are used to validate the TLS connection.
                         format: byte
                         type: string
+                      caProvider:
+                        description: The provider for the CA bundle to use to validate
+                          Vault server certificate.
+                        properties:
+                          key:
+                            description: The key the value inside of the provider
+                              type to use, only used with "Secret" type
+                            type: string
+                          name:
+                            description: The name of the object located at the provider
+                              type.
+                            type: string
+                          namespace:
+                            default: Default
+                            description: The namespace the Provider type is in.
+                            type: string
+                          type:
+                            description: The type of provider to use such as "Secret",
+                              or "ConfigMap".
+                            enum:
+                            - Secret
+                            - ConfigMap
+                            type: string
+                        required:
+                        - name
+                        - namespace
+                        - type
+                        type: object
                       namespace:
                         description: 'Name of the vault namespace. Namespaces is a
                           set of features within Vault Enterprise that allows Vault

+ 11 - 0
docs/snippets/full-secret-store.yaml

@@ -43,7 +43,18 @@ spec:
       version: "v2"
       # vault enterprise namespace: https://www.vaultproject.io/docs/enterprise/namespaces
       namespace: "a-team"
+      # base64 encoded string of certificate
       caBundle: "..."
+      # Instead of caBundle you can also specify a caProvider
+      # this will retrieve the cert from a Secret or ConfigMap
+      caProvider:
+        # Can be Secret or ConfigMap
+        type: "Secret"
+        # This is optional, if not specified will be 'Default'
+        namespace: "my-cert-secret-namespace"
+        name: "my-cert-secret"
+        key: "cert-key"
+
       auth:
         # static token: https://www.vaultproject.io/docs/auth/token
         tokenSecretRef:

+ 69 - 4
pkg/provider/vault/vault.go

@@ -66,10 +66,13 @@ const (
 
 	errGetKubeSecret = "cannot get Kubernetes secret %q: %w"
 	errSecretKeyFmt  = "cannot find secret data for key: %q"
+	errConfigMapFmt  = "cannot find config map data for key: %q"
 
 	errClientTLSAuth = "error from Client TLS Auth: %q"
 
 	errVaultRevokeToken = "error while revoking token: %w"
+
+	errUnknownCAProvider = "unknown caProvider type given"
 )
 
 type Client interface {
@@ -233,14 +236,40 @@ func (v *client) newConfig() (*vault.Config, error) {
 	cfg := vault.DefaultConfig()
 	cfg.Address = v.store.Server
 
-	if len(v.store.CABundle) == 0 {
+	if len(v.store.CABundle) == 0 && v.store.CAProvider == nil {
 		return cfg, nil
 	}
 
 	caCertPool := x509.NewCertPool()
-	ok := caCertPool.AppendCertsFromPEM(v.store.CABundle)
-	if !ok {
-		return nil, errors.New(errVaultCert)
+
+	if len(v.store.CABundle) > 0 {
+		ok := caCertPool.AppendCertsFromPEM(v.store.CABundle)
+		if !ok {
+			return nil, errors.New(errVaultCert)
+		}
+	}
+
+	if v.store.CAProvider != nil {
+		var cert []byte
+		var err error
+
+		switch v.store.CAProvider.Type {
+		case esv1alpha1.CAProviderTypeSecret:
+			cert, err = getCertFromSecret(v)
+		case esv1alpha1.CAProviderTypeConfigMap:
+			cert, err = getCertFromConfigMap(v)
+		default:
+			return nil, errors.New(errUnknownCAProvider)
+		}
+
+		if err != nil {
+			return nil, err
+		}
+
+		ok := caCertPool.AppendCertsFromPEM(cert)
+		if !ok {
+			return nil, errors.New(errVaultCert)
+		}
 	}
 
 	if transport, ok := cfg.HttpClient.Transport.(*http.Transport); ok {
@@ -250,6 +279,42 @@ func (v *client) newConfig() (*vault.Config, error) {
 	return cfg, nil
 }
 
+func getCertFromSecret(v *client) ([]byte, error) {
+	secretRef := esmeta.SecretKeySelector{
+		Name:      v.store.CAProvider.Name,
+		Namespace: &v.store.CAProvider.Namespace,
+		Key:       v.store.CAProvider.Key,
+	}
+	ctx := context.Background()
+	res, err := v.secretKeyRef(ctx, &secretRef)
+	if err != nil {
+		return nil, fmt.Errorf(errVaultCert, err)
+	}
+
+	return []byte(res), nil
+}
+
+func getCertFromConfigMap(v *client) ([]byte, error) {
+	objKey := types.NamespacedName{
+		Namespace: v.store.CAProvider.Namespace,
+		Name:      v.store.CAProvider.Name,
+	}
+
+	configMapRef := &corev1.ConfigMap{}
+	ctx := context.Background()
+	err := v.kube.Get(ctx, objKey, configMapRef)
+	if err != nil {
+		return nil, fmt.Errorf(errVaultCert, err)
+	}
+
+	val, ok := configMapRef.Data[v.store.CAProvider.Key]
+	if !ok {
+		return nil, fmt.Errorf(errConfigMapFmt, v.store.CAProvider.Key)
+	}
+
+	return []byte(val), nil
+}
+
 func (v *client) setAuth(ctx context.Context, client Client, cfg *vault.Config) error {
 	tokenRef := v.store.Auth.TokenSecretRef
 	if tokenRef != nil {

File diff suppressed because it is too large
+ 166 - 41
pkg/provider/vault/vault_test.go