Last active
February 21, 2023 16:44
-
-
Save sebnyberg/19d02d5207f82e4076e9266af6783211 to your computer and use it in GitHub Desktop.
Some Azure Go SDK auth things
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package azurex | |
import ( | |
"context" | |
"errors" | |
"fmt" | |
"os" | |
"strings" | |
"github.com/Azure/azure-sdk-for-go/profiles/2020-09-01/resources/mgmt/subscriptions" | |
mgmtstorage "github.com/Azure/azure-sdk-for-go/profiles/2020-09-01/storage/mgmt/storage" | |
"github.com/Azure/azure-sdk-for-go/storage" | |
"github.com/Azure/go-autorest/autorest" | |
"github.com/Azure/go-autorest/autorest/azure" | |
"github.com/Azure/go-autorest/autorest/azure/auth" | |
) | |
// getAccountClient retrieves a blob storage account client in an automated way | |
// based on the local Azure CLI session or SP credentials in environment vars. | |
// | |
// This function will pick the first subscription available to the user, and | |
// the first account with the requested name within the subscription. | |
// | |
func getAccountClient( | |
ctx context.Context, | |
accountName string, | |
) (storage.BlobStorageClient, error) { | |
nilClient := storage.BlobStorageClient{} | |
// Initialize authorizer based either on CLI or environment variables. | |
authorizer, err := auth.NewAuthorizerFromCLIWithResource( | |
azure.PublicCloud.ResourceManagerEndpoint, | |
) | |
if err != nil { // Try environment | |
cfg := &auth.ClientCredentialsConfig{ | |
ClientID: os.Getenv("AZURE_CLIENT_ID"), | |
ClientSecret: os.Getenv("AZURE_CLIENT_SECRET"), | |
TenantID: os.Getenv("AZURE_TENANT_ID"), | |
Resource: azure.PublicCloud.ResourceManagerEndpoint, | |
AADEndpoint: azure.PublicCloud.ActiveDirectoryEndpoint, | |
} | |
authorizer, err = cfg.Authorizer() | |
if err != nil { | |
return nilClient, fmt.Errorf( | |
"auth initialization failed, %v", err) | |
} | |
} | |
// Get subscription ID. | |
subID, err := inferSubscriptionIDFromUser(ctx, authorizer) | |
if err != nil { | |
return nilClient, fmt.Errorf( | |
"could not infer subscription id for logged in user, %v", err) | |
} | |
// Find the resource group name of the account | |
rg, err := findAccountRG(ctx, authorizer, subID, accountName) | |
if err != nil { | |
return nilClient, fmt.Errorf("could not find storage account, %v", err) | |
} | |
// Get account key | |
accountKey, err := getAccountKey(ctx, authorizer, subID, rg, accountName) | |
if err != nil { | |
return nilClient, err | |
} | |
// Initialize and return client | |
client, err := storage.NewBasicClient(accountName, accountKey) | |
if err != nil { | |
return nilClient, fmt.Errorf("client, %w", err) | |
} | |
return client.GetBlobService(), nil | |
} | |
// getAccountKey lists account keys and picks the first one from the list. | |
func getAccountKey( | |
ctx context.Context, | |
authorizer autorest.Authorizer, | |
subscriptionID string, | |
resourceGroupName string, | |
accountName string, | |
) (string, error) { | |
accountsClient := mgmtstorage.NewAccountsClient(subscriptionID) | |
accountsClient.Authorizer = authorizer | |
listKeysResult, err := accountsClient.ListKeys(ctx, | |
resourceGroupName, accountName) | |
if err != nil { | |
return "", err | |
} | |
if len(*listKeysResult.Keys) == 0 { | |
return "", errors.New("failed to list keys in storage acc") | |
} | |
key := *(*listKeysResult.Keys)[0].Value | |
return key, nil | |
} | |
// findAccountRG lists storage accounts available within the subscription and | |
// returns the resourge group name of the first matching storage account, or an | |
// error if the storage account could not be found. | |
func findAccountRG( | |
ctx context.Context, | |
authorizer autorest.Authorizer, | |
subscriptionID string, | |
accountName string, | |
) (string, error) { | |
accountsClient := mgmtstorage.NewAccountsClient(subscriptionID) | |
accountsClient.Authorizer = authorizer | |
result, err := accountsClient.List(ctx) | |
if err != nil { | |
return "", fmt.Errorf("list blob accounts err, %w", err) | |
} | |
for _, val := range *result.Value { | |
if *val.Name == accountName { | |
idparts := strings.Split(*val.ID, "/") | |
if len(idparts) < 4 { | |
return "", errors.New("invalid account id") | |
} | |
return idparts[4], nil | |
} | |
} | |
return "", errors.New("not found") | |
} | |
// inferSubscriptionIDFromUser lists subscriptions available to the user that is | |
// embedded in the authorizer, and returns the first available subscription. | |
func inferSubscriptionIDFromUser( | |
ctx context.Context, | |
authorizer autorest.Authorizer, | |
) (string, error) { | |
subscriptionClient := subscriptions.NewClient() | |
subscriptionClient.Authorizer = authorizer | |
res, err := subscriptionClient.List(ctx) | |
if err != nil { | |
return "", fmt.Errorf("failed to list subscriptions, %v", err) | |
} | |
subs := res.Values() | |
if len(subs) < 1 { | |
return "", errors.New("logged in user does not have access to any subscriptions") | |
} | |
for _, sub := range subs { | |
return *sub.SubscriptionID, nil | |
} | |
return "", errors.New("subscription id not found") | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import ( | |
"github.com/Azure/azure-sdk-for-go/profiles/2019-03-01/resources/mgmt/subscriptions" | |
"github.com/Azure/azure-sdk-for-go/services/web/mgmt/2019-08-01/web" | |
"github.com/Azure/go-autorest/autorest" | |
"github.com/Azure/go-autorest/autorest/azure/auth" | |
) | |
func getMasterKey(ctx context.Context, appName string) (string, error) { | |
authorizer, err := auth.NewAuthorizerFromCLI() | |
if err != nil { | |
authorizer, err = auth.NewAuthorizerFromEnvironment() | |
if err != nil { | |
return "", fmt.Errorf( | |
"auth initialization failed, %v", err) | |
} | |
} | |
subscriptionID, err := findSubscriptionID(ctx, authorizer) | |
if err != nil { | |
return "", fmt.Errorf( | |
"failed to infer subscription ID based on logged in user, %v", err) | |
} | |
site, err := findSite(ctx, authorizer, subscriptionID, appName) | |
if err != nil { | |
return "", fmt.Errorf("could not find site with name %v, %v", appName, err) | |
} | |
appsClient := web.NewAppsClient(subscriptionID) | |
appsClient.Authorizer = authorizer | |
keys, err := appsClient.ListHostKeys(ctx, *site.ResourceGroup, *site.Name) | |
if err != nil { | |
return "", fmt.Errorf( | |
"failed to list host keys for app with name %v, %v", appName, err) | |
} | |
return *keys.MasterKey, nil | |
} | |
func findSubscriptionID( | |
ctx context.Context, | |
authorizer autorest.Authorizer, | |
) (string, error) { | |
subscriptionClient := subscriptions.NewClient() | |
subscriptionClient.Authorizer = authorizer | |
res, err := subscriptionClient.List(ctx) | |
if err != nil { | |
return "", fmt.Errorf("failed to list subscriptions, %v", err) | |
} | |
subs := res.Values() | |
if len(subs) < 1 { | |
return "", errors.New("logged in user does not have access to any subscriptions") | |
} | |
for _, sub := range subs { | |
return *sub.SubscriptionID, nil | |
} | |
return "", errors.New("subscription id not found") | |
} | |
func findSite( | |
ctx context.Context, | |
authorizer autorest.Authorizer, | |
subscriptionID string, | |
name string, | |
) (*web.Site, error) { | |
appsClient := web.NewAppsClient(subscriptionID) | |
appsClient.Authorizer = authorizer | |
// Find app service that we are looking for | |
apps, err := appsClient.List(ctx) | |
if err != nil { | |
return nil, fmt.Errorf("failed to list web apps, %v", err) | |
} | |
for { | |
for _, site := range apps.Values() { | |
if *site.Name == name { | |
return &site, nil | |
} | |
} | |
if !apps.NotDone() { | |
break | |
} | |
if err := apps.NextWithContext(ctx); err != nil { | |
return nil, fmt.Errorf("failed fetch next page of web apps, %v", err) | |
} | |
} | |
return nil, errors.New("site not found") | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
func findResourceGroup( | |
ctx context.Context, | |
accountName string, | |
subscriptionID string, | |
token string, | |
) (string, error) { | |
var jsonResp struct { | |
Value []struct { | |
ID string `json:"id"` | |
Name string `json:"name"` | |
} `json:"value"` | |
} | |
url := fmt.Sprintf("https://management.azure.com/subscriptions/%v/providers/Microsoft.Storage/storageAccounts?api-version=2021-04-01", | |
subscriptionID, | |
) | |
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) | |
if err != nil { | |
return "", err | |
} | |
req.Header.Set("Content-Type", "application/json") | |
req.Header.Set("Authorization", fmt.Sprintf("Bearer %v", token)) | |
resp, err := http.DefaultClient.Do(req) | |
if err != nil { | |
return "", err | |
} | |
if err := json.NewDecoder(resp.Body).Decode(&jsonResp); err != nil { | |
return "", err | |
} | |
for _, val := range jsonResp.Value { | |
if val.Name == accountName { | |
parts := strings.Split(val.ID, "/") | |
if len(parts) < 4 { | |
return "", fmt.Errorf("failed to parse account resource name") | |
} | |
return parts[3], nil | |
} | |
} | |
return "", errors.New("coult not find storage account") | |
} | |
func findSubscriptionID( | |
ctx context.Context, | |
token string, | |
) (string, error) { | |
var jsonResp struct { | |
Value []struct { | |
SubscriptionID string `json:"subscriptionId"` | |
} `json:"value"` | |
} | |
url := "https://management.azure.com/subscriptions?api-version=2020-01-01" | |
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) | |
if err != nil { | |
return "", err | |
} | |
req.Header.Set("Content-Type", "application/json") | |
req.Header.Set("Authorization", fmt.Sprintf("Bearer %v", token)) | |
resp, err := http.DefaultClient.Do(req) | |
if err != nil { | |
return "", err | |
} | |
if err := json.NewDecoder(resp.Body).Decode(&jsonResp); err != nil { | |
return "", err | |
} | |
if len(jsonResp.Value) == 0 { | |
return "", errors.New("no subscriptions available for service principal") | |
} | |
return jsonResp.Value[0].SubscriptionID, nil | |
} | |
func maybeGetTokenFromCLI() (string, error) { | |
token, err := cli.GetTokenFromCLI(azure.PublicCloud.ResourceManagerEndpoint) | |
if err != nil { | |
return "", err | |
} | |
adalToken, err := token.ToADALToken() | |
if err != nil { | |
return "", err | |
} | |
return adalToken.AccessToken, nil | |
} | |
func maybeGetTokenFromEnv() (string, error) { | |
for _, envName := range []string{ | |
"AZURE_CLIENT_ID", | |
"AZURE_CLIENT_SECRET", | |
"AZURE_TENANT_ID", | |
} { | |
if os.Getenv(envName) == "" { | |
return "", fmt.Errorf("azure token from env failed, %v is unset", envName) | |
} | |
} | |
cfg := &auth.ClientCredentialsConfig{ | |
ClientID: os.Getenv("AZURE_CLIENT_ID"), | |
ClientSecret: os.Getenv("AZURE_CLIENT_SECRET"), | |
TenantID: os.Getenv("AZURE_TENANT_ID"), | |
Resource: azure.PublicCloud.ResourceManagerEndpoint, | |
AADEndpoint: azure.PublicCloud.ActiveDirectoryEndpoint, | |
} | |
token, err := cfg.ServicePrincipalToken() | |
if err != nil { | |
return "", err | |
} | |
if err := token.EnsureFresh(); err != nil { | |
return "", err | |
} | |
return token.OAuthToken(), nil | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Assumptions:
Process: