Crypto component for Azure Key Vault (#2692)
Signed-off-by: ItalyPaleAle <43508+ItalyPaleAle@users.noreply.github.com>
This commit is contained in:
parent
f2d46b4489
commit
f6d9ac7e99
|
|
@ -161,6 +161,15 @@ const components = {
|
||||||
'configuration.redis': {
|
'configuration.redis': {
|
||||||
certification: true,
|
certification: true,
|
||||||
},
|
},
|
||||||
|
'crypto.azure.keyvault': {
|
||||||
|
conformance: true,
|
||||||
|
requiredSecrets: [
|
||||||
|
'AzureKeyVaultName',
|
||||||
|
'AzureKeyVaultTenantId',
|
||||||
|
'AzureKeyVaultServicePrincipalClientId',
|
||||||
|
'AzureKeyVaultServicePrincipalClientSecret',
|
||||||
|
],
|
||||||
|
},
|
||||||
'crypto.localstorage': {
|
'crypto.localstorage': {
|
||||||
conformance: true,
|
conformance: true,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
/*
|
||||||
|
Copyright 2023 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 (
|
||||||
|
"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
|
||||||
|
"github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys"
|
||||||
|
|
||||||
|
internals "github.com/dapr/kit/crypto"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
validEncryptionAlgs map[string]struct{}
|
||||||
|
validSignatureAlgs map[string]struct{}
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetJWKEncryptionAlgorithm returns a JSONWebKeyEncryptionAlgorithm constant is the algorithm is a supported one.
|
||||||
|
func GetJWKEncryptionAlgorithm(algorithm string) *azkeys.JSONWebKeyEncryptionAlgorithm {
|
||||||
|
// Special case for AES-CBC, since we treat A[NNN]CBC as having PKCS#7 padding, and A[NNN]CBC-NOPAD as not using padding
|
||||||
|
switch algorithm {
|
||||||
|
case internals.Algorithm_A128CBC, internals.Algorithm_A192CBC, internals.Algorithm_A256CBC:
|
||||||
|
// Append "PAD", e.g. "A128CBCPAD"
|
||||||
|
algorithm += "PAD"
|
||||||
|
case internals.Algorithm_A128CBC_NOPAD, internals.Algorithm_A192CBC_NOPAD, internals.Algorithm_A256CBC_NOPAD:
|
||||||
|
// Remove the "-NOPAD" suffix, e.g. "A128CBC"
|
||||||
|
algorithm = algorithm[:len(algorithm)-6]
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := validEncryptionAlgs[algorithm]; ok {
|
||||||
|
return to.Ptr(azkeys.JSONWebKeyEncryptionAlgorithm(algorithm))
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetJWKSignatureAlgorithm returns a JSONWebKeySignatureAlgorithm constant is the algorithm is a supported one.
|
||||||
|
func GetJWKSignatureAlgorithm(algorithm string) *azkeys.JSONWebKeySignatureAlgorithm {
|
||||||
|
if _, ok := validSignatureAlgs[algorithm]; ok {
|
||||||
|
return to.Ptr(azkeys.JSONWebKeySignatureAlgorithm(algorithm))
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type algorithms interface {
|
||||||
|
azkeys.JSONWebKeyEncryptionAlgorithm | azkeys.JSONWebKeySignatureAlgorithm
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsAlgorithmAsymmetric returns true if the algorithm identifier is asymmetric.
|
||||||
|
func IsAlgorithmAsymmetric[T algorithms](algorithm T) bool {
|
||||||
|
algStr := string(algorithm)
|
||||||
|
switch algStr[0:2] {
|
||||||
|
case "RS", "ES", "PS":
|
||||||
|
// RSNULL is a reserved keyword
|
||||||
|
return algStr != "RSNULL"
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,437 @@
|
||||||
|
/*
|
||||||
|
Copyright 2023 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"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"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/keyvault/azkeys"
|
||||||
|
"github.com/lestrrat-go/jwx/v2/jwa"
|
||||||
|
"github.com/lestrrat-go/jwx/v2/jwk"
|
||||||
|
|
||||||
|
contribCrypto "github.com/dapr/components-contrib/crypto"
|
||||||
|
contribMetadata "github.com/dapr/components-contrib/metadata"
|
||||||
|
internals "github.com/dapr/kit/crypto"
|
||||||
|
"github.com/dapr/kit/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errKeyNotFound = errors.New("key not found in the vault")
|
||||||
|
|
||||||
|
// Used to initialize validEncryptionAlgs and validSignatureAlgs lazily when the first component of this kind is initialized
|
||||||
|
algsParsed sync.Once
|
||||||
|
)
|
||||||
|
|
||||||
|
type keyvaultCrypto struct {
|
||||||
|
keyCache *contribCrypto.PubKeyCache
|
||||||
|
md keyvaultMetadata
|
||||||
|
vaultClient *azkeys.Client
|
||||||
|
logger logger.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAzureKeyvaultCrypto returns a new Azure Key Vault crypto provider.
|
||||||
|
func NewAzureKeyvaultCrypto(logger logger.Logger) contribCrypto.SubtleCrypto {
|
||||||
|
return &keyvaultCrypto{
|
||||||
|
logger: logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init creates a Azure Key Vault client.
|
||||||
|
func (k *keyvaultCrypto) Init(_ context.Context, metadata contribCrypto.Metadata) error {
|
||||||
|
// Convert from data from the Azure SDK, which returns a slice, into a map
|
||||||
|
// We perform the initialization here, lazily, when the first component of this kind is initialized
|
||||||
|
// (These functions do not make network calls)
|
||||||
|
algsParsed.Do(func() {
|
||||||
|
listEncryption := azkeys.PossibleJSONWebKeyEncryptionAlgorithmValues()
|
||||||
|
validEncryptionAlgs = make(map[string]struct{}, len(listEncryption))
|
||||||
|
for _, v := range listEncryption {
|
||||||
|
validEncryptionAlgs[string(v)] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
listSignature := azkeys.PossibleJSONWebKeySignatureAlgorithmValues()
|
||||||
|
validSignatureAlgs = make(map[string]struct{}, len(listSignature))
|
||||||
|
for _, v := range listSignature {
|
||||||
|
validSignatureAlgs[string(v)] = struct{}{}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Init the metadata
|
||||||
|
err := k.md.InitWithMetadata(metadata)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to load metadata: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a cache for keys
|
||||||
|
k.keyCache = contribCrypto.NewPubKeyCache(k.getKeyCacheFn)
|
||||||
|
|
||||||
|
// Init the Azure SDK client
|
||||||
|
k.vaultClient, err = azkeys.NewClient(k.getVaultURI(), k.md.cred, &azkeys.ClientOptions{
|
||||||
|
ClientOptions: azcore.ClientOptions{
|
||||||
|
Telemetry: policy.TelemetryOptions{
|
||||||
|
ApplicationID: "dapr-" + logger.DaprVersion,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Features returns the features available in this crypto provider.
|
||||||
|
func (k *keyvaultCrypto) Features() []contribCrypto.Feature {
|
||||||
|
return []contribCrypto.Feature{} // No Feature supported.
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetKey returns the public part of a key stored in the vault.
|
||||||
|
// This method returns an error if the key is symmetric.
|
||||||
|
// The key argument can be in the format "name" or "name/version".
|
||||||
|
func (k *keyvaultCrypto) GetKey(parentCtx context.Context, key string) (pubKey jwk.Key, err error) {
|
||||||
|
kid := newKeyID(key)
|
||||||
|
|
||||||
|
// If the key is cacheable, get it from the cache
|
||||||
|
if kid.Cacheable() {
|
||||||
|
return k.keyCache.GetKey(parentCtx, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
return k.getKeyFromVault(parentCtx, kid)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *keyvaultCrypto) getKeyFromVault(parentCtx context.Context, kid keyID) (pubKey jwk.Key, err error) {
|
||||||
|
ctx, cancel := context.WithTimeout(parentCtx, k.md.RequestTimeout)
|
||||||
|
res, err := k.vaultClient.GetKey(ctx, kid.Name, kid.Version, nil)
|
||||||
|
cancel()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get key from Key Vault: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return KeyBundleToKey(&res.KeyBundle)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handler for the getKeyCacheFn method
|
||||||
|
func (k *keyvaultCrypto) getKeyCacheFn(key string) func(resolve func(jwk.Key), reject func(error)) {
|
||||||
|
kid := newKeyID(key)
|
||||||
|
parentCtx := context.Background()
|
||||||
|
return func(resolve func(jwk.Key), reject func(error)) {
|
||||||
|
pk, err := k.getKeyFromVault(parentCtx, kid)
|
||||||
|
if err != nil {
|
||||||
|
reject(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resolve(pk)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encrypt a small message and returns the ciphertext.
|
||||||
|
// The key argument can be in the format "name" or "name/version".
|
||||||
|
func (k *keyvaultCrypto) Encrypt(parentCtx context.Context, plaintext []byte, algorithmStr string, key string, nonce []byte, associatedData []byte) (ciphertext []byte, tag []byte, err error) {
|
||||||
|
kid := newKeyID(key)
|
||||||
|
|
||||||
|
algorithm := GetJWKEncryptionAlgorithm(algorithmStr)
|
||||||
|
if algorithm == nil {
|
||||||
|
return nil, nil, fmt.Errorf("invalid algorithm: %s", algorithmStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encrypting with symmetric or non-cacheable keys must happen in the vault
|
||||||
|
if !kid.Cacheable() || !IsAlgorithmAsymmetric(*algorithm) {
|
||||||
|
return k.encryptInVault(parentCtx, plaintext, algorithm, kid, nonce, associatedData)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Using a cacheable, asymmetric key, we can encrypt the data directly here
|
||||||
|
pk, err := k.keyCache.GetKey(parentCtx, key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to retrieve public key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the key has expired, we cannot use that to encrypt data
|
||||||
|
if dpk, ok := pk.(*contribCrypto.Key); ok && !dpk.IsValid() {
|
||||||
|
return nil, nil, errors.New("the key is outside of its time validity bounds")
|
||||||
|
}
|
||||||
|
|
||||||
|
ciphertext, err = internals.EncryptPublicKey(plaintext, algorithmStr, pk, associatedData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to encrypt data: %w", err)
|
||||||
|
}
|
||||||
|
return ciphertext, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *keyvaultCrypto) encryptInVault(parentCtx context.Context, plaintext []byte, algorithm *azkeys.JSONWebKeyEncryptionAlgorithm, kid keyID, nonce []byte, associatedData []byte) (ciphertext []byte, tag []byte, err error) {
|
||||||
|
ctx, cancel := context.WithTimeout(parentCtx, k.md.RequestTimeout)
|
||||||
|
res, err := k.vaultClient.Encrypt(ctx, kid.Name, kid.Version, azkeys.KeyOperationsParameters{
|
||||||
|
Algorithm: algorithm,
|
||||||
|
Value: plaintext,
|
||||||
|
IV: nonce,
|
||||||
|
AAD: associatedData,
|
||||||
|
}, nil)
|
||||||
|
cancel()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("error from Key Vault: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.Result == nil {
|
||||||
|
return nil, nil, errors.New("response from Key Vault does not contain a valid ciphertext")
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.Result, res.AuthenticationTag, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decrypt a small message and returns the plaintext.
|
||||||
|
// The key argument can be in the format "name" or "name/version".
|
||||||
|
func (k *keyvaultCrypto) Decrypt(parentCtx context.Context, ciphertext []byte, algorithmStr string, key string, nonce []byte, tag []byte, associatedData []byte) (plaintext []byte, err error) {
|
||||||
|
kid := newKeyID(key)
|
||||||
|
|
||||||
|
algorithm := GetJWKEncryptionAlgorithm(algorithmStr)
|
||||||
|
if algorithm == nil {
|
||||||
|
return nil, fmt.Errorf("invalid algorithm: %s", algorithmStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(parentCtx, k.md.RequestTimeout)
|
||||||
|
res, err := k.vaultClient.Decrypt(ctx, kid.Name, kid.Version, azkeys.KeyOperationsParameters{
|
||||||
|
Algorithm: algorithm,
|
||||||
|
Value: ciphertext,
|
||||||
|
IV: nonce,
|
||||||
|
Tag: tag,
|
||||||
|
AAD: associatedData,
|
||||||
|
}, nil)
|
||||||
|
cancel()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error from Key Vault: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.Result == nil {
|
||||||
|
return nil, errors.New("response from Key Vault does not contain a valid plaintext")
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WrapKey wraps a symmetric key.
|
||||||
|
// The key argument can be in the format "name" or "name/version".
|
||||||
|
func (k *keyvaultCrypto) WrapKey(parentCtx context.Context, plaintextKey jwk.Key, algorithmStr string, key string, nonce []byte, associatedData []byte) (wrappedKey []byte, tag []byte, err error) {
|
||||||
|
// Azure Key Vault does not support wrapping asymmetric keys
|
||||||
|
if plaintextKey.KeyType() != jwa.OctetSeq {
|
||||||
|
return nil, nil, errors.New("cannot wrap asymmetric keys")
|
||||||
|
}
|
||||||
|
plaintext, err := internals.SerializeKey(plaintextKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("cannot serialize key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
kid := newKeyID(key)
|
||||||
|
|
||||||
|
algorithm := GetJWKEncryptionAlgorithm(algorithmStr)
|
||||||
|
if algorithm == nil {
|
||||||
|
return nil, nil, fmt.Errorf("invalid algorithm: %s", algorithmStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encrypting with symmetric or non-cacheable keys must happen in the vault
|
||||||
|
if !kid.Cacheable() || !IsAlgorithmAsymmetric(*algorithm) {
|
||||||
|
return k.wrapKeyInVault(parentCtx, plaintext, algorithm, kid, nonce, associatedData)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Using a cacheable, asymmetric key, we can encrypt the data directly here
|
||||||
|
pk, err := k.keyCache.GetKey(parentCtx, key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to retrieve public key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the key has expired, we cannot use that to encrypt data
|
||||||
|
if dpk, ok := pk.(*contribCrypto.Key); ok && !dpk.IsValid() {
|
||||||
|
return nil, nil, errors.New("the key is outside of its time validity bounds")
|
||||||
|
}
|
||||||
|
|
||||||
|
wrappedKey, err = internals.EncryptPublicKey(plaintext, algorithmStr, pk, associatedData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to wrap key: %w", err)
|
||||||
|
}
|
||||||
|
return wrappedKey, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *keyvaultCrypto) wrapKeyInVault(parentCtx context.Context, plaintextKey []byte, algorithm *azkeys.JSONWebKeyEncryptionAlgorithm, kid keyID, nonce []byte, associatedData []byte) (wrappedKey []byte, tag []byte, err error) {
|
||||||
|
ctx, cancel := context.WithTimeout(parentCtx, k.md.RequestTimeout)
|
||||||
|
res, err := k.vaultClient.WrapKey(ctx, kid.Name, kid.Version, azkeys.KeyOperationsParameters{
|
||||||
|
Algorithm: algorithm,
|
||||||
|
Value: plaintextKey,
|
||||||
|
IV: nonce,
|
||||||
|
AAD: associatedData,
|
||||||
|
}, nil)
|
||||||
|
cancel()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("error from Key Vault: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.Result == nil {
|
||||||
|
return nil, nil, errors.New("response from Key Vault does not contain a valid wrapped key")
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.Result, res.AuthenticationTag, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnwrapKey unwraps a key.
|
||||||
|
// The key argument can be in the format "name" or "name/version".
|
||||||
|
func (k *keyvaultCrypto) UnwrapKey(parentCtx context.Context, wrappedKey []byte, algorithmStr string, key string, nonce []byte, tag []byte, associatedData []byte) (plaintextKey jwk.Key, err error) {
|
||||||
|
kid := newKeyID(key)
|
||||||
|
|
||||||
|
algorithm := GetJWKEncryptionAlgorithm(algorithmStr)
|
||||||
|
if algorithm == nil {
|
||||||
|
return nil, fmt.Errorf("invalid algorithm: %s", algorithmStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(parentCtx, k.md.RequestTimeout)
|
||||||
|
res, err := k.vaultClient.UnwrapKey(ctx, kid.Name, kid.Version, azkeys.KeyOperationsParameters{
|
||||||
|
Algorithm: algorithm,
|
||||||
|
Value: wrappedKey,
|
||||||
|
IV: nonce,
|
||||||
|
Tag: tag,
|
||||||
|
AAD: associatedData,
|
||||||
|
}, nil)
|
||||||
|
cancel()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error from Key Vault: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.Result == nil {
|
||||||
|
return nil, errors.New("response from Key Vault does not contain a valid unwrapped key")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Key Vault allows wrapping/unwrapping only symmetric keys, so no need to try and decode an ASN.1 DER-encoded sequence
|
||||||
|
plaintextKey, err = jwk.FromRaw(res.Result)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create JWK from raw key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return plaintextKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign a digest.
|
||||||
|
// The key argument can be in the format "name" or "name/version".
|
||||||
|
func (k *keyvaultCrypto) Sign(parentCtx context.Context, digest []byte, algorithmStr string, key string) (signature []byte, err error) {
|
||||||
|
kid := newKeyID(key)
|
||||||
|
|
||||||
|
algorithm := GetJWKSignatureAlgorithm(algorithmStr)
|
||||||
|
if algorithm == nil {
|
||||||
|
return nil, fmt.Errorf("invalid algorithm: %s", algorithmStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(parentCtx, k.md.RequestTimeout)
|
||||||
|
res, err := k.vaultClient.Sign(ctx, kid.Name, kid.Version, azkeys.SignParameters{
|
||||||
|
Algorithm: algorithm,
|
||||||
|
Value: digest,
|
||||||
|
}, nil)
|
||||||
|
cancel()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error from Key Vault: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.Result == nil {
|
||||||
|
return nil, errors.New("response from Key Vault does not contain a valid signature")
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify a signature.
|
||||||
|
// The key argument can be in the format "name" or "name/version".
|
||||||
|
func (k *keyvaultCrypto) Verify(parentCtx context.Context, digest []byte, signature []byte, algorithmStr string, key string) (valid bool, err error) {
|
||||||
|
kid := newKeyID(key)
|
||||||
|
|
||||||
|
algorithm := GetJWKSignatureAlgorithm(algorithmStr)
|
||||||
|
if algorithm == nil {
|
||||||
|
return false, fmt.Errorf("invalid algorithm: %s", algorithmStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verifying with non-cacheable keys must happen in the vault
|
||||||
|
if !kid.Cacheable() {
|
||||||
|
return k.verifyInVault(parentCtx, digest, signature, algorithm, kid)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Using a cacheable, asymmetric key, we can verify the data directly here
|
||||||
|
pk, err := k.keyCache.GetKey(parentCtx, key)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("failed to retrieve public key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
valid, err = internals.VerifyPublicKey(digest, signature, algorithmStr, pk)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("failed to verify signature: %w", err)
|
||||||
|
}
|
||||||
|
return valid, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *keyvaultCrypto) verifyInVault(parentCtx context.Context, digest []byte, signature []byte, algorithm *azkeys.JSONWebKeySignatureAlgorithm, kid keyID) (valid bool, err error) {
|
||||||
|
ctx, cancel := context.WithTimeout(parentCtx, k.md.RequestTimeout)
|
||||||
|
res, err := k.vaultClient.Verify(ctx, kid.Name, kid.Version, azkeys.VerifyParameters{
|
||||||
|
Algorithm: algorithm,
|
||||||
|
Digest: digest,
|
||||||
|
Signature: signature,
|
||||||
|
}, nil)
|
||||||
|
cancel()
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("error from Key Vault: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.Value == nil {
|
||||||
|
return false, errors.New("response from Key Vault does not contain a valid response")
|
||||||
|
}
|
||||||
|
|
||||||
|
return *res.Value, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getVaultURI returns Azure Key Vault URI.
|
||||||
|
func (k *keyvaultCrypto) getVaultURI() string {
|
||||||
|
return fmt.Sprintf("https://%s.%s", k.md.VaultName, k.md.vaultDNSSuffix)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (keyvaultCrypto) GetComponentMetadata() map[string]string {
|
||||||
|
metadataStruct := keyvaultMetadata{}
|
||||||
|
metadataInfo := map[string]string{}
|
||||||
|
contribMetadata.GetMetadataInfoFromStructType(reflect.TypeOf(metadataStruct), &metadataInfo)
|
||||||
|
return metadataInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
type keyID struct {
|
||||||
|
Version string
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newKeyID(val string) keyID {
|
||||||
|
obj := keyID{}
|
||||||
|
idx := strings.IndexRune(val, '/')
|
||||||
|
// Can't be on position 0, because the key name must be at least 1 character
|
||||||
|
if idx > 0 {
|
||||||
|
obj.Version = val[idx+1:]
|
||||||
|
obj.Name = val[:idx]
|
||||||
|
} else {
|
||||||
|
obj.Name = val
|
||||||
|
}
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cacheable returns true if the key can be cached locally.
|
||||||
|
func (id keyID) Cacheable() bool {
|
||||||
|
switch strings.ToLower(id.Version) {
|
||||||
|
case "", "latest":
|
||||||
|
return false
|
||||||
|
default:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,142 @@
|
||||||
|
/*
|
||||||
|
Copyright 2023 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 (
|
||||||
|
"crypto"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
|
"crypto/rsa"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
|
||||||
|
"github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys"
|
||||||
|
"github.com/lestrrat-go/jwx/v2/jwk"
|
||||||
|
|
||||||
|
contribCrypto "github.com/dapr/components-contrib/crypto"
|
||||||
|
)
|
||||||
|
|
||||||
|
// KeyBundleToKey converts an azkeys.KeyBundle object to a contribCrypto.Key object, containing only the public part of the asymmetric key.
|
||||||
|
func KeyBundleToKey(bundle *azkeys.KeyBundle) (*contribCrypto.Key, error) {
|
||||||
|
if bundle == nil ||
|
||||||
|
bundle.Key == nil || bundle.Key.KID == nil ||
|
||||||
|
bundle.Attributes == nil || bundle.Attributes.Enabled == nil || *bundle.Attributes.Enabled == false {
|
||||||
|
return nil, errKeyNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the key ID
|
||||||
|
kid := bundle.Key.KID.Name()
|
||||||
|
if ver := bundle.Key.KID.Version(); ver != "" {
|
||||||
|
kid += "/" + ver
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the public key and create a jwk.Key from that
|
||||||
|
pk, err := JSONWebKey{*bundle.Key}.Public()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to extract public key as crypto.PublicKey: %w", err)
|
||||||
|
}
|
||||||
|
jwkObj, err := jwk.FromRaw(pk)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create jwk.Key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to daprcrypto.Key
|
||||||
|
return contribCrypto.NewKey(jwkObj, kid, bundle.Attributes.Expires, bundle.Attributes.NotBefore), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSONWebKey extends azkeys.JSONWebKey to add methods to export the key.
|
||||||
|
type JSONWebKey struct {
|
||||||
|
azkeys.JSONWebKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// Public returns the public key included the object, as a crypto.PublicKey object.
|
||||||
|
// This method returns an error if it's invoked on a JSONWebKey object representing a symmetric key.
|
||||||
|
func (key JSONWebKey) Public() (crypto.PublicKey, error) {
|
||||||
|
if key.Kty == nil {
|
||||||
|
return nil, errors.New("property Kty is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case IsRSAKey(*key.Kty):
|
||||||
|
return key.publicRSA()
|
||||||
|
case IsECKey(*key.Kty):
|
||||||
|
return key.publicEC()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.New("unsupported key type")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (key JSONWebKey) publicRSA() (*rsa.PublicKey, error) {
|
||||||
|
res := &rsa.PublicKey{}
|
||||||
|
|
||||||
|
// N = modulus
|
||||||
|
if len(key.N) == 0 {
|
||||||
|
return nil, errors.New("property N is empty")
|
||||||
|
}
|
||||||
|
res.N = &big.Int{}
|
||||||
|
res.N.SetBytes(key.N)
|
||||||
|
|
||||||
|
// e = public exponent
|
||||||
|
if len(key.E) == 0 {
|
||||||
|
return nil, errors.New("property e is empty")
|
||||||
|
}
|
||||||
|
res.E = int(big.NewInt(0).SetBytes(key.E).Uint64())
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (key JSONWebKey) publicEC() (*ecdsa.PublicKey, error) {
|
||||||
|
res := &ecdsa.PublicKey{}
|
||||||
|
|
||||||
|
if key.Crv == nil {
|
||||||
|
return nil, errors.New("property Crv is nil")
|
||||||
|
}
|
||||||
|
switch *key.Crv {
|
||||||
|
case azkeys.JSONWebKeyCurveNameP256:
|
||||||
|
res.Curve = elliptic.P256()
|
||||||
|
case azkeys.JSONWebKeyCurveNameP384:
|
||||||
|
res.Curve = elliptic.P384()
|
||||||
|
case azkeys.JSONWebKeyCurveNameP521:
|
||||||
|
res.Curve = elliptic.P521()
|
||||||
|
case azkeys.JSONWebKeyCurveNameP256K:
|
||||||
|
return nil, errors.New("curves of type P-256K are not supported by this method")
|
||||||
|
}
|
||||||
|
|
||||||
|
// X coordinate
|
||||||
|
if len(key.X) == 0 {
|
||||||
|
return nil, errors.New("property X is empty")
|
||||||
|
}
|
||||||
|
res.X = &big.Int{}
|
||||||
|
res.X.SetBytes(key.X)
|
||||||
|
|
||||||
|
// Y coordinate
|
||||||
|
if len(key.Y) == 0 {
|
||||||
|
return nil, errors.New("property Y is empty")
|
||||||
|
}
|
||||||
|
res.Y = &big.Int{}
|
||||||
|
res.Y.SetBytes(key.Y)
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsRSAKey returns true if the key is an RSA key (RSA or RSA-HSM).
|
||||||
|
func IsRSAKey(kt azkeys.JSONWebKeyType) bool {
|
||||||
|
return kt == azkeys.JSONWebKeyTypeRSA || kt == azkeys.JSONWebKeyTypeRSAHSM
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsECKey returns true if the key is an EC key (EC or EC-HSM).
|
||||||
|
func IsECKey(kt azkeys.JSONWebKeyType) bool {
|
||||||
|
return kt == azkeys.JSONWebKeyTypeEC || kt == azkeys.JSONWebKeyTypeECHSM
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
/*
|
||||||
|
Copyright 2023 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 (
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
|
||||||
|
|
||||||
|
contribCrypto "github.com/dapr/components-contrib/crypto"
|
||||||
|
azauth "github.com/dapr/components-contrib/internal/authentication/azure"
|
||||||
|
"github.com/dapr/components-contrib/metadata"
|
||||||
|
)
|
||||||
|
|
||||||
|
const defaultRequestTimeout = 30 * time.Second
|
||||||
|
|
||||||
|
type keyvaultMetadata struct {
|
||||||
|
// Name of the Azure Key Vault resource (required).
|
||||||
|
VaultName string `json:"vaultName" mapstructure:"vaultName"`
|
||||||
|
|
||||||
|
// Timeout for network requests, as a Go duration string (e.g. "30s")
|
||||||
|
// Defaults to "30s".
|
||||||
|
RequestTimeout time.Duration `json:"requestTimeout" mapstructure:"requestTimeout"`
|
||||||
|
|
||||||
|
// Internal properties
|
||||||
|
vaultDNSSuffix string
|
||||||
|
cred azcore.TokenCredential
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *keyvaultMetadata) InitWithMetadata(meta contribCrypto.Metadata) error {
|
||||||
|
m.reset()
|
||||||
|
|
||||||
|
// Decode the metadata
|
||||||
|
err := metadata.DecodeMetadata(meta.Properties, &m)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vault name
|
||||||
|
if m.VaultName == "" {
|
||||||
|
return errors.New("metadata property 'vaultName' is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set default requestTimeout if empty
|
||||||
|
if m.RequestTimeout < time.Second {
|
||||||
|
m.RequestTimeout = defaultRequestTimeout
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the DNS suffix
|
||||||
|
settings, err := azauth.NewEnvironmentSettings(meta.Properties)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m.vaultDNSSuffix = settings.EndpointSuffix(azauth.ServiceAzureKeyVault)
|
||||||
|
|
||||||
|
// Get the credentials object
|
||||||
|
m.cred, err = settings.GetTokenCredential()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset the object
|
||||||
|
func (m *keyvaultMetadata) reset() {
|
||||||
|
m.VaultName = ""
|
||||||
|
m.RequestTimeout = defaultRequestTimeout
|
||||||
|
|
||||||
|
m.vaultDNSSuffix = ""
|
||||||
|
m.cred = nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
/*
|
||||||
|
Copyright 2023 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 crypto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/chebyrash/promise"
|
||||||
|
"github.com/lestrrat-go/jwx/v2/jwk"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetKeyFn is the type of the getKeyFn function used by the PubKeyCache.
|
||||||
|
type GetKeyFn = func(key string) func(resolve func(jwk.Key), reject func(error))
|
||||||
|
|
||||||
|
// PubKeyCache implements GetKey with a local cache.
|
||||||
|
type PubKeyCache struct {
|
||||||
|
getKeyFn GetKeyFn
|
||||||
|
|
||||||
|
pubKeys map[string]*promise.Promise[jwk.Key]
|
||||||
|
pubKeysLock *sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPubKeyCache returns a new PubKeyCache object
|
||||||
|
func NewPubKeyCache(getKeyFn GetKeyFn) *PubKeyCache {
|
||||||
|
return &PubKeyCache{
|
||||||
|
getKeyFn: getKeyFn,
|
||||||
|
pubKeys: map[string]*promise.Promise[jwk.Key]{},
|
||||||
|
pubKeysLock: &sync.Mutex{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetKey returns a public key from the cache, or uses getKeyFn to request it
|
||||||
|
func (kc *PubKeyCache) GetKey(parentCtx context.Context, key string) (pubKey jwk.Key, err error) {
|
||||||
|
timeoutPromise := promise.New(func(_ func(jwk.Key), reject func(error)) {
|
||||||
|
<-parentCtx.Done()
|
||||||
|
reject(parentCtx.Err())
|
||||||
|
})
|
||||||
|
|
||||||
|
// Check if the key is in the cache already
|
||||||
|
kc.pubKeysLock.Lock()
|
||||||
|
p, ok := kc.pubKeys[key]
|
||||||
|
if ok {
|
||||||
|
kc.pubKeysLock.Unlock()
|
||||||
|
return promise.Race(p, timeoutPromise).Await()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new promise, which resolves with a background context
|
||||||
|
p = promise.New(kc.getKeyFn(key))
|
||||||
|
p = promise.Catch(p, func(err error) error {
|
||||||
|
kc.pubKeysLock.Lock()
|
||||||
|
delete(kc.pubKeys, key)
|
||||||
|
kc.pubKeysLock.Unlock()
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
kc.pubKeys[key] = p
|
||||||
|
kc.pubKeysLock.Unlock()
|
||||||
|
|
||||||
|
return promise.Race(p, timeoutPromise).Await()
|
||||||
|
}
|
||||||
2
go.mod
2
go.mod
|
|
@ -13,6 +13,7 @@ require (
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/data/azappconfig v0.5.0
|
github.com/Azure/azure-sdk-for-go/sdk/data/azappconfig v0.5.0
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos v0.3.3
|
github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos v0.3.3
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/data/aztables v1.0.1
|
github.com/Azure/azure-sdk-for-go/sdk/data/aztables v1.0.1
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.9.0
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets v0.11.0
|
github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets v0.11.0
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs v0.5.0
|
github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs v0.5.0
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus v1.2.1
|
github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus v1.2.1
|
||||||
|
|
@ -42,6 +43,7 @@ require (
|
||||||
github.com/bradfitz/gomemcache v0.0.0-20230124162541-5f7a7d875746
|
github.com/bradfitz/gomemcache v0.0.0-20230124162541-5f7a7d875746
|
||||||
github.com/camunda/zeebe/clients/go/v8 v8.1.8
|
github.com/camunda/zeebe/clients/go/v8 v8.1.8
|
||||||
github.com/cenkalti/backoff/v4 v4.2.0
|
github.com/cenkalti/backoff/v4 v4.2.0
|
||||||
|
github.com/chebyrash/promise v0.0.0-20220530143319-1123826567d6
|
||||||
github.com/cinience/go_rocketmq v0.0.2
|
github.com/cinience/go_rocketmq v0.0.2
|
||||||
github.com/cloudevents/sdk-go/binding/format/protobuf/v2 v2.13.0
|
github.com/cloudevents/sdk-go/binding/format/protobuf/v2 v2.13.0
|
||||||
github.com/cloudevents/sdk-go/v2 v2.13.0
|
github.com/cloudevents/sdk-go/v2 v2.13.0
|
||||||
|
|
|
||||||
4
go.sum
4
go.sum
|
|
@ -431,6 +431,8 @@ github.com/Azure/azure-sdk-for-go/sdk/data/aztables v1.0.1/go.mod h1:l3wvZkG9oW0
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0/go.mod h1:yqy467j36fJxcRV2TzfVZ1pCb5vxm4BtZPUdYWe/Xo8=
|
github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0/go.mod h1:yqy467j36fJxcRV2TzfVZ1pCb5vxm4BtZPUdYWe/Xo8=
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.1.2 h1:+5VZ72z0Qan5Bog5C+ZkgSqUbeVUd9wgtHOrIKuc5b8=
|
github.com/Azure/azure-sdk-for-go/sdk/internal v1.1.2 h1:+5VZ72z0Qan5Bog5C+ZkgSqUbeVUd9wgtHOrIKuc5b8=
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.1.2/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w=
|
github.com/Azure/azure-sdk-for-go/sdk/internal v1.1.2/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w=
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.9.0 h1:TOFrNxfjslms5nLLIMjW7N0+zSALX4KiGsptmpb16AA=
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.9.0/go.mod h1:EAyXOW1F6BTJPiK2pDvmnvxOHPxoTYWoqBeIlql+QhI=
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets v0.11.0 h1:82w8tzLcOwDP/Q35j/wEBPt0n0kVC3cjtPdD62G8UAk=
|
github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets v0.11.0 h1:82w8tzLcOwDP/Q35j/wEBPt0n0kVC3cjtPdD62G8UAk=
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets v0.11.0/go.mod h1:S78i9yTr4o/nXlH76bKjGUye9Z2wSxO5Tz7GoDr4vfI=
|
github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets v0.11.0/go.mod h1:S78i9yTr4o/nXlH76bKjGUye9Z2wSxO5Tz7GoDr4vfI=
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.0 h1:Lg6BW0VPmCwcMlvOviL3ruHFO+H9tZNqscK0AeuFjGM=
|
github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.0 h1:Lg6BW0VPmCwcMlvOviL3ruHFO+H9tZNqscK0AeuFjGM=
|
||||||
|
|
@ -663,6 +665,8 @@ github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL
|
||||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
|
github.com/chebyrash/promise v0.0.0-20220530143319-1123826567d6 h1:AtcTeZIfucJjiqhIeMoOAR292ti2QOyo2aqN3SoWopo=
|
||||||
|
github.com/chebyrash/promise v0.0.0-20220530143319-1123826567d6/go.mod h1:4DRxP3p0R7/5msq1uKcI1THYmfWgFXxQqr0DutaIAEk=
|
||||||
github.com/chenzhuoyu/iasm v0.0.0-20220818063314-28c361dae733/go.mod h1:wOQ0nsbeOLa2awv8bUYFW/EHXbjQMlZ10fAlXDB2sz8=
|
github.com/chenzhuoyu/iasm v0.0.0-20220818063314-28c361dae733/go.mod h1:wOQ0nsbeOLa2awv8bUYFW/EHXbjQMlZ10fAlXDB2sz8=
|
||||||
github.com/chenzhuoyu/iasm v0.0.0-20230222070914-0b1b64b0e762 h1:4+00EOUb1t9uxAbgY8VvgfKJKDpim3co4MqsAbelIbs=
|
github.com/chenzhuoyu/iasm v0.0.0-20230222070914-0b1b64b0e762 h1:4+00EOUb1t9uxAbgY8VvgfKJKDpim3co4MqsAbelIbs=
|
||||||
github.com/chenzhuoyu/iasm v0.0.0-20230222070914-0b1b64b0e762/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
|
github.com/chenzhuoyu/iasm v0.0.0-20230222070914-0b1b64b0e762/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
apiVersion: dapr.io/v1alpha1
|
||||||
|
kind: Component
|
||||||
|
metadata:
|
||||||
|
name: azurekeyvault
|
||||||
|
spec:
|
||||||
|
type: crypto.azure.keyvault
|
||||||
|
metadata:
|
||||||
|
- name: vaultName
|
||||||
|
value: ${{AzureKeyVaultName}}
|
||||||
|
- name: azureTenantId
|
||||||
|
value: ${{AzureKeyVaultTenantId}}
|
||||||
|
- name: azureClientId
|
||||||
|
value: ${{AzureKeyVaultServicePrincipalClientId}}
|
||||||
|
- name: azureClientSecret
|
||||||
|
value: ${{AzureKeyVaultServicePrincipalClientSecret}}
|
||||||
|
|
@ -53,3 +53,19 @@ components:
|
||||||
- algorithms: ["A256CBC", "A256GCM", "A256KW", "C20P", "XC20P", "C20PKW", "XC20PKW", "A128CBC-HS256"]
|
- algorithms: ["A256CBC", "A256GCM", "A256KW", "C20P", "XC20P", "C20PKW", "XC20PKW", "A128CBC-HS256"]
|
||||||
type: symmetric
|
type: symmetric
|
||||||
name: symmetric-256
|
name: symmetric-256
|
||||||
|
- component: azure.keyvault
|
||||||
|
# Althoguh Azure Key Vault supports symmetric keys, those are only available in "Managed HSMs", which are too impractical for our tests
|
||||||
|
allOperations: false
|
||||||
|
operations: []
|
||||||
|
config:
|
||||||
|
keys:
|
||||||
|
- algorithms: ["ES256"]
|
||||||
|
type: private
|
||||||
|
name: ec256key
|
||||||
|
- algorithms: ["ES512"]
|
||||||
|
type: private
|
||||||
|
# "521" is not a typo
|
||||||
|
name: ec521key
|
||||||
|
- algorithms: ["PS256" , "PS384" , "PS512" , "RS256" , "RS384" , "RS512" , "RSA1_5" , "RSA-OAEP" , "RSA-OAEP-256"]
|
||||||
|
type: private
|
||||||
|
name: rsakey
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,7 @@ import (
|
||||||
b_rabbitmq "github.com/dapr/components-contrib/bindings/rabbitmq"
|
b_rabbitmq "github.com/dapr/components-contrib/bindings/rabbitmq"
|
||||||
b_redis "github.com/dapr/components-contrib/bindings/redis"
|
b_redis "github.com/dapr/components-contrib/bindings/redis"
|
||||||
c_redis "github.com/dapr/components-contrib/configuration/redis"
|
c_redis "github.com/dapr/components-contrib/configuration/redis"
|
||||||
|
cr_azurekeyvault "github.com/dapr/components-contrib/crypto/azure/keyvault"
|
||||||
cr_jwks "github.com/dapr/components-contrib/crypto/jwks"
|
cr_jwks "github.com/dapr/components-contrib/crypto/jwks"
|
||||||
cr_localstorage "github.com/dapr/components-contrib/crypto/localstorage"
|
cr_localstorage "github.com/dapr/components-contrib/crypto/localstorage"
|
||||||
p_snssqs "github.com/dapr/components-contrib/pubsub/aws/snssqs"
|
p_snssqs "github.com/dapr/components-contrib/pubsub/aws/snssqs"
|
||||||
|
|
@ -538,6 +539,8 @@ func loadSecretStore(tc TestComponent) secretstores.SecretStore {
|
||||||
func loadCryptoProvider(tc TestComponent) contribCrypto.SubtleCrypto {
|
func loadCryptoProvider(tc TestComponent) contribCrypto.SubtleCrypto {
|
||||||
var component contribCrypto.SubtleCrypto
|
var component contribCrypto.SubtleCrypto
|
||||||
switch tc.Component {
|
switch tc.Component {
|
||||||
|
case "azure.keyvault":
|
||||||
|
component = cr_azurekeyvault.NewAzureKeyvaultCrypto(testLogger)
|
||||||
case "localstorage":
|
case "localstorage":
|
||||||
component = cr_localstorage.NewLocalStorageCrypto(testLogger)
|
component = cr_localstorage.NewLocalStorageCrypto(testLogger)
|
||||||
case "jwks":
|
case "jwks":
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue