azure_cloud_id.go 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. /*
  2. Copyright © The ESO Authors
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. https://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package akeyless
  14. import (
  15. "context"
  16. "encoding/base64"
  17. "errors"
  18. "fmt"
  19. "os"
  20. "strings"
  21. "github.com/Azure/azure-sdk-for-go/sdk/azcore"
  22. "github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud"
  23. "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
  24. "github.com/Azure/azure-sdk-for-go/sdk/azidentity"
  25. azure_cloud_id "github.com/akeylesslabs/akeyless-go-cloud-id/cloudprovider/azure"
  26. corev1 "k8s.io/api/core/v1"
  27. "k8s.io/apimachinery/pkg/types"
  28. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  29. esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
  30. )
  31. const (
  32. azureDefaultAudience = "api://AzureADTokenExchange"
  33. annotationClientID = "azure.workload.identity/client-id"
  34. annotationTenantID = "azure.workload.identity/tenant-id"
  35. serviceAccountTokenExpirationSeconds int64 = 600
  36. errMissingAzureClientID = "missing Azure client ID: set accessTypeParam or annotate the service account with %s"
  37. errMissingAzureTenantID = "missing Azure tenant ID: annotate the service account with %s or set AZURE_TENANT_ID"
  38. )
  39. func (a *akeylessBase) getAzureCloudID(ctx context.Context, accTypeParam string, auth *esv1.AkeylessAuth) (string, error) {
  40. if auth == nil || auth.ServiceAccountRef == nil {
  41. cloudID, err := azure_cloud_id.GetCloudId(accTypeParam)
  42. if err != nil {
  43. return "", err
  44. }
  45. return cloudID, nil
  46. }
  47. return a.getAzureCloudIDFromServiceAccount(ctx, auth.ServiceAccountRef, accTypeParam)
  48. }
  49. func (a *akeylessBase) getAzureCloudIDFromServiceAccount(ctx context.Context, ref *esmeta.ServiceAccountSelector, accTypeParam string) (string, error) {
  50. if ref == nil {
  51. return "", errors.New("serviceAccountRef is required")
  52. }
  53. ns := a.namespace
  54. if a.storeKind == esv1.ClusterSecretStoreKind && ref.Namespace != nil {
  55. ns = *ref.Namespace
  56. }
  57. sa := &corev1.ServiceAccount{}
  58. if err := a.kube.Get(ctx, types.NamespacedName{Name: ref.Name, Namespace: ns}, sa); err != nil {
  59. return "", fmt.Errorf("failed to get service account %q: %w", ref.Name, err)
  60. }
  61. clientID, err := azureClientID(sa, accTypeParam)
  62. if err != nil {
  63. return "", err
  64. }
  65. tenantID, err := azureTenantID(sa)
  66. if err != nil {
  67. return "", err
  68. }
  69. getAssertion := func(ctx context.Context) (string, error) {
  70. return a.getJWTfromServiceAccountToken(ctx, *ref, []string{azureDefaultAudience}, serviceAccountTokenExpirationSeconds)
  71. }
  72. azureCloud := azureCloudSettingsFromEnv()
  73. cred, err := azidentity.NewClientAssertionCredential(tenantID, clientID, getAssertion, &azidentity.ClientAssertionCredentialOptions{
  74. ClientOptions: azcore.ClientOptions{Cloud: azureCloud.cloud},
  75. })
  76. if err != nil {
  77. return "", fmt.Errorf("failed to create Azure client assertion credential: %w", err)
  78. }
  79. accessToken, err := cred.GetToken(ctx, policy.TokenRequestOptions{Scopes: []string{azureCloud.scope}})
  80. if err != nil {
  81. return "", fmt.Errorf("failed to get Azure access token: %w", err)
  82. }
  83. // akeyless-go-cloud-id GetCloudId returns a base64-encoded access token (see
  84. // cloudprovider/azure/cloud_id.go); keep the same format for Workload Identity.
  85. return base64.StdEncoding.EncodeToString([]byte(accessToken.Token)), nil
  86. }
  87. func azureClientID(sa *corev1.ServiceAccount, accTypeParam string) (string, error) {
  88. if sa != nil {
  89. if val, ok := sa.Annotations[annotationClientID]; ok && val != "" {
  90. return val, nil
  91. }
  92. }
  93. if accTypeParam != "" {
  94. return accTypeParam, nil
  95. }
  96. return "", fmt.Errorf(errMissingAzureClientID, annotationClientID)
  97. }
  98. type azureCloudSettings struct {
  99. scope string
  100. cloud cloud.Configuration
  101. }
  102. var (
  103. publicAzureCloudSettings = azureCloudSettings{
  104. scope: azure_cloud_id.AzureADManagementScope,
  105. cloud: cloud.AzurePublic,
  106. }
  107. usGovAzureCloudSettings = azureCloudSettings{
  108. scope: "https://management.usgovcloudapi.net/.default",
  109. cloud: cloud.AzureGovernment,
  110. }
  111. chinaAzureCloudSettings = azureCloudSettings{
  112. scope: "https://management.chinacloudapi.cn/.default",
  113. cloud: cloud.AzureChina,
  114. }
  115. )
  116. func azureCloudSettingsFromEnv() azureCloudSettings {
  117. for _, key := range []string{"AZURE_ENVIRONMENT", "AZURE_CLOUD"} {
  118. if v, ok := os.LookupEnv(key); ok {
  119. if cfg, ok := azureCloudSettingsFromName(v); ok {
  120. return cfg
  121. }
  122. }
  123. }
  124. return publicAzureCloudSettings
  125. }
  126. func azureCloudSettingsFromName(raw string) (azureCloudSettings, bool) {
  127. switch strings.ToLower(strings.TrimSpace(raw)) {
  128. case "azurecloud", "azurepubliccloud":
  129. return publicAzureCloudSettings, true
  130. case "azureusgovernment", "azureusgovernmentcloud":
  131. return usGovAzureCloudSettings, true
  132. case "azurechinacloud", "azurechinacloud21vianet":
  133. return chinaAzureCloudSettings, true
  134. default:
  135. return azureCloudSettings{}, false
  136. }
  137. }
  138. func azureTenantID(sa *corev1.ServiceAccount) (string, error) {
  139. if sa != nil {
  140. if val, ok := sa.Annotations[annotationTenantID]; ok && val != "" {
  141. return val, nil
  142. }
  143. }
  144. if tenantID := os.Getenv("AZURE_TENANT_ID"); tenantID != "" {
  145. return tenantID, nil
  146. }
  147. return "", fmt.Errorf(errMissingAzureTenantID, annotationTenantID)
  148. }