azure: support aad-pod-identity auth when using restic (#2602)

Signed-off-by: Steve Kriss <krisss@vmware.com>
This commit is contained in:
Steve Kriss
2020-06-08 12:17:43 -06:00
committed by GitHub
parent 13afbf39a1
commit a5346c1a87
4 changed files with 60 additions and 43 deletions

View File

@@ -1,5 +1,5 @@
/*
Copyright 2017, 2019 the Velero contributors.
Copyright 2017, 2019, 2020 the Velero contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -21,42 +21,48 @@ import (
"os"
"strings"
storagemgmt "github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2018-02-01/storage"
"github.com/Azure/go-autorest/autorest"
"github.com/Azure/go-autorest/autorest/adal"
storagemgmt "github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2019-06-01/storage"
"github.com/Azure/go-autorest/autorest/azure"
"github.com/Azure/go-autorest/autorest/azure/auth"
"github.com/joho/godotenv"
"github.com/pkg/errors"
)
const (
tenantIDEnvVar = "AZURE_TENANT_ID"
subscriptionIDEnvVar = "AZURE_SUBSCRIPTION_ID"
clientIDEnvVar = "AZURE_CLIENT_ID"
clientSecretEnvVar = "AZURE_CLIENT_SECRET"
cloudNameEnvVar = "AZURE_CLOUD_NAME"
resourceGroupConfigKey = "resourceGroup"
storageAccountConfigKey = "storageAccount"
storageAccountKeyEnvVarConfigKey = "storageAccountKeyEnvVar"
subscriptionIdConfigKey = "subscriptionId"
subscriptionIDConfigKey = "subscriptionId"
)
// getSubscriptionID gets the subscription ID from the 'config' map if it contains
// it, else from the AZURE_SUBSCRIPTION_ID environment variable.
func getSubscriptionID(config map[string]string) string {
if subscriptionID := config[subscriptionIDConfigKey]; subscriptionID != "" {
return subscriptionID
}
return os.Getenv(subscriptionIDEnvVar)
}
func getStorageAccountKey(config map[string]string) (string, *azure.Environment, error) {
// load environment vars from $AZURE_CREDENTIALS_FILE, if it exists
if err := loadEnv(); err != nil {
return "", nil, err
}
// 1. Get Azure cloud from AZURE_CLOUD_NAME, if it exists. If the env var does not
// Get Azure cloud from AZURE_CLOUD_NAME, if it exists. If the env var does not
// exist, parseAzureEnvironment will return azure.PublicCloud.
env, err := parseAzureEnvironment(os.Getenv(cloudNameEnvVar))
if err != nil {
return "", nil, errors.Wrap(err, "unable to parse azure cloud name environment variable")
}
// 2. get storage key from secret using key config[storageAccountKeyEnvVarConfigKey]. If the config does not
// Get storage key from secret using key config[storageAccountKeyEnvVarConfigKey]. If the config does not
// exist, continue obtaining it using API
if secretKeyEnvVar := config[storageAccountKeyEnvVarConfigKey]; secretKeyEnvVar != "" {
storageKey := os.Getenv(secretKeyEnvVar)
@@ -67,35 +73,33 @@ func getStorageAccountKey(config map[string]string) (string, *azure.Environment,
return storageKey, env, nil
}
// 3. we need AZURE_TENANT_ID, AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, AZURE_SUBSCRIPTION_ID
envVars, err := getRequiredValues(os.Getenv, tenantIDEnvVar, clientIDEnvVar, clientSecretEnvVar, subscriptionIDEnvVar)
if err != nil {
return "", nil, errors.Wrap(err, "unable to get all required environment variables")
// get subscription ID from object store config or AZURE_SUBSCRIPTION_ID environment variable
subscriptionID := getSubscriptionID(config)
if subscriptionID == "" {
return "", nil, errors.New("azure subscription ID not found in object store's config or in environment variable")
}
// 4. check whether a different subscription ID was set for backups in config["subscriptionId"]
subscriptionId := envVars[subscriptionIDEnvVar]
if val := config[subscriptionIdConfigKey]; val != "" {
subscriptionId = val
}
// 5. we need config["resourceGroup"], config["storageAccount"]
// we need config["resourceGroup"], config["storageAccount"]
if _, err := getRequiredValues(mapLookup(config), resourceGroupConfigKey, storageAccountConfigKey); err != nil {
return "", env, errors.Wrap(err, "unable to get all required config values")
}
// 6. get SPT
spt, err := newServicePrincipalToken(envVars[tenantIDEnvVar], envVars[clientIDEnvVar], envVars[clientSecretEnvVar], env)
// get authorizer from environment in the following order:
// 1. client credentials (AZURE_TENANT_ID, AZURE_CLIENT_ID, AZURE_CLIENT_SECRET)
// 2. client certificate (AZURE_CERTIFICATE_PATH, AZURE_CERTIFICATE_PASSWORD)
// 3. username and password (AZURE_USERNAME, AZURE_PASSWORD)
// 4. MSI (managed service identity)
authorizer, err := auth.NewAuthorizerFromEnvironment()
if err != nil {
return "", env, errors.Wrap(err, "error getting service principal token")
return "", nil, errors.Wrap(err, "error getting authorizer from environment")
}
// 7. get storageAccountsClient
storageAccountsClient := storagemgmt.NewAccountsClientWithBaseURI(env.ResourceManagerEndpoint, subscriptionId)
storageAccountsClient.Authorizer = autorest.NewBearerAuthorizer(spt)
// get storageAccountsClient
storageAccountsClient := storagemgmt.NewAccountsClientWithBaseURI(env.ResourceManagerEndpoint, subscriptionID)
storageAccountsClient.Authorizer = authorizer
// 8. get storage key
res, err := storageAccountsClient.ListKeys(context.TODO(), config[resourceGroupConfigKey], config[storageAccountConfigKey])
// get storage key
res, err := storageAccountsClient.ListKeys(context.TODO(), config[resourceGroupConfigKey], config[storageAccountConfigKey], storagemgmt.Kerb)
if err != nil {
return "", env, errors.WithStack(err)
}
@@ -169,15 +173,6 @@ func parseAzureEnvironment(cloudName string) (*azure.Environment, error) {
return &env, errors.WithStack(err)
}
func newServicePrincipalToken(tenantID, clientID, clientSecret string, env *azure.Environment) (*adal.ServicePrincipalToken, error) {
oauthConfig, err := adal.NewOAuthConfig(env.ActiveDirectoryEndpoint, tenantID)
if err != nil {
return nil, errors.Wrap(err, "error getting OAuthConfig")
}
return adal.NewServicePrincipalToken(*oauthConfig, clientID, clientSecret, env.ResourceManagerEndpoint)
}
func getRequiredValues(getValue func(string) string, keys ...string) (map[string]string, error) {
missing := []string{}
results := map[string]string{}