Browse Source

feat: add azkv.environmentType (#1469)

users of USGovCloud, ChinaCloud, GermanCloud need slightly different
configuration for AADEndpoint and keyvault resource.

This is based on CSI Secret Store Azure KV driver,

Signed-off-by: Moritz Johner <beller.moritz@googlemail.com>

Signed-off-by: Moritz Johner <beller.moritz@googlemail.com>
Moritz Johner 3 years ago
parent
commit
2d20b5488e

+ 21 - 0
apis/externalsecrets/v1beta1/secretstore_azurekv_types.go

@@ -34,6 +34,20 @@ const (
 	AzureWorkloadIdentity AzureAuthType = "WorkloadIdentity"
 	AzureWorkloadIdentity AzureAuthType = "WorkloadIdentity"
 )
 )
 
 
+// AzureEnvironmentType specifies the Azure cloud environment endpoints to use for
+// connecting and authenticating with Azure. By default it points to the public cloud AAD endpoint.
+// The following endpoints are available, also see here: https://github.com/Azure/go-autorest/blob/main/autorest/azure/environments.go#L152
+// PublicCloud, USGovernmentCloud, ChinaCloud, GermanCloud
+// +kubebuilder:validation:Enum=PublicCloud;USGovernmentCloud;ChinaCloud;GermanCloud
+type AzureEnvironmentType string
+
+const (
+	AzureEnvironmentPublicCloud       AzureEnvironmentType = "PublicCloud"
+	AzureEnvironmentUSGovernmentCloud AzureEnvironmentType = "USGovernmentCloud"
+	AzureEnvironmentChinaCloud        AzureEnvironmentType = "ChinaCloud"
+	AzureEnvironmentGermanCloud       AzureEnvironmentType = "GermanCloud"
+)
+
 // Configures an store to sync secrets using Azure KV.
 // Configures an store to sync secrets using Azure KV.
 type AzureKVProvider struct {
 type AzureKVProvider struct {
 	// Auth type defines how to authenticate to the keyvault service.
 	// Auth type defines how to authenticate to the keyvault service.
@@ -51,6 +65,13 @@ type AzureKVProvider struct {
 	// +optional
 	// +optional
 	TenantID *string `json:"tenantId,omitempty"`
 	TenantID *string `json:"tenantId,omitempty"`
 
 
+	// EnvironmentType specifies the Azure cloud environment endpoints to use for
+	// connecting and authenticating with Azure. By default it points to the public cloud AAD endpoint.
+	// The following endpoints are available, also see here: https://github.com/Azure/go-autorest/blob/main/autorest/azure/environments.go#L152
+	// PublicCloud, USGovernmentCloud, ChinaCloud, GermanCloud
+	// +kubebuilder:default=PublicCloud
+	EnvironmentType AzureEnvironmentType `json:"environmentType,omitempty"`
+
 	// Auth configures how the operator authenticates with Azure. Required for ServicePrincipal auth type.
 	// Auth configures how the operator authenticates with Azure. Required for ServicePrincipal auth type.
 	// +optional
 	// +optional
 	AuthSecretRef *AzureKVAuth `json:"authSecretRef,omitempty"`
 	AuthSecretRef *AzureKVAuth `json:"authSecretRef,omitempty"`

+ 13 - 0
config/crds/bases/external-secrets.io_clustersecretstores.yaml

@@ -1734,6 +1734,19 @@ spec:
                         - ManagedIdentity
                         - ManagedIdentity
                         - WorkloadIdentity
                         - WorkloadIdentity
                         type: string
                         type: string
+                      environmentType:
+                        default: PublicCloud
+                        description: 'EnvironmentType specifies the Azure cloud environment
+                          endpoints to use for connecting and authenticating with
+                          Azure. By default it points to the public cloud AAD endpoint.
+                          The following endpoints are available, also see here: https://github.com/Azure/go-autorest/blob/main/autorest/azure/environments.go#L152
+                          PublicCloud, USGovernmentCloud, ChinaCloud, GermanCloud'
+                        enum:
+                        - PublicCloud
+                        - USGovernmentCloud
+                        - ChinaCloud
+                        - GermanCloud
+                        type: string
                       identityId:
                       identityId:
                         description: If multiple Managed Identity is assigned to the
                         description: If multiple Managed Identity is assigned to the
                           pod, you can select the one to be used
                           pod, you can select the one to be used

+ 13 - 0
config/crds/bases/external-secrets.io_secretstores.yaml

@@ -1734,6 +1734,19 @@ spec:
                         - ManagedIdentity
                         - ManagedIdentity
                         - WorkloadIdentity
                         - WorkloadIdentity
                         type: string
                         type: string
+                      environmentType:
+                        default: PublicCloud
+                        description: 'EnvironmentType specifies the Azure cloud environment
+                          endpoints to use for connecting and authenticating with
+                          Azure. By default it points to the public cloud AAD endpoint.
+                          The following endpoints are available, also see here: https://github.com/Azure/go-autorest/blob/main/autorest/azure/environments.go#L152
+                          PublicCloud, USGovernmentCloud, ChinaCloud, GermanCloud'
+                        enum:
+                        - PublicCloud
+                        - USGovernmentCloud
+                        - ChinaCloud
+                        - GermanCloud
+                        type: string
                       identityId:
                       identityId:
                         description: If multiple Managed Identity is assigned to the
                         description: If multiple Managed Identity is assigned to the
                           pod, you can select the one to be used
                           pod, you can select the one to be used

+ 18 - 0
deploy/crds/bundle.yaml

@@ -1654,6 +1654,15 @@ spec:
                             - ManagedIdentity
                             - ManagedIdentity
                             - WorkloadIdentity
                             - WorkloadIdentity
                           type: string
                           type: string
+                        environmentType:
+                          default: PublicCloud
+                          description: 'EnvironmentType specifies the Azure cloud environment endpoints to use for connecting and authenticating with Azure. By default it points to the public cloud AAD endpoint. The following endpoints are available, also see here: https://github.com/Azure/go-autorest/blob/main/autorest/azure/environments.go#L152 PublicCloud, USGovernmentCloud, ChinaCloud, GermanCloud'
+                          enum:
+                            - PublicCloud
+                            - USGovernmentCloud
+                            - ChinaCloud
+                            - GermanCloud
+                          type: string
                         identityId:
                         identityId:
                           description: If multiple Managed Identity is assigned to the pod, you can select the one to be used
                           description: If multiple Managed Identity is assigned to the pod, you can select the one to be used
                           type: string
                           type: string
@@ -4374,6 +4383,15 @@ spec:
                             - ManagedIdentity
                             - ManagedIdentity
                             - WorkloadIdentity
                             - WorkloadIdentity
                           type: string
                           type: string
+                        environmentType:
+                          default: PublicCloud
+                          description: 'EnvironmentType specifies the Azure cloud environment endpoints to use for connecting and authenticating with Azure. By default it points to the public cloud AAD endpoint. The following endpoints are available, also see here: https://github.com/Azure/go-autorest/blob/main/autorest/azure/environments.go#L152 PublicCloud, USGovernmentCloud, ChinaCloud, GermanCloud'
+                          enum:
+                            - PublicCloud
+                            - USGovernmentCloud
+                            - ChinaCloud
+                            - GermanCloud
+                          type: string
                         identityId:
                         identityId:
                           description: If multiple Managed Identity is assigned to the pod, you can select the one to be used
                           description: If multiple Managed Identity is assigned to the pod, you can select the one to be used
                           type: string
                           type: string

+ 14 - 0
docs/provider-azure-key-vault.md

@@ -11,6 +11,20 @@ We support Service Principals, Managed Identity and Workload Identity authentica
 
 
 To use Managed Identity authentication, you should use [aad-pod-identity](https://azure.github.io/aad-pod-identity/docs/) to assign the identity to external-secrets operator. To add the selector to external-secrets operator, use `podLabels` in your values.yaml in case of Helm installation of external-secrets.
 To use Managed Identity authentication, you should use [aad-pod-identity](https://azure.github.io/aad-pod-identity/docs/) to assign the identity to external-secrets operator. To add the selector to external-secrets operator, use `podLabels` in your values.yaml in case of Helm installation of external-secrets.
 
 
+We support connecting to different cloud flavours azure supports: `PublicCloud`, `USGovernmentCloud`, `ChinaCloud` and `GermanCloud`. You have to specify the `environmentType` and point to the correct cloud flavour. This defaults to `PublicCloud`.
+
+```
+apiVersion: external-secrets.io/v1beta1
+kind: SecretStore
+metadata:
+  name: azure-backend
+spec:
+  provider:
+    azurekv:
+      # PublicCloud, USGovernmentCloud, ChinaCloud, GermanCloud
+      environmentType: PublicCloud # default
+```
+
 Minimum required permissions are `Get` over secret and certificate permissions. This can be done by adding a Key Vault access policy:
 Minimum required permissions are `Get` over secret and certificate permissions. This can be done by adding a Key Vault access policy:
 
 
 ```sh
 ```sh

+ 49 - 14
pkg/provider/azure/keyvault/keyvault.go

@@ -27,6 +27,7 @@ import (
 	"github.com/Azure/azure-sdk-for-go/profiles/latest/keyvault/keyvault"
 	"github.com/Azure/azure-sdk-for-go/profiles/latest/keyvault/keyvault"
 	"github.com/Azure/go-autorest/autorest"
 	"github.com/Azure/go-autorest/autorest"
 	"github.com/Azure/go-autorest/autorest/adal"
 	"github.com/Azure/go-autorest/autorest/adal"
+	"github.com/Azure/go-autorest/autorest/azure"
 	kvauth "github.com/Azure/go-autorest/autorest/azure/auth"
 	kvauth "github.com/Azure/go-autorest/autorest/azure/auth"
 	"github.com/AzureAD/microsoft-authentication-library-for-go/apps/confidential"
 	"github.com/AzureAD/microsoft-authentication-library-for-go/apps/confidential"
 	"github.com/tidwall/gjson"
 	"github.com/tidwall/gjson"
@@ -48,7 +49,6 @@ const (
 	defaultObjType       = "secret"
 	defaultObjType       = "secret"
 	objectTypeCert       = "cert"
 	objectTypeCert       = "cert"
 	objectTypeKey        = "key"
 	objectTypeKey        = "key"
-	vaultResource        = "https://vault.azure.net"
 	azureDefaultAudience = "api://AzureADTokenExchange"
 	azureDefaultAudience = "api://AzureADTokenExchange"
 	annotationClientID   = "azure.workload.identity/client-id"
 	annotationClientID   = "azure.workload.identity/client-id"
 	annotationTenantID   = "azure.workload.identity/tenant-id"
 	annotationTenantID   = "azure.workload.identity/tenant-id"
@@ -423,6 +423,8 @@ func getSecretMapProperties(tags map[string]*string, key, property string) map[s
 }
 }
 
 
 func (a *Azure) authorizerForWorkloadIdentity(ctx context.Context, tokenProvider tokenProviderFunc) (autorest.Authorizer, error) {
 func (a *Azure) authorizerForWorkloadIdentity(ctx context.Context, tokenProvider tokenProviderFunc) (autorest.Authorizer, error) {
+	aadEndpoint := aadEndpointForProviderConfig(a.provider)
+	kvResource := kvResourceForProviderConfig(a.provider)
 	// if no serviceAccountRef was provided
 	// if no serviceAccountRef was provided
 	// we expect certain env vars to be present.
 	// we expect certain env vars to be present.
 	// They are set by the azure workload identity webhook.
 	// They are set by the azure workload identity webhook.
@@ -437,7 +439,7 @@ func (a *Azure) authorizerForWorkloadIdentity(ctx context.Context, tokenProvider
 		if err != nil {
 		if err != nil {
 			return nil, fmt.Errorf(errReadTokenFile, tokenFilePath, err)
 			return nil, fmt.Errorf(errReadTokenFile, tokenFilePath, err)
 		}
 		}
-		tp, err := tokenProvider(ctx, string(token), clientID, tenantID)
+		tp, err := tokenProvider(ctx, string(token), clientID, tenantID, aadEndpoint, kvResource)
 		if err != nil {
 		if err != nil {
 			return nil, err
 			return nil, err
 		}
 		}
@@ -467,7 +469,7 @@ func (a *Azure) authorizerForWorkloadIdentity(ctx context.Context, tokenProvider
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
-	tp, err := tokenProvider(ctx, token, clientID, tenantID)
+	tp, err := tokenProvider(ctx, token, clientID, tenantID, aadEndpoint, kvResource)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
@@ -491,26 +493,27 @@ type tokenProvider struct {
 	accessToken string
 	accessToken string
 }
 }
 
 
-type tokenProviderFunc func(ctx context.Context, token, clientID, tenantID string) (adal.OAuthTokenProvider, error)
+type tokenProviderFunc func(ctx context.Context, token, clientID, tenantID, aadEndpoint, kvResource string) (adal.OAuthTokenProvider, error)
 
 
-func newTokenProvider(ctx context.Context, token, clientID, tenantID string) (adal.OAuthTokenProvider, error) {
+func newTokenProvider(ctx context.Context, token, clientID, tenantID, aadEndpoint, kvResource string) (adal.OAuthTokenProvider, error) {
 	// exchange token with Azure AccessToken
 	// exchange token with Azure AccessToken
 	cred, err := confidential.NewCredFromAssertion(token)
 	cred, err := confidential.NewCredFromAssertion(token)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
-
-	// AZURE_AUTHORITY_HOST
-
 	cClient, err := confidential.New(clientID, cred, confidential.WithAuthority(
 	cClient, err := confidential.New(clientID, cred, confidential.WithAuthority(
-		fmt.Sprintf("https://login.microsoftonline.com/%s/oauth2/token", tenantID),
+		fmt.Sprintf("%s%s/oauth2/token", aadEndpoint, tenantID),
 	))
 	))
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
-
+	scope := kvResource
+	// .default needs to be added to the scope
+	if !strings.Contains(kvResource, ".default") {
+		scope = fmt.Sprintf("%s/.default", kvResource)
+	}
 	authRes, err := cClient.AcquireTokenByCredential(ctx, []string{
 	authRes, err := cClient.AcquireTokenByCredential(ctx, []string{
-		"https://vault.azure.net/.default",
+		scope,
 	})
 	})
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
@@ -526,7 +529,7 @@ func (t *tokenProvider) OAuthToken() string {
 
 
 func (a *Azure) authorizerForManagedIdentity() (autorest.Authorizer, error) {
 func (a *Azure) authorizerForManagedIdentity() (autorest.Authorizer, error) {
 	msiConfig := kvauth.NewMSIConfig()
 	msiConfig := kvauth.NewMSIConfig()
-	msiConfig.Resource = vaultResource
+	msiConfig.Resource = kvResourceForProviderConfig(a.provider)
 	if a.provider.IdentityID != nil {
 	if a.provider.IdentityID != nil {
 		msiConfig.ClientID = *a.provider.IdentityID
 		msiConfig.ClientID = *a.provider.IdentityID
 	}
 	}
@@ -555,9 +558,9 @@ func (a *Azure) authorizerForServicePrincipal(ctx context.Context) (autorest.Aut
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
-
 	clientCredentialsConfig := kvauth.NewClientCredentialsConfig(cid, csec, *a.provider.TenantID)
 	clientCredentialsConfig := kvauth.NewClientCredentialsConfig(cid, csec, *a.provider.TenantID)
-	clientCredentialsConfig.Resource = vaultResource
+	clientCredentialsConfig.Resource = kvResourceForProviderConfig(a.provider)
+	clientCredentialsConfig.AADEndpoint = aadEndpointForProviderConfig(a.provider)
 	return clientCredentialsConfig.Authorizer()
 	return clientCredentialsConfig.Authorizer()
 }
 }
 
 
@@ -591,6 +594,38 @@ func (a *Azure) Validate() (esv1beta1.ValidationResult, error) {
 	return esv1beta1.ValidationResultReady, nil
 	return esv1beta1.ValidationResultReady, nil
 }
 }
 
 
+func aadEndpointForProviderConfig(prov *esv1beta1.AzureKVProvider) string {
+	switch prov.EnvironmentType {
+	case esv1beta1.AzureEnvironmentPublicCloud:
+		return azure.PublicCloud.ActiveDirectoryEndpoint
+	case esv1beta1.AzureEnvironmentChinaCloud:
+		return azure.ChinaCloud.ActiveDirectoryEndpoint
+	case esv1beta1.AzureEnvironmentUSGovernmentCloud:
+		return azure.USGovernmentCloud.ActiveDirectoryEndpoint
+	case esv1beta1.AzureEnvironmentGermanCloud:
+		return azure.GermanCloud.ActiveDirectoryEndpoint
+	default:
+		return azure.PublicCloud.ActiveDirectoryEndpoint
+	}
+}
+
+func kvResourceForProviderConfig(prov *esv1beta1.AzureKVProvider) string {
+	var res string
+	switch prov.EnvironmentType {
+	case esv1beta1.AzureEnvironmentPublicCloud:
+		res = azure.PublicCloud.KeyVaultEndpoint
+	case esv1beta1.AzureEnvironmentChinaCloud:
+		res = azure.ChinaCloud.KeyVaultEndpoint
+	case esv1beta1.AzureEnvironmentUSGovernmentCloud:
+		res = azure.USGovernmentCloud.KeyVaultEndpoint
+	case esv1beta1.AzureEnvironmentGermanCloud:
+		res = azure.GermanCloud.KeyVaultEndpoint
+	default:
+		res = azure.PublicCloud.KeyVaultEndpoint
+	}
+	return strings.TrimSuffix(res, "/")
+}
+
 func getObjType(ref esv1beta1.ExternalSecretDataRemoteRef) (string, string) {
 func getObjType(ref esv1beta1.ExternalSecretDataRemoteRef) (string, string) {
 	objectType := defaultObjType
 	objectType := defaultObjType
 
 

+ 1 - 1
pkg/provider/azure/keyvault/keyvault_auth_test.go

@@ -193,7 +193,7 @@ func TestGetAuthorizorForWorkloadIdentity(t *testing.T) {
 				kubeClient: awsauthfake.NewCreateTokenMock(saToken),
 				kubeClient: awsauthfake.NewCreateTokenMock(saToken),
 				provider:   store.Spec.Provider.AzureKV,
 				provider:   store.Spec.Provider.AzureKV,
 			}
 			}
-			tokenProvider := func(ctx context.Context, token, clientID, tenantID string) (adal.OAuthTokenProvider, error) {
+			tokenProvider := func(ctx context.Context, token, clientID, tenantID, aadEndpoint, kvResource string) (adal.OAuthTokenProvider, error) {
 				tassert.Equal(t, token, saToken)
 				tassert.Equal(t, token, saToken)
 				tassert.Equal(t, clientID, clientID)
 				tassert.Equal(t, clientID, clientID)
 				tassert.Equal(t, tenantID, tenantID)
 				tassert.Equal(t, tenantID, tenantID)