Sfoglia il codice sorgente

WIP: SetSecret for keyvault certificates

Signed-off-by: Gustavo Carvalho <gustavo.carvalho@container-solutions.com>
Gustavo Carvalho 4 anni fa
parent
commit
ebb11a77fd

+ 15 - 0
pkg/provider/azure/keyvault/fake/fake.go

@@ -24,6 +24,9 @@ type AzureMockClient struct {
 	getSecret          func(ctx context.Context, vaultBaseURL string, secretName string, secretVersion string) (result keyvault.SecretBundle, err error)
 	getSecret          func(ctx context.Context, vaultBaseURL string, secretName string, secretVersion string) (result keyvault.SecretBundle, err error)
 	getSecretsComplete func(ctx context.Context, vaultBaseURL string, maxresults *int32) (result keyvault.SecretListResultIterator, err error)
 	getSecretsComplete func(ctx context.Context, vaultBaseURL string, maxresults *int32) (result keyvault.SecretListResultIterator, err error)
 	getCertificate     func(ctx context.Context, vaultBaseURL string, certificateName string, certificateVersion string) (result keyvault.CertificateBundle, err error)
 	getCertificate     func(ctx context.Context, vaultBaseURL string, certificateName string, certificateVersion string) (result keyvault.CertificateBundle, err error)
+	setSecret          func(ctx context.Context, vaultBaseURL string, secretName string, parameters keyvault.SecretSetParameters) (result keyvault.SecretBundle, err error)
+	importCertificate  func(ctx context.Context, vaultBaseURL string, certificateName string, parameters keyvault.CertificateImportParameters) (result keyvault.CertificateBundle, err error)
+	createKey          func(ctx context.Context, vaultBaseURL string, keyName string, parameters keyvault.KeyCreateParameters) (result keyvault.KeyBundle, err error)
 }
 }
 
 
 func (mc *AzureMockClient) GetSecret(ctx context.Context, vaultBaseURL, secretName, secretVersion string) (result keyvault.SecretBundle, err error) {
 func (mc *AzureMockClient) GetSecret(ctx context.Context, vaultBaseURL, secretName, secretVersion string) (result keyvault.SecretBundle, err error) {
@@ -42,6 +45,18 @@ func (mc *AzureMockClient) GetSecretsComplete(ctx context.Context, vaultBaseURL
 	return mc.getSecretsComplete(ctx, vaultBaseURL, maxresults)
 	return mc.getSecretsComplete(ctx, vaultBaseURL, maxresults)
 }
 }
 
 
+func (mc *AzureMockClient) SetSecret(ctx context.Context, vaultBaseURL string, secretName string, parameters keyvault.SecretSetParameters) (result keyvault.SecretBundle, err error) {
+	return mc.setSecret(ctx, vaultBaseURL, secretName, parameters)
+}
+
+func (mc *AzureMockClient) ImportCertificate(ctx context.Context, vaultBaseURL string, certificateName string, parameters keyvault.CertificateImportParameters) (result keyvault.CertificateBundle, err error) {
+	return mc.importCertificate(ctx, vaultBaseURL, certificateName, parameters)
+}
+
+func (mc *AzureMockClient) CreateKey(ctx context.Context, vaultBaseURL string, keyName string, parameters keyvault.KeyCreateParameters) (result keyvault.KeyBundle, err error) {
+	return mc.createKey(ctx, vaultBaseURL, keyName, parameters)
+}
+
 func (mc *AzureMockClient) WithValue(serviceURL, secretName, secretVersion string, apiOutput keyvault.SecretBundle, err error) {
 func (mc *AzureMockClient) WithValue(serviceURL, secretName, secretVersion string, apiOutput keyvault.SecretBundle, err error) {
 	if mc != nil {
 	if mc != nil {
 		mc.getSecret = func(ctx context.Context, serviceURL, secretName, secretVersion string) (result keyvault.SecretBundle, retErr error) {
 		mc.getSecret = func(ctx context.Context, serviceURL, secretName, secretVersion string) (result keyvault.SecretBundle, retErr error) {

+ 81 - 3
pkg/provider/azure/keyvault/keyvault.go

@@ -16,6 +16,9 @@ package keyvault
 
 
 import (
 import (
 	"context"
 	"context"
+	"crypto/sha1"
+	"crypto/x509"
+	b64 "encoding/base64"
 	"encoding/json"
 	"encoding/json"
 	"errors"
 	"errors"
 	"fmt"
 	"fmt"
@@ -38,6 +41,7 @@ import (
 	kcorev1 "k8s.io/client-go/kubernetes/typed/core/v1"
 	kcorev1 "k8s.io/client-go/kubernetes/typed/core/v1"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	ctrlcfg "sigs.k8s.io/controller-runtime/pkg/client/config"
 	ctrlcfg "sigs.k8s.io/controller-runtime/pkg/client/config"
+	"software.sslmate.com/src/go-pkcs12"
 
 
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
 	smmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
 	smmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
@@ -90,6 +94,9 @@ type SecretClient interface {
 	GetSecret(ctx context.Context, vaultBaseURL string, secretName string, secretVersion string) (result keyvault.SecretBundle, err error)
 	GetSecret(ctx context.Context, vaultBaseURL string, secretName string, secretVersion string) (result keyvault.SecretBundle, err error)
 	GetSecretsComplete(ctx context.Context, vaultBaseURL string, maxresults *int32) (result keyvault.SecretListResultIterator, err error)
 	GetSecretsComplete(ctx context.Context, vaultBaseURL string, maxresults *int32) (result keyvault.SecretListResultIterator, err error)
 	GetCertificate(ctx context.Context, vaultBaseURL string, certificateName string, certificateVersion string) (result keyvault.CertificateBundle, err error)
 	GetCertificate(ctx context.Context, vaultBaseURL string, certificateName string, certificateVersion string) (result keyvault.CertificateBundle, err error)
+	SetSecret(ctx context.Context, vaultBaseURL string, secretName string, parameters keyvault.SecretSetParameters) (result keyvault.SecretBundle, err error)
+	CreateKey(ctx context.Context, vaultBaseURL string, keyName string, parameters keyvault.KeyCreateParameters) (result keyvault.KeyBundle, err error)
+	ImportCertificate(ctx context.Context, vaultBaseURL string, certificateName string, parameters keyvault.CertificateImportParameters) (result keyvault.CertificateBundle, err error)
 }
 }
 
 
 type Azure struct {
 type Azure struct {
@@ -109,7 +116,7 @@ func init() {
 
 
 // Capabilities return the provider supported capabilities (ReadOnly, WriteOnly, ReadWrite).
 // Capabilities return the provider supported capabilities (ReadOnly, WriteOnly, ReadWrite).
 func (a *Azure) Capabilities() esv1beta1.SecretStoreCapabilities {
 func (a *Azure) Capabilities() esv1beta1.SecretStoreCapabilities {
-	return esv1beta1.SecretStoreReadOnly
+	return esv1beta1.SecretStoreReadWrite
 }
 }
 
 
 // NewClient constructs a new secrets client based on the provided store.
 // NewClient constructs a new secrets client based on the provided store.
@@ -201,9 +208,80 @@ func (a *Azure) ValidateStore(store esv1beta1.GenericStore) error {
 	return nil
 	return nil
 }
 }
 
 
+func getCertificateFromValue(value []byte) (*x509.Certificate, error) {
+	_, localCert, err := pkcs12.Decode(value, "")
+	if err != nil {
+		return x509.ParseCertificate(value)
+	}
+	return localCert, err
+}
+
 // Not Implemented SetSecret.
 // Not Implemented SetSecret.
-func (a *Azure) SetSecret(ctx context.Context, value []byte, remoteRef esv1beta1.PushRemoteRef) error {
-	return fmt.Errorf("not implemented")
+func (a *Azure) SetSecret(ctx context.Context, value []byte, ref esv1beta1.PushRemoteRef) error {
+	objectType, secretName := getObjType(esv1beta1.ExternalSecretDataRemoteRef{Key: ref.GetRemoteKey()})
+	val := string(value)
+	enabled := true
+	manager := "external-secrets"
+	switch objectType {
+	case defaultObjType:
+		// returns a SecretBundle with the secret value
+		// https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/services/keyvault/v7.0/keyvault#SecretBundle
+		secretParams := keyvault.SecretSetParameters{
+			Value: &val,
+			Tags: map[string]*string{
+				"managed-by": &manager,
+			},
+			SecretAttributes: &keyvault.SecretAttributes{
+				Enabled: &enabled,
+			},
+		}
+		_, err := a.baseClient.SetSecret(ctx, *a.provider.VaultURL, secretName, secretParams)
+		if err != nil {
+			return fmt.Errorf("could not set secret %v: %v", ref.GetRemoteKey(), err)
+		}
+	case objectTypeCert:
+
+		val := b64.StdEncoding.EncodeToString(value)
+		localCert, err := getCertificateFromValue(value)
+		if err != nil {
+			return fmt.Errorf("value from secret is not a valid certificate:%v", err)
+		}
+		b := sha1.Sum(localCert.Raw)
+		sha1Fingerprint := b64.RawURLEncoding.EncodeToString(b[:])
+		cert, err := a.baseClient.GetCertificate(ctx, *a.provider.VaultURL, secretName, "")
+		if err != nil && err.(autorest.DetailedError).StatusCode != 404 {
+			return err
+		}
+		if err == nil {
+			man, ok := cert.Tags["managed-by"]
+			if !ok || man == nil || *man != manager {
+				return fmt.Errorf("certificate not managed by external-secrets")
+			}
+
+			if cert.X509Thumbprint != nil && *cert.X509Thumbprint == sha1Fingerprint {
+				return nil //Certificate is the same. No need to update
+			}
+		}
+		// returns a CertBundle. We return CER contents of x509 certificate
+		// see: https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/services/keyvault/v7.0/keyvault#CertificateBundle
+		params := keyvault.CertificateImportParameters{
+			Base64EncodedCertificate: &val,
+			Tags: map[string]*string{
+				"managed-by": &manager,
+			},
+		}
+		_, err = a.baseClient.ImportCertificate(ctx, *a.provider.VaultURL, secretName, params)
+		if err != nil {
+			return fmt.Errorf("could not import certificate %v: %v", secretName, err)
+		}
+	case objectTypeKey:
+		// returns a KeyBundle that contains a jwk
+		// azure kv returns only public keys
+		// see: https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/services/keyvault/v7.0/keyvault#KeyBundle
+	}
+
+	return nil
+
 }
 }
 
 
 // Implements store.Client.GetAllSecrets Interface.
 // Implements store.Client.GetAllSecrets Interface.