|
@@ -14,6 +14,8 @@ See the License for the specific language governing permissions and
|
|
|
limitations under the License.
|
|
limitations under the License.
|
|
|
*/
|
|
*/
|
|
|
|
|
|
|
|
|
|
+// Package keyvault implements a provider for Azure Key Vault secrets, allowing
|
|
|
|
|
+// External Secrets to read from and write to Azure Key Vault.
|
|
|
package keyvault
|
|
package keyvault
|
|
|
|
|
|
|
|
import (
|
|
import (
|
|
@@ -66,14 +68,21 @@ import (
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
const (
|
|
|
- defaultObjType = "secret"
|
|
|
|
|
- objectTypeCert = "cert"
|
|
|
|
|
- objectTypeKey = "key"
|
|
|
|
|
|
|
+ defaultObjType = "secret"
|
|
|
|
|
+ objectTypeCert = "cert"
|
|
|
|
|
+ objectTypeKey = "key"
|
|
|
|
|
+
|
|
|
|
|
+ // AzureDefaultAudience is the default audience used for Azure AD token exchange.
|
|
|
AzureDefaultAudience = "api://AzureADTokenExchange"
|
|
AzureDefaultAudience = "api://AzureADTokenExchange"
|
|
|
- AnnotationClientID = "azure.workload.identity/client-id"
|
|
|
|
|
- AnnotationTenantID = "azure.workload.identity/tenant-id"
|
|
|
|
|
- managerLabel = "external-secrets"
|
|
|
|
|
- managedBy = "managed-by"
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // AnnotationClientID is the annotation key for Azure Workload Identity client ID.
|
|
|
|
|
+ AnnotationClientID = "azure.workload.identity/client-id"
|
|
|
|
|
+
|
|
|
|
|
+ // AnnotationTenantID is the annotation key for Azure Workload Identity tenant ID.
|
|
|
|
|
+ AnnotationTenantID = "azure.workload.identity/tenant-id"
|
|
|
|
|
+
|
|
|
|
|
+ managerLabel = "external-secrets"
|
|
|
|
|
+ managedBy = "managed-by"
|
|
|
|
|
|
|
|
errUnexpectedStoreSpec = "unexpected store spec"
|
|
errUnexpectedStoreSpec = "unexpected store spec"
|
|
|
errMissingAuthType = "cannot initialize Azure Client: no valid authType was specified"
|
|
errMissingAuthType = "cannot initialize Azure Client: no valid authType was specified"
|
|
@@ -107,7 +116,7 @@ const (
|
|
|
var _ esv1.SecretsClient = &Azure{}
|
|
var _ esv1.SecretsClient = &Azure{}
|
|
|
var _ esv1.Provider = &Azure{}
|
|
var _ esv1.Provider = &Azure{}
|
|
|
|
|
|
|
|
-// interface to keyvault.BaseClient.
|
|
|
|
|
|
|
+// SecretClient is an interface to keyvault.BaseClient.
|
|
|
type SecretClient interface {
|
|
type SecretClient interface {
|
|
|
GetKey(ctx context.Context, vaultBaseURL string, keyName string, keyVersion string) (result keyvault.KeyBundle, err error)
|
|
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)
|
|
GetSecret(ctx context.Context, vaultBaseURL string, secretName string, secretVersion string) (result keyvault.SecretBundle, err error)
|
|
@@ -121,6 +130,7 @@ type SecretClient interface {
|
|
|
DeleteSecret(ctx context.Context, vaultBaseURL string, secretName string) (result keyvault.DeletedSecretBundle, err error)
|
|
DeleteSecret(ctx context.Context, vaultBaseURL string, secretName string) (result keyvault.DeletedSecretBundle, err error)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// Azure implements the External Secrets provider for Azure Key Vault.
|
|
|
type Azure struct {
|
|
type Azure struct {
|
|
|
crClient client.Client
|
|
crClient client.Client
|
|
|
kubeClient kcorev1.CoreV1Interface
|
|
kubeClient kcorev1.CoreV1Interface
|
|
@@ -137,6 +147,8 @@ type Azure struct {
|
|
|
certsClient *azcertificates.Client
|
|
certsClient *azcertificates.Client
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// PushSecretMetadataSpec defines metadata for pushing secrets to Azure Key Vault,
|
|
|
|
|
+// including expiration date and tags.
|
|
|
type PushSecretMetadataSpec struct {
|
|
type PushSecretMetadataSpec struct {
|
|
|
ExpirationDate string `json:"expirationDate,omitempty"`
|
|
ExpirationDate string `json:"expirationDate,omitempty"`
|
|
|
Tags map[string]string `json:"tags,omitempty"`
|
|
Tags map[string]string `json:"tags,omitempty"`
|
|
@@ -289,6 +301,7 @@ func getProvider(store esv1.GenericStore) (*esv1.AzureKVProvider, error) {
|
|
|
return spc.Provider.AzureKV, nil
|
|
return spc.Provider.AzureKV, nil
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// ValidateStore validates the Azure Key Vault provider configuration.
|
|
|
func (a *Azure) ValidateStore(store esv1.GenericStore) (admission.Warnings, error) {
|
|
func (a *Azure) ValidateStore(store esv1.GenericStore) (admission.Warnings, error) {
|
|
|
if store == nil {
|
|
if store == nil {
|
|
|
return nil, errors.New(errInvalidStore)
|
|
return nil, errors.New(errInvalidStore)
|
|
@@ -414,6 +427,7 @@ func (a *Azure) deleteKeyVaultCertificate(ctx context.Context, certName string)
|
|
|
return nil
|
|
return nil
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// DeleteSecret deletes a secret from Azure Key Vault.
|
|
|
func (a *Azure) DeleteSecret(ctx context.Context, remoteRef esv1.PushSecretRemoteRef) error {
|
|
func (a *Azure) DeleteSecret(ctx context.Context, remoteRef esv1.PushSecretRemoteRef) error {
|
|
|
objectType, secretName := getObjType(esv1.ExternalSecretDataRemoteRef{Key: remoteRef.GetRemoteKey()})
|
|
objectType, secretName := getObjType(esv1.ExternalSecretDataRemoteRef{Key: remoteRef.GetRemoteKey()})
|
|
|
switch objectType {
|
|
switch objectType {
|
|
@@ -437,6 +451,7 @@ func (a *Azure) DeleteSecret(ctx context.Context, remoteRef esv1.PushSecretRemot
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// SecretExists checks if a secret exists in Azure Key Vault.
|
|
|
func (a *Azure) SecretExists(ctx context.Context, remoteRef esv1.PushSecretRemoteRef) (bool, error) {
|
|
func (a *Azure) SecretExists(ctx context.Context, remoteRef esv1.PushSecretRemoteRef) (bool, error) {
|
|
|
if a.useNewSDK() {
|
|
if a.useNewSDK() {
|
|
|
return a.secretExistsWithNewSDK(ctx, remoteRef)
|
|
return a.secretExistsWithNewSDK(ctx, remoteRef)
|
|
@@ -758,7 +773,7 @@ func (a *Azure) PushSecret(ctx context.Context, secret *corev1.Secret, data esv1
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// Implements store.Client.GetAllSecrets Interface.
|
|
|
|
|
|
|
+// GetAllSecrets implements store.Client.GetAllSecrets Interface.
|
|
|
// Retrieves a map[string][]byte with the secret names as key and the secret itself as the calue.
|
|
// Retrieves a map[string][]byte with the secret names as key and the secret itself as the calue.
|
|
|
func (a *Azure) GetAllSecrets(ctx context.Context, ref esv1.ExternalSecretFind) (map[string][]byte, error) {
|
|
func (a *Azure) GetAllSecrets(ctx context.Context, ref esv1.ExternalSecretFind) (map[string][]byte, error) {
|
|
|
if a.useNewSDK() {
|
|
if a.useNewSDK() {
|
|
@@ -912,8 +927,9 @@ func (a *Azure) getSecretTagsWithLegacySDK(ctx context.Context, ref esv1.Externa
|
|
|
return secretTagsData, nil
|
|
return secretTagsData, nil
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// Implements store.Client.GetSecretMap Interface.
|
|
|
|
|
-// New version of GetSecretMap.
|
|
|
|
|
|
|
+// GetSecretMap returns a map of secret values from Azure KeyVault by fetching the secret with
|
|
|
|
|
+// the given name and parsing it as a JSON object. If MetadataPolicy is set to Fetch, it will
|
|
|
|
|
+// return the secret tags instead.
|
|
|
func (a *Azure) GetSecretMap(ctx context.Context, ref esv1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
|
|
func (a *Azure) GetSecretMap(ctx context.Context, ref esv1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
|
|
|
objectType, secretName := getObjType(ref)
|
|
objectType, secretName := getObjType(ref)
|
|
|
|
|
|
|
@@ -1087,10 +1103,12 @@ func (a *Azure) authorizerForWorkloadIdentity(ctx context.Context, tokenProvider
|
|
|
if len(a.provider.ServiceAccountRef.Audiences) > 0 {
|
|
if len(a.provider.ServiceAccountRef.Audiences) > 0 {
|
|
|
audiences = append(audiences, a.provider.ServiceAccountRef.Audiences...)
|
|
audiences = append(audiences, a.provider.ServiceAccountRef.Audiences...)
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
token, err := FetchSAToken(ctx, ns, a.provider.ServiceAccountRef.Name, audiences, a.kubeClient)
|
|
token, err := FetchSAToken(ctx, ns, a.provider.ServiceAccountRef.Name, audiences, a.kubeClient)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
return nil, err
|
|
return nil, err
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
tp, err := tokenProvider(ctx, token, clientID, tenantID, aadEndpoint, kvResource)
|
|
tp, err := tokenProvider(ctx, token, clientID, tenantID, aadEndpoint, kvResource)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
return nil, err
|
|
return nil, err
|
|
@@ -1098,6 +1116,9 @@ func (a *Azure) authorizerForWorkloadIdentity(ctx context.Context, tokenProvider
|
|
|
return autorest.NewBearerAuthorizer(tp), nil
|
|
return autorest.NewBearerAuthorizer(tp), nil
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// FetchSAToken retrieves a service account token from Kubernetes with the specified audiences.
|
|
|
|
|
+// It takes the service account namespace, name, audience list, and Kubernetes client interface.
|
|
|
|
|
+// Returns the token string or an error if the token creation fails.
|
|
|
func FetchSAToken(ctx context.Context, ns, name string, audiences []string, kubeClient kcorev1.CoreV1Interface) (string, error) {
|
|
func FetchSAToken(ctx context.Context, ns, name string, audiences []string, kubeClient kcorev1.CoreV1Interface) (string, error) {
|
|
|
token, err := kubeClient.ServiceAccounts(ns).CreateToken(ctx, name, &authv1.TokenRequest{
|
|
token, err := kubeClient.ServiceAccounts(ns).CreateToken(ctx, name, &authv1.TokenRequest{
|
|
|
Spec: authv1.TokenRequestSpec{
|
|
Spec: authv1.TokenRequestSpec{
|
|
@@ -1117,11 +1138,12 @@ type tokenProvider struct {
|
|
|
|
|
|
|
|
type tokenProviderFunc func(ctx context.Context, token, clientID, tenantID, aadEndpoint, kvResource string) (adal.OAuthTokenProvider, error)
|
|
type tokenProviderFunc func(ctx context.Context, token, clientID, tenantID, aadEndpoint, kvResource string) (adal.OAuthTokenProvider, error)
|
|
|
|
|
|
|
|
|
|
+// NewTokenProvider creates a new Azure OAuth token provider for authentication.
|
|
|
func NewTokenProvider(ctx context.Context, token, clientID, tenantID, aadEndpoint, kvResource string) (adal.OAuthTokenProvider, error) {
|
|
func NewTokenProvider(ctx context.Context, token, clientID, tenantID, aadEndpoint, kvResource string) (adal.OAuthTokenProvider, error) {
|
|
|
- // exchange token with Azure AccessToken
|
|
|
|
|
- cred := confidential.NewCredFromAssertionCallback(func(ctx context.Context, aro confidential.AssertionRequestOptions) (string, error) {
|
|
|
|
|
|
|
+ cred := confidential.NewCredFromAssertionCallback(func(_ context.Context, _ confidential.AssertionRequestOptions) (string, error) {
|
|
|
return token, nil
|
|
return token, nil
|
|
|
})
|
|
})
|
|
|
|
|
+
|
|
|
cClient, err := confidential.New(fmt.Sprintf("%s%s", aadEndpoint, tenantID), clientID, cred)
|
|
cClient, err := confidential.New(fmt.Sprintf("%s%s", aadEndpoint, tenantID), clientID, cred)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
return nil, err
|
|
return nil, err
|
|
@@ -1202,25 +1224,25 @@ func (a *Azure) getAuthorizerFromCredentials(ctx context.Context) (autorest.Auth
|
|
|
*a.provider.TenantID,
|
|
*a.provider.TenantID,
|
|
|
a.provider.EnvironmentType,
|
|
a.provider.EnvironmentType,
|
|
|
)
|
|
)
|
|
|
- } else {
|
|
|
|
|
- clientCertificate, err := resolvers.SecretKeyRef(
|
|
|
|
|
- ctx,
|
|
|
|
|
- a.crClient,
|
|
|
|
|
- a.store.GetKind(),
|
|
|
|
|
- a.namespace, a.provider.AuthSecretRef.ClientCertificate,
|
|
|
|
|
- )
|
|
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- return nil, err
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ clientCertificate, err := resolvers.SecretKeyRef(
|
|
|
|
|
+ ctx,
|
|
|
|
|
+ a.crClient,
|
|
|
|
|
+ a.store.GetKind(),
|
|
|
|
|
+ a.namespace, a.provider.AuthSecretRef.ClientCertificate,
|
|
|
|
|
+ )
|
|
|
|
|
|
|
|
- return getAuthorizerForClientCertificate(
|
|
|
|
|
- clientID,
|
|
|
|
|
- []byte(clientCertificate),
|
|
|
|
|
- *a.provider.TenantID,
|
|
|
|
|
- a.provider.EnvironmentType,
|
|
|
|
|
- )
|
|
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return nil, err
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ return getAuthorizerForClientCertificate(
|
|
|
|
|
+ clientID,
|
|
|
|
|
+ []byte(clientCertificate),
|
|
|
|
|
+ *a.provider.TenantID,
|
|
|
|
|
+ a.provider.EnvironmentType,
|
|
|
|
|
+ )
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
func getAuthorizerForClientSecret(clientID, clientSecret, tenantID string, environmentType esv1.AzureEnvironmentType) (autorest.Authorizer, error) {
|
|
func getAuthorizerForClientSecret(clientID, clientSecret, tenantID string, environmentType esv1.AzureEnvironmentType) (autorest.Authorizer, error) {
|
|
@@ -1237,10 +1259,12 @@ func getAuthorizerForClientCertificate(clientID string, certificateBytes []byte,
|
|
|
return clientCertificateConfig.Authorizer()
|
|
return clientCertificateConfig.Authorizer()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// Close closes the Azure Key Vault provider.
|
|
|
func (a *Azure) Close(_ context.Context) error {
|
|
func (a *Azure) Close(_ context.Context) error {
|
|
|
return nil
|
|
return nil
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// Validate validates the Azure Key Vault provider configuration.
|
|
|
func (a *Azure) Validate() (esv1.ValidationResult, error) {
|
|
func (a *Azure) Validate() (esv1.ValidationResult, error) {
|
|
|
if a.store.GetKind() == esv1.ClusterSecretStoreKind && isReferentSpec(a.provider) {
|
|
if a.store.GetKind() == esv1.ClusterSecretStoreKind && isReferentSpec(a.provider) {
|
|
|
return esv1.ValidationResultUnknown, nil
|
|
return esv1.ValidationResultUnknown, nil
|
|
@@ -1263,6 +1287,7 @@ func isReferentSpec(prov *esv1.AzureKVProvider) bool {
|
|
|
return false
|
|
return false
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// AadEndpointForType returns the Azure Active Directory endpoint for the specified Azure environment type.
|
|
|
func AadEndpointForType(t esv1.AzureEnvironmentType) string {
|
|
func AadEndpointForType(t esv1.AzureEnvironmentType) string {
|
|
|
switch t {
|
|
switch t {
|
|
|
case esv1.AzureEnvironmentPublicCloud:
|
|
case esv1.AzureEnvironmentPublicCloud:
|
|
@@ -1282,6 +1307,7 @@ func AadEndpointForType(t esv1.AzureEnvironmentType) string {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// ServiceManagementEndpointForType returns the service management endpoint for the specified Azure environment type.
|
|
|
func ServiceManagementEndpointForType(t esv1.AzureEnvironmentType) string {
|
|
func ServiceManagementEndpointForType(t esv1.AzureEnvironmentType) string {
|
|
|
switch t {
|
|
switch t {
|
|
|
case esv1.AzureEnvironmentPublicCloud:
|
|
case esv1.AzureEnvironmentPublicCloud:
|