Просмотр исходного кода

WIP: SetSecret for keyvault certificates

Signed-off-by: Gustavo Carvalho <gustavo.carvalho@container-solutions.com>
Gustavo Carvalho 4 лет назад
Родитель
Сommit
ebb11a77fd
2 измененных файлов с 96 добавлено и 3 удалено
  1. 15 0
      pkg/provider/azure/keyvault/fake/fake.go
  2. 81 3
      pkg/provider/azure/keyvault/keyvault.go

+ 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)
 	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)
+	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) {
@@ -42,6 +45,18 @@ func (mc *AzureMockClient) GetSecretsComplete(ctx context.Context, vaultBaseURL
 	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) {
 	if mc != nil {
 		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 (
 	"context"
+	"crypto/sha1"
+	"crypto/x509"
+	b64 "encoding/base64"
 	"encoding/json"
 	"errors"
 	"fmt"
@@ -38,6 +41,7 @@ import (
 	kcorev1 "k8s.io/client-go/kubernetes/typed/core/v1"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	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"
 	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)
 	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)
+	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 {
@@ -109,7 +116,7 @@ func init() {
 
 // Capabilities return the provider supported capabilities (ReadOnly, WriteOnly, ReadWrite).
 func (a *Azure) Capabilities() esv1beta1.SecretStoreCapabilities {
-	return esv1beta1.SecretStoreReadOnly
+	return esv1beta1.SecretStoreReadWrite
 }
 
 // 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
 }
 
+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.
-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.