components-contrib/secretstores/azure/keyvault/keyvault.go

215 lines
6.4 KiB
Go

/*
Copyright 2021 The Dapr Authors
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"
"fmt"
"reflect"
"strconv"
"strings"
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
"github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets"
azauth "github.com/dapr/components-contrib/common/authentication/azure"
"github.com/dapr/components-contrib/metadata"
"github.com/dapr/components-contrib/secretstores"
"github.com/dapr/kit/logger"
kitmd "github.com/dapr/kit/metadata"
)
// Keyvault secret store component metadata properties
// This is in addition to what's defined in authentication/azure.
const (
VersionID = "version_id"
secretItemIDPrefix = "/secrets/"
)
var _ secretstores.SecretStore = (*keyvaultSecretStore)(nil)
type keyvaultSecretStore struct {
vaultName string
vaultClient *azsecrets.Client
vaultDNSSuffix string
logger logger.Logger
}
type KeyvaultMetadata struct {
VaultName string
}
// NewAzureKeyvaultSecretStore returns a new Azure Key Vault secret store.
func NewAzureKeyvaultSecretStore(logger logger.Logger) secretstores.SecretStore {
return &keyvaultSecretStore{
vaultName: "",
vaultClient: nil,
logger: logger,
}
}
// Init creates a Azure Key Vault client.
func (k *keyvaultSecretStore) Init(_ context.Context, meta secretstores.Metadata) error {
m := KeyvaultMetadata{}
if err := kitmd.DecodeMetadata(meta.Properties, &m); err != nil {
return err
}
// Fix for maintaining backwards compatibility with a change introduced in 1.3 that allowed specifying an Azure environment by setting a FQDN for vault name
// This should be considered deprecated and users should rely the "azureEnvironment" metadata instead, but it's maintained here for backwards-compatibility
if m.VaultName != "" {
keyVaultSuffixToEnvironment := map[string]string{
".vault.azure.net": "AzurePublicCloud",
".vault.azure.cn": "AzureChinaCloud",
".vault.usgovcloudapi.net": "AzureUSGovernmentCloud",
}
for suffix, environment := range keyVaultSuffixToEnvironment {
if strings.HasSuffix(m.VaultName, suffix) {
meta.Properties["azureEnvironment"] = environment
m.VaultName = strings.TrimPrefix(strings.TrimSuffix(m.VaultName, suffix), "https://")
k.vaultName = m.VaultName
break
}
}
}
// Initialization code
settings, err := azauth.NewEnvironmentSettings(meta.Properties)
if err != nil {
return err
}
k.vaultName = m.VaultName
k.vaultDNSSuffix = settings.EndpointSuffix(azauth.ServiceAzureKeyVault)
cred, err := settings.GetTokenCredential()
if err != nil {
return err
}
coreClientOpts := azcore.ClientOptions{
Telemetry: policy.TelemetryOptions{
ApplicationID: "dapr-" + logger.DaprVersion,
},
}
client, clientErr := azsecrets.NewClient(k.getVaultURI(), cred, &azsecrets.ClientOptions{
ClientOptions: coreClientOpts,
})
k.vaultClient = client
return clientErr
}
// GetSecret retrieves a secret using a key and returns a map of decrypted string/string values.
func (k *keyvaultSecretStore) GetSecret(ctx context.Context, req secretstores.GetSecretRequest) (secretstores.GetSecretResponse, error) {
version := "" // empty string means latest version
if val, ok := req.Metadata[VersionID]; ok {
version = val
}
secretResp, err := k.vaultClient.GetSecret(ctx, req.Name, version, nil)
if err != nil {
return secretstores.GetSecretResponse{}, err
}
secretValue := ""
if secretResp.Value != nil {
secretValue = *secretResp.Value
}
return secretstores.GetSecretResponse{
Data: map[string]string{
req.Name: secretValue,
},
}, nil
}
// BulkGetSecret retrieves all secrets in the store and returns a map of decrypted string/string values.
func (k *keyvaultSecretStore) BulkGetSecret(ctx context.Context, req secretstores.BulkGetSecretRequest) (secretstores.BulkGetSecretResponse, error) {
maxResults, err := k.getMaxResultsFromMetadata(req.Metadata)
if err != nil {
return secretstores.BulkGetSecretResponse{}, err
}
resp := secretstores.BulkGetSecretResponse{
Data: map[string]map[string]string{},
}
secretIDPrefix := k.getVaultURI() + secretItemIDPrefix
pager := k.vaultClient.NewListSecretPropertiesPager(nil)
out:
for pager.More() {
pr, err := pager.NextPage(ctx)
if err != nil {
return secretstores.BulkGetSecretResponse{}, err
}
for _, secret := range pr.Value {
if secret.Attributes == nil || secret.Attributes.Enabled == nil || !*secret.Attributes.Enabled {
continue
}
secretName := strings.TrimPrefix(secret.ID.Name(), secretIDPrefix)
secretResp, err := k.vaultClient.GetSecret(ctx, secretName, "", nil) // empty string means latest version
if err != nil {
return secretstores.BulkGetSecretResponse{}, err
}
secretValue := ""
if secretResp.Value != nil {
secretValue = *secretResp.Value
}
resp.Data[secretName] = map[string]string{secretName: secretValue}
}
if maxResults != nil && *maxResults > 0 && len(resp.Data) >= int(*maxResults) {
break out
}
}
return resp, nil
}
// getVaultURI returns Azure Key Vault URI.
func (k *keyvaultSecretStore) getVaultURI() string {
return fmt.Sprintf("https://%s.%s", k.vaultName, k.vaultDNSSuffix)
}
func (k *keyvaultSecretStore) getMaxResultsFromMetadata(metadata map[string]string) (*int32, error) {
if s, ok := metadata["maxresults"]; ok && s != "" {
val, err := strconv.Atoi(s)
if err != nil {
return nil, err
}
converted := int32(val) //nolint:gosec
return &converted, nil
}
return nil, nil
}
// Features returns the features available in this secret store.
func (k *keyvaultSecretStore) Features() []secretstores.Feature {
return []secretstores.Feature{} // No Feature supported.
}
func (k *keyvaultSecretStore) GetComponentMetadata() (metadataInfo metadata.MetadataMap) {
metadataStruct := KeyvaultMetadata{}
metadata.GetMetadataInfoFromStructType(reflect.TypeOf(metadataStruct), &metadataInfo, metadata.SecretStoreType)
return
}