diff --git a/docs/ce/azure-specific/azure-devops-locking-connection-methods.mdx b/docs/ce/azure-specific/azure-devops-locking-connection-methods.mdx index 34a7fb04..2fccf193 100644 --- a/docs/ce/azure-specific/azure-devops-locking-connection-methods.mdx +++ b/docs/ce/azure-specific/azure-devops-locking-connection-methods.mdx @@ -12,17 +12,22 @@ There is one mandatory environment variable the user will have to set, in order * CLIENT\_SECRET +* MANAGED\_IDENTITY + Then, depending on the value of `DIGGER_AZURE_AUTH_METHOD`, the user will have to set other environment variables. -1. **SHARED\_KEY** - * `DIGGER_AZURE_SA_NAME`: Storage account name +1. **SHARED\_KEY** + * `DIGGER_AZURE_SA_NAME`: Storage account name * `DIGGER_AZURE_SHARED_KEY`: shared key of the storage account -2. **CONNECTION\_STRING** +2. **CONNECTION\_STRING** * `DIGGER_AZURE_CONNECTION_STRING`: connection string -3. **CLIENT\_SECRET** - * `DIGGER_AZURE_TENANT_ID`: tenant id to use - * `DIGGER_AZURE_CLIENT_ID`: client id of the service principal - * `DIGGER_AZURE_CLIENT_SECRET`: secret of the service principal - * `DIGGER_AZURE_SA_NAME`: storage account name \ No newline at end of file +3. **CLIENT\_SECRET** + * `DIGGER_AZURE_TENANT_ID`: tenant id to use + * `DIGGER_AZURE_CLIENT_ID`: client id of the service principal + * `DIGGER_AZURE_CLIENT_SECRET`: secret of the service principal + * `DIGGER_AZURE_SA_NAME`: storage account name + +4. **MANAGED\_IDENTITY** + * `DIGGER_AZURE_SA_NAME`: storage account name diff --git a/libs/locking/azure/storage_account.go b/libs/locking/azure/storage_account.go index eeef11fc..d04fcec1 100644 --- a/libs/locking/azure/storage_account.go +++ b/libs/locking/azure/storage_account.go @@ -27,7 +27,7 @@ type StorageAccount struct { func NewStorageAccountLock() (*StorageAccount, error) { authMethod := os.Getenv("DIGGER_AZURE_AUTH_METHOD") if authMethod == "" { - return nil, fmt.Errorf("'DIGGER_AZURE_AUTH_METHOD' environment variable must be set to either 'SHARED_KEY' or 'CONNECTION_STRING' or 'CLIENT_SECRET'") + return nil, fmt.Errorf("'DIGGER_AZURE_AUTH_METHOD' environment variable must be set to either 'SHARED_KEY' or 'CONNECTION_STRING' or 'CLIENT_SECRET' or 'MANAGED_IDENTITY") } svcClient, err := getServiceClient(authMethod) @@ -128,6 +128,10 @@ func getServiceClient(authMethod string) (*aztables.ServiceClient, error) { return getClientSecretSvcClient() } + if authMethod == "MANAGED_IDENTITY" { + return getManagedIdentitySvcCLient() + } + return nil, fmt.Errorf("could not initialize service client, because no valid authentication method was found") } @@ -187,6 +191,27 @@ func getClientSecretSvcClient() (*aztables.ServiceClient, error) { return svcClient, nil } +func getManagedIdentitySvcCLient() (*aztables.ServiceClient, error) { + saName := os.Getenv("DIGGER_AZURE_SA_NAME") + + if saName == "" { + return nil, fmt.Errorf("you must set 'DIGGER_AZURE_SA_NAME' when using managed identity authentication") + } + + serviceURL := getServiceURL(saName) + + cred, err := azidentity.NewManagedIdentityCredential(nil) + if err != nil { + return nil, fmt.Errorf("could not create create managed identity credential: %v", err) + } + + svcClient, err := aztables.NewServiceClient(serviceURL, cred, nil) + if err != nil { + return nil, fmt.Errorf("could not create service client with managed identity authentication: %v", err) + } + return svcClient, nil +} + func (sal *StorageAccount) createTableIfNotExists() error { exists, err := sal.isTableExists(TABLE_NAME) if err != nil { diff --git a/libs/locking/azure/storage_account_test.go b/libs/locking/azure/storage_account_test.go index f06afcc4..2dd51583 100644 --- a/libs/locking/azure/storage_account_test.go +++ b/libs/locking/azure/storage_account_test.go @@ -64,6 +64,17 @@ var ( loadClientSecretEnv() }, }, + { + name: "Managed Identity authentication mode", + loadEnv: func(s *SALockTestSuite) { + // Skip this test case if we are not testing on a real storage account + if !usingRealSA { + s.T().Skip("Managed identity method can only be tested when used against a real storage account.") + return + } + loadManagedIdentityEnv() + }, + }, } ) @@ -137,6 +148,15 @@ func (suite *SALockTestSuite) TestNewStorageAccountLock_WithClientSecret_Missing suite.Error(err, "should have got an error") } +func (suite *SALockTestSuite) TestNewStorageAccountLock_WithManagedIdentity_MissingEnv() { + loadManagedIdentityEnv() + os.Setenv("DIGGER_AZURE_SA_NAME", "") + + sal, err := NewStorageAccountLock() + suite.Nil(sal) + suite.Error(err, "should have got an error") +} + func (suite *SALockTestSuite) TestLock_WhenNotLockedYet() { for _, tc := range testCases { suite.Run(tc.name, func() { @@ -373,6 +393,12 @@ func loadClientSecretEnv() { os.Setenv("DIGGER_AZURE_SA_NAME", envs["DIGGER_AZURE_SA_NAME"]) } +func loadManagedIdentityEnv() { + os.Setenv("DIGGER_AZURE_AUTH_METHOD", "MANAGED_IDENTITY") + + os.Setenv("DIGGER_AZURE_SA_NAME", envs["DIGGER_AZURE_SA_NAME"]) +} + // Clean environment variables func cleanEnv() { for _, env := range envNames {