| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281 |
- /*
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- */
- package keyvault
- import (
- "context"
- "encoding/json"
- "fmt"
- "path"
- "strings"
- "github.com/Azure/azure-sdk-for-go/profiles/latest/keyvault/keyvault"
- kvauth "github.com/Azure/go-autorest/autorest/azure/auth"
- "github.com/tidwall/gjson"
- corev1 "k8s.io/api/core/v1"
- "k8s.io/apimachinery/pkg/types"
- "sigs.k8s.io/controller-runtime/pkg/client"
- esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
- smmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
- "github.com/external-secrets/external-secrets/pkg/provider"
- "github.com/external-secrets/external-secrets/pkg/provider/schema"
- )
- const (
- defaultObjType = "secret"
- )
- // Provider satisfies the provider interface.
- type Provider struct{}
- // interface to keyvault.BaseClient.
- type SecretClient interface {
- GetKey(ctx context.Context, vaultBaseURL string, keyName string, keyVersion string) (result keyvault.KeyBundle, 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)
- GetCertificate(ctx context.Context, vaultBaseURL string, certificateName string, certificateVersion string) (result keyvault.CertificateBundle, err error)
- }
- // Azure satisfies the provider.SecretsClient interface.
- type Azure struct {
- kube client.Client
- store esv1alpha1.GenericStore
- baseClient SecretClient
- vaultURL string
- namespace string
- }
- func init() {
- schema.Register(&Provider{}, &esv1alpha1.SecretStoreProvider{
- AzureKV: &esv1alpha1.AzureKVProvider{},
- })
- }
- // NewClient constructs a new secrets client based on the provided store.
- func (p *Provider) NewClient(ctx context.Context, store esv1alpha1.GenericStore, kube client.Client, namespace string) (provider.SecretsClient, error) {
- return newClient(ctx, store, kube, namespace)
- }
- func newClient(ctx context.Context, store esv1alpha1.GenericStore, kube client.Client, namespace string) (provider.SecretsClient, error) {
- anAzure := &Azure{
- kube: kube,
- store: store,
- namespace: namespace,
- }
- azClient, vaultURL, err := anAzure.newAzureClient(ctx)
- if err != nil {
- return nil, err
- }
- anAzure.baseClient = azClient
- anAzure.vaultURL = vaultURL
- return anAzure, nil
- }
- // Implements store.Client.GetSecret Interface.
- // Retrieves a secret/Key/Certificate with the secret name defined in ref.Name
- // The Object Type is defined as a prefix in the ref.Name , if no prefix is defined , we assume a secret is required.
- func (a *Azure) GetSecret(ctx context.Context, ref esv1alpha1.ExternalSecretDataRemoteRef) ([]byte, error) {
- version := ""
- basicClient := a.baseClient
- objectType, secretName := getObjType(ref)
- if ref.Version != "" {
- version = ref.Version
- }
- 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
- secretResp, err := basicClient.GetSecret(context.Background(), a.vaultURL, secretName, version)
- if err != nil {
- return nil, err
- }
- if ref.Property == "" {
- return []byte(*secretResp.Value), nil
- }
- res := gjson.Get(*secretResp.Value, ref.Property)
- if !res.Exists() {
- return nil, fmt.Errorf("property %s does not exist in key %s", ref.Property, ref.Key)
- }
- return []byte(res.String()), err
- case "cert":
- // 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
- secretResp, err := basicClient.GetCertificate(context.Background(), a.vaultURL, secretName, version)
- if err != nil {
- return nil, err
- }
- return *secretResp.Cer, nil
- case "key":
- // returns a KeyBundla 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
- keyResp, err := basicClient.GetKey(context.Background(), a.vaultURL, secretName, version)
- if err != nil {
- return nil, err
- }
- return json.Marshal(keyResp.Key)
- }
- return nil, fmt.Errorf("unknown Azure Keyvault object Type for %s", secretName)
- }
- // Implements store.Client.GetSecretMap Interface.
- // New version of GetSecretMap.
- func (a *Azure) GetSecretMap(ctx context.Context, ref esv1alpha1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
- objectType, secretName := getObjType(ref)
- switch objectType {
- case defaultObjType:
- data, err := a.GetSecret(ctx, ref)
- if err != nil {
- return nil, err
- }
- kv := make(map[string]string)
- err = json.Unmarshal(data, &kv)
- if err != nil {
- return nil, fmt.Errorf("error unmarshalling json data: %w", err)
- }
- secretData := make(map[string][]byte)
- for k, v := range kv {
- secretData[k] = []byte(v)
- }
- return secretData, nil
- case "cert":
- return nil, fmt.Errorf("cannot get use dataFrom to get certificate secret")
- case "key":
- return nil, fmt.Errorf("cannot get use dataFrom to get key secret")
- }
- return nil, fmt.Errorf("unknown Azure Keyvault object Type for %s", secretName)
- }
- // Implements store.Client.GetAllSecrets Interface.
- // New version of GetAllSecrets.
- func (a *Azure) GetAllSecrets(ctx context.Context, ref esv1alpha1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
- basicClient := a.baseClient
- secretsMap := make(map[string][]byte)
- secretListIter, err := basicClient.GetSecretsComplete(context.Background(), a.vaultURL, nil)
- if err != nil {
- return nil, err
- }
- for secretListIter.NotDone() {
- secretList := secretListIter.Response().Value
- for _, secret := range *secretList {
- if !*secret.Attributes.Enabled {
- continue
- }
- secretName := path.Base(*secret.ID)
- secretResp, err := basicClient.GetSecret(context.Background(), a.vaultURL, secretName, "")
- secretValue := *secretResp.Value
- if err != nil {
- return nil, err
- }
- secretsMap[secretName] = []byte(secretValue)
- }
- err = secretListIter.Next()
- if err != nil {
- return nil, err
- }
- }
- return secretsMap, nil
- }
- func (a *Azure) newAzureClient(ctx context.Context) (*keyvault.BaseClient, string, error) {
- spec := *a.store.GetSpec().Provider.AzureKV
- tenantID := *spec.TenantID
- vaultURL := *spec.VaultURL
- if spec.AuthSecretRef == nil {
- return nil, "", fmt.Errorf("missing clientID/clientSecret in store config")
- }
- clusterScoped := false
- if a.store.GetObjectKind().GroupVersionKind().Kind == esv1alpha1.ClusterSecretStoreKind {
- clusterScoped = true
- }
- if spec.AuthSecretRef.ClientID == nil || spec.AuthSecretRef.ClientSecret == nil {
- return nil, "", fmt.Errorf("missing accessKeyID/secretAccessKey in store config")
- }
- cid, err := a.secretKeyRef(ctx, a.store.GetNamespace(), *spec.AuthSecretRef.ClientID, clusterScoped)
- if err != nil {
- return nil, "", err
- }
- csec, err := a.secretKeyRef(ctx, a.store.GetNamespace(), *spec.AuthSecretRef.ClientSecret, clusterScoped)
- if err != nil {
- return nil, "", err
- }
- clientCredentialsConfig := kvauth.NewClientCredentialsConfig(cid, csec, tenantID)
- // the default resource api is the management URL and not the vault URL which we need for keyvault operations
- clientCredentialsConfig.Resource = "https://vault.azure.net"
- authorizer, err := clientCredentialsConfig.Authorizer()
- if err != nil {
- return nil, "", err
- }
- basicClient := keyvault.New()
- basicClient.Authorizer = authorizer
- return &basicClient, vaultURL, nil
- }
- func (a *Azure) secretKeyRef(ctx context.Context, namespace string, secretRef smmeta.SecretKeySelector, clusterScoped bool) (string, error) {
- var secret corev1.Secret
- ref := types.NamespacedName{
- Namespace: namespace,
- Name: secretRef.Name,
- }
- if clusterScoped && secretRef.Namespace != nil {
- ref.Namespace = *secretRef.Namespace
- }
- err := a.kube.Get(ctx, ref, &secret)
- if err != nil {
- return "", fmt.Errorf("could not find secret %s/%s: %w", ref.Namespace, ref.Name, err)
- }
- keyBytes, ok := secret.Data[secretRef.Key]
- if !ok {
- return "", fmt.Errorf("no data for %q in secret '%s/%s'", secretRef.Key, secretRef.Name, namespace)
- }
- value := strings.TrimSpace(string(keyBytes))
- return value, nil
- }
- func (a *Azure) Close(ctx context.Context) error {
- return nil
- }
- func getObjType(ref esv1alpha1.ExternalSecretDataRemoteRef) (string, string) {
- objectType := defaultObjType
- secretName := ref.Key
- nameSplitted := strings.Split(secretName, "/")
- if len(nameSplitted) > 1 {
- objectType = nameSplitted[0]
- secretName = nameSplitted[1]
- // TODO: later tokens can be used to read the secret tags
- }
- return objectType, secretName
- }
|