From f7cdf318dbd53cf6cb4c2497f9f262d683f55129 Mon Sep 17 00:00:00 2001
From: Nathaniel Wesley Filardo <nfilardo@microsoft.com>
Date: Mon, 14 Nov 2022 04:12:52 +0000
Subject: [PATCH] azureblob: support simple "environment credentials"

As per
https://learn.microsoft.com/en-us/dotnet/api/azure.identity.environmentcredential?view=azure-dotnet

This supports only AZURE_CLIENT_SECRET-based authentication, as with the
existing service principal support.

Co-authored-by: Nick Craig-Wood <nick@craig-wood.com>
---
 backend/azureblob/azureblob.go | 35 ++++++++++++++++++++++++++--------
 1 file changed, 27 insertions(+), 8 deletions(-)

diff --git a/backend/azureblob/azureblob.go b/backend/azureblob/azureblob.go
index e52a48133..728a7405e 100644
--- a/backend/azureblob/azureblob.go
+++ b/backend/azureblob/azureblob.go
@@ -88,6 +88,15 @@ Leave blank normally. Needed only if you want to use a service principal instead
 
 See ["Create an Azure service principal"](https://docs.microsoft.com/en-us/cli/azure/create-an-azure-service-principal-azure-cli) and ["Assign an Azure role for access to blob data"](https://docs.microsoft.com/en-us/azure/storage/common/storage-auth-aad-rbac-cli) pages for more details.
 `,
+		}, {
+			Name: "env_auth",
+			Help: `Read credentials from runtime (environment variables).
+
+Pull credentials from AZURE_TENANT_ID and AZURE_CLIENT_{ID,SECRET} environment vars.
+See EnvironmentCredential in the Azure docs for more info.
+
+Other authentication methods will, if specified, override this flag.`,
+			Default: false,
 		}, {
 			Name: "key",
 			Help: "Storage Account Key.\n\nLeave blank to use SAS URL or Emulator.",
@@ -270,6 +279,7 @@ type Options struct {
 	Account              string               `config:"account"`
 	ServicePrincipalFile string               `config:"service_principal_file"`
 	Key                  string               `config:"key"`
+	EnvAuth              bool                 `config:"env_auth"`
 	UseMSI               bool                 `config:"use_msi"`
 	MSIObjectID          string               `config:"msi_object_id"`
 	MSIClientID          string               `config:"msi_client_id"`
@@ -714,20 +724,29 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e
 		} else {
 			serviceURL = azblob.NewServiceURL(*u, pipeline)
 		}
-	case opt.ServicePrincipalFile != "":
+	case opt.ServicePrincipalFile != "" || opt.EnvAuth:
 		// Create a standard URL.
 		u, err = url.Parse(fmt.Sprintf("https://%s.%s", opt.Account, opt.Endpoint))
 		if err != nil {
 			return nil, fmt.Errorf("failed to make azure storage url from account and endpoint: %w", err)
 		}
-		// Try loading service principal credentials from file.
-		loadedCreds, err := os.ReadFile(env.ShellExpand(opt.ServicePrincipalFile))
-		if err != nil {
-			return nil, fmt.Errorf("error opening service principal credentials file: %w", err)
-		}
 		var spCredentials servicePrincipalCredentials
-		if err := json.Unmarshal(loadedCreds, &spCredentials); err != nil {
-			return nil, fmt.Errorf("error parsing credentials from JSON file: %w", err)
+		if opt.ServicePrincipalFile != "" {
+			// Try loading service principal credentials from file.
+			loadedCreds, err := os.ReadFile(env.ShellExpand(opt.ServicePrincipalFile))
+			if err != nil {
+				return nil, fmt.Errorf("error opening service principal credentials file: %w", err)
+			}
+
+			if err := json.Unmarshal(loadedCreds, &spCredentials); err != nil {
+				return nil, fmt.Errorf("error parsing credentials from JSON file: %w", err)
+			}
+		} else {
+			spCredentials = servicePrincipalCredentials{
+				Tenant:   os.Getenv("AZURE_TENANT_ID"),
+				AppID:    os.Getenv("AZURE_CLIENT_ID"),
+				Password: os.Getenv("AZURE_CLIENT_SECRET"),
+			}
 		}
 		// Create a token refresher from service principal credentials.
 		tokenRefresher, err := newServicePrincipalTokenRefresher(ctx, spCredentials)