Azure Backend: support OIDC authentication

This commit is contained in:
Tom Bamford 2022-04-26 18:20:40 +01:00
parent 807e7c0350
commit d08bc4463d
No known key found for this signature in database
GPG Key ID: 6343CE10F70C3D21
5 changed files with 137 additions and 18 deletions

View File

@ -81,11 +81,16 @@ func buildArmClient(ctx context.Context, config BackendConfig) (*ArmClient, erro
// Managed Service Identity // Managed Service Identity
MsiEndpoint: config.MsiEndpoint, MsiEndpoint: config.MsiEndpoint,
// OIDC
IDTokenRequestURL: config.OIDCRequestURL,
IDTokenRequestToken: config.OIDCRequestToken,
// Feature Toggles // Feature Toggles
SupportsAzureCliToken: true, SupportsAzureCliToken: true,
SupportsClientCertAuth: true, SupportsClientCertAuth: true,
SupportsClientSecretAuth: true, SupportsClientSecretAuth: true,
SupportsManagedServiceIdentity: config.UseMsi, SupportsManagedServiceIdentity: config.UseMsi,
SupportsOIDCAuth: config.UseOIDC,
UseMicrosoftGraph: config.UseMicrosoftGraph, UseMicrosoftGraph: config.UseMicrosoftGraph,
} }
armConfig, err := builder.Build() armConfig, err := builder.Build()
@ -106,13 +111,13 @@ func buildArmClient(ctx context.Context, config BackendConfig) (*ArmClient, erro
sender := sender.BuildSender("backend/remote-state/azure") sender := sender.BuildSender("backend/remote-state/azure")
var auth autorest.Authorizer var auth autorest.Authorizer
if builder.UseMicrosoftGraph { if builder.UseMicrosoftGraph {
log.Printf("[DEBUG] Obtaining a MSAL / Microsoft Graph token for Resource Manager..") log.Printf("[DEBUG] Obtaining an MSAL / Microsoft Graph token for Resource Manager..")
auth, err = armConfig.GetMSALToken(ctx, hamiltonEnv.ResourceManager, sender, oauthConfig, env.TokenAudience) auth, err = armConfig.GetMSALToken(ctx, hamiltonEnv.ResourceManager, sender, oauthConfig, env.TokenAudience)
if err != nil { if err != nil {
return nil, err return nil, err
} }
} else { } else {
log.Printf("[DEBUG] Obtaining a ADAL / Azure Active Directory Graph token for Resource Manager..") log.Printf("[DEBUG] Obtaining an ADAL / Azure Active Directory Graph token for Resource Manager..")
auth, err = armConfig.GetADALToken(ctx, sender, oauthConfig, env.TokenAudience) auth, err = armConfig.GetADALToken(ctx, sender, oauthConfig, env.TokenAudience)
if err != nil { if err != nil {
return nil, err return nil, err
@ -121,14 +126,14 @@ func buildArmClient(ctx context.Context, config BackendConfig) (*ArmClient, erro
if config.UseAzureADAuthentication { if config.UseAzureADAuthentication {
if builder.UseMicrosoftGraph { if builder.UseMicrosoftGraph {
log.Printf("[DEBUG] Obtaining a MSAL / Microsoft Graph token for Storage..") log.Printf("[DEBUG] Obtaining an MSAL / Microsoft Graph token for Storage..")
storageAuth, err := armConfig.GetMSALToken(ctx, hamiltonEnv.Storage, sender, oauthConfig, env.ResourceIdentifiers.Storage) storageAuth, err := armConfig.GetMSALToken(ctx, hamiltonEnv.Storage, sender, oauthConfig, env.ResourceIdentifiers.Storage)
if err != nil { if err != nil {
return nil, err return nil, err
} }
client.azureAdStorageAuth = &storageAuth client.azureAdStorageAuth = &storageAuth
} else { } else {
log.Printf("[DEBUG] Obtaining a ADAL / Azure Active Directory Graph token for Storage..") log.Printf("[DEBUG] Obtaining an ADAL / Azure Active Directory Graph token for Storage..")
storageAuth, err := armConfig.GetADALToken(ctx, sender, oauthConfig, env.ResourceIdentifiers.Storage) storageAuth, err := armConfig.GetADALToken(ctx, sender, oauthConfig, env.ResourceIdentifiers.Storage)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -135,6 +135,28 @@ func New() backend.Backend {
DefaultFunc: schema.EnvDefaultFunc("ARM_MSI_ENDPOINT", ""), DefaultFunc: schema.EnvDefaultFunc("ARM_MSI_ENDPOINT", ""),
}, },
// OIDC auth specific fields
"use_oidc": {
Type: schema.TypeBool,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("ARM_USE_OIDC", false),
Description: "Allow OIDC to be used for authentication",
},
"oidc_request_url": {
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.MultiEnvDefaultFunc([]string{"ARM_OIDC_REQUEST_URL", "ACTIONS_ID_TOKEN_REQUEST_URL"}, ""),
Description: "The URL for the OIDC provider from which to request an ID token",
},
"oidc_request_token": {
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.MultiEnvDefaultFunc([]string{"ARM_OIDC_REQUEST_TOKEN", "ACTIONS_ID_TOKEN_REQUEST_TOKEN"}, ""),
Description: "The bearer token for the request to the OIDC provider",
},
// Feature Flags // Feature Flags
"use_azuread_auth": { "use_azuread_auth": {
Type: schema.TypeBool, Type: schema.TypeBool,
@ -182,11 +204,14 @@ type BackendConfig struct {
MetadataHost string MetadataHost string
Environment string Environment string
MsiEndpoint string MsiEndpoint string
OIDCRequestURL string
OIDCRequestToken string
ResourceGroupName string ResourceGroupName string
SasToken string SasToken string
SubscriptionID string SubscriptionID string
TenantID string TenantID string
UseMsi bool UseMsi bool
UseOIDC bool
UseAzureADAuthentication bool UseAzureADAuthentication bool
UseMicrosoftGraph bool UseMicrosoftGraph bool
} }
@ -213,12 +238,15 @@ func (b *Backend) configure(ctx context.Context) error {
MetadataHost: data.Get("metadata_host").(string), MetadataHost: data.Get("metadata_host").(string),
Environment: data.Get("environment").(string), Environment: data.Get("environment").(string),
MsiEndpoint: data.Get("msi_endpoint").(string), MsiEndpoint: data.Get("msi_endpoint").(string),
OIDCRequestURL: data.Get("oidc_request_url").(string),
OIDCRequestToken: data.Get("oidc_request_token").(string),
ResourceGroupName: data.Get("resource_group_name").(string), ResourceGroupName: data.Get("resource_group_name").(string),
SasToken: data.Get("sas_token").(string), SasToken: data.Get("sas_token").(string),
StorageAccountName: data.Get("storage_account_name").(string), StorageAccountName: data.Get("storage_account_name").(string),
SubscriptionID: data.Get("subscription_id").(string), SubscriptionID: data.Get("subscription_id").(string),
TenantID: data.Get("tenant_id").(string), TenantID: data.Get("tenant_id").(string),
UseMsi: data.Get("use_msi").(bool), UseMsi: data.Get("use_msi").(bool),
UseOIDC: data.Get("use_oidc").(bool),
UseAzureADAuthentication: data.Get("use_azuread_auth").(bool), UseAzureADAuthentication: data.Get("use_azuread_auth").(bool),
UseMicrosoftGraph: data.Get("use_microsoft_graph").(bool), UseMicrosoftGraph: data.Get("use_microsoft_graph").(bool),
} }

View File

@ -39,7 +39,7 @@ func TestBackendConfig(t *testing.T) {
} }
} }
func TestBackendAccessKeyBasic(t *testing.T) { func TestAccBackendAccessKeyBasic(t *testing.T) {
testAccAzureBackend(t) testAccAzureBackend(t)
rs := acctest.RandString(4) rs := acctest.RandString(4)
res := testResourceNames(rs, "testState") res := testResourceNames(rs, "testState")
@ -65,7 +65,7 @@ func TestBackendAccessKeyBasic(t *testing.T) {
backend.TestBackendStates(t, b) backend.TestBackendStates(t, b)
} }
func TestBackendSASTokenBasic(t *testing.T) { func TestAccBackendSASTokenBasic(t *testing.T) {
testAccAzureBackend(t) testAccAzureBackend(t)
rs := acctest.RandString(4) rs := acctest.RandString(4)
res := testResourceNames(rs, "testState") res := testResourceNames(rs, "testState")
@ -95,7 +95,35 @@ func TestBackendSASTokenBasic(t *testing.T) {
backend.TestBackendStates(t, b) backend.TestBackendStates(t, b)
} }
func TestBackendADALAzureADAuthBasic(t *testing.T) { func TestAccBackendOIDCBasic(t *testing.T) {
testAccAzureBackend(t)
rs := acctest.RandString(4)
res := testResourceNames(rs, "testState")
armClient := buildTestClient(t, res)
ctx := context.TODO()
err := armClient.buildTestResources(ctx, &res)
defer armClient.destroyTestResources(ctx, res)
if err != nil {
t.Fatalf("Error creating Test Resources: %q", err)
}
b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
"storage_account_name": res.storageAccountName,
"container_name": res.storageContainerName,
"key": res.storageKeyName,
"resource_group_name": res.resourceGroup,
"use_oidc": true,
"subscription_id": os.Getenv("ARM_SUBSCRIPTION_ID"),
"tenant_id": os.Getenv("ARM_TENANT_ID"),
"environment": os.Getenv("ARM_ENVIRONMENT"),
"endpoint": os.Getenv("ARM_ENDPOINT"),
})).(*Backend)
backend.TestBackendStates(t, b)
}
func TestAccBackendADALAzureADAuthBasic(t *testing.T) {
testAccAzureBackend(t) testAccAzureBackend(t)
rs := acctest.RandString(4) rs := acctest.RandString(4)
res := testResourceNames(rs, "testState") res := testResourceNames(rs, "testState")
@ -123,7 +151,7 @@ func TestBackendADALAzureADAuthBasic(t *testing.T) {
backend.TestBackendStates(t, b) backend.TestBackendStates(t, b)
} }
func TestBackendADALManagedServiceIdentityBasic(t *testing.T) { func TestAccBackendADALManagedServiceIdentityBasic(t *testing.T) {
testAccAzureBackendRunningInAzure(t) testAccAzureBackendRunningInAzure(t)
rs := acctest.RandString(4) rs := acctest.RandString(4)
res := testResourceNames(rs, "testState") res := testResourceNames(rs, "testState")
@ -151,7 +179,7 @@ func TestBackendADALManagedServiceIdentityBasic(t *testing.T) {
backend.TestBackendStates(t, b) backend.TestBackendStates(t, b)
} }
func TestBackendADALServicePrincipalClientCertificateBasic(t *testing.T) { func TestAccBackendADALServicePrincipalClientCertificateBasic(t *testing.T) {
testAccAzureBackend(t) testAccAzureBackend(t)
clientCertPassword := os.Getenv("ARM_CLIENT_CERTIFICATE_PASSWORD") clientCertPassword := os.Getenv("ARM_CLIENT_CERTIFICATE_PASSWORD")
@ -188,7 +216,7 @@ func TestBackendADALServicePrincipalClientCertificateBasic(t *testing.T) {
backend.TestBackendStates(t, b) backend.TestBackendStates(t, b)
} }
func TestBackendADALServicePrincipalClientSecretBasic(t *testing.T) { func TestAccBackendADALServicePrincipalClientSecretBasic(t *testing.T) {
testAccAzureBackend(t) testAccAzureBackend(t)
rs := acctest.RandString(4) rs := acctest.RandString(4)
res := testResourceNames(rs, "testState") res := testResourceNames(rs, "testState")
@ -217,7 +245,7 @@ func TestBackendADALServicePrincipalClientSecretBasic(t *testing.T) {
backend.TestBackendStates(t, b) backend.TestBackendStates(t, b)
} }
func TestBackendADALServicePrincipalClientSecretCustomEndpoint(t *testing.T) { func TestAccBackendADALServicePrincipalClientSecretCustomEndpoint(t *testing.T) {
testAccAzureBackend(t) testAccAzureBackend(t)
// this is only applicable for Azure Stack. // this is only applicable for Azure Stack.
@ -253,7 +281,7 @@ func TestBackendADALServicePrincipalClientSecretCustomEndpoint(t *testing.T) {
backend.TestBackendStates(t, b) backend.TestBackendStates(t, b)
} }
func TestBackendMSALAzureADAuthBasic(t *testing.T) { func TestAccBackendMSALAzureADAuthBasic(t *testing.T) {
testAccAzureBackend(t) testAccAzureBackend(t)
rs := acctest.RandString(4) rs := acctest.RandString(4)
res := testResourceNames(rs, "testState") res := testResourceNames(rs, "testState")
@ -282,7 +310,7 @@ func TestBackendMSALAzureADAuthBasic(t *testing.T) {
backend.TestBackendStates(t, b) backend.TestBackendStates(t, b)
} }
func TestBackendMSALManagedServiceIdentityBasic(t *testing.T) { func TestAccBackendMSALManagedServiceIdentityBasic(t *testing.T) {
testAccAzureBackendRunningInAzure(t) testAccAzureBackendRunningInAzure(t)
rs := acctest.RandString(4) rs := acctest.RandString(4)
res := testResourceNames(rs, "testState") res := testResourceNames(rs, "testState")
@ -311,7 +339,7 @@ func TestBackendMSALManagedServiceIdentityBasic(t *testing.T) {
backend.TestBackendStates(t, b) backend.TestBackendStates(t, b)
} }
func TestBackendMSALServicePrincipalClientCertificateBasic(t *testing.T) { func TestAccBackendMSALServicePrincipalClientCertificateBasic(t *testing.T) {
testAccAzureBackend(t) testAccAzureBackend(t)
clientCertPassword := os.Getenv("ARM_CLIENT_CERTIFICATE_PASSWORD") clientCertPassword := os.Getenv("ARM_CLIENT_CERTIFICATE_PASSWORD")
@ -349,7 +377,7 @@ func TestBackendMSALServicePrincipalClientCertificateBasic(t *testing.T) {
backend.TestBackendStates(t, b) backend.TestBackendStates(t, b)
} }
func TestBackendMSALServicePrincipalClientSecretBasic(t *testing.T) { func TestAccBackendMSALServicePrincipalClientSecretBasic(t *testing.T) {
testAccAzureBackend(t) testAccAzureBackend(t)
rs := acctest.RandString(4) rs := acctest.RandString(4)
res := testResourceNames(rs, "testState") res := testResourceNames(rs, "testState")
@ -379,7 +407,7 @@ func TestBackendMSALServicePrincipalClientSecretBasic(t *testing.T) {
backend.TestBackendStates(t, b) backend.TestBackendStates(t, b)
} }
func TestBackendMSALServicePrincipalClientSecretCustomEndpoint(t *testing.T) { func TestAccBackendMSALServicePrincipalClientSecretCustomEndpoint(t *testing.T) {
testAccAzureBackend(t) testAccAzureBackend(t)
// this is only applicable for Azure Stack. // this is only applicable for Azure Stack.
@ -416,7 +444,7 @@ func TestBackendMSALServicePrincipalClientSecretCustomEndpoint(t *testing.T) {
backend.TestBackendStates(t, b) backend.TestBackendStates(t, b)
} }
func TestBackendAccessKeyLocked(t *testing.T) { func TestAccBackendAccessKeyLocked(t *testing.T) {
testAccAzureBackend(t) testAccAzureBackend(t)
rs := acctest.RandString(4) rs := acctest.RandString(4)
res := testResourceNames(rs, "testState") res := testResourceNames(rs, "testState")
@ -454,7 +482,7 @@ func TestBackendAccessKeyLocked(t *testing.T) {
backend.TestBackendStateForceUnlockInWS(t, b1, b2, "foo") backend.TestBackendStateForceUnlockInWS(t, b1, b2, "foo")
} }
func TestBackendServicePrincipalLocked(t *testing.T) { func TestAccBackendServicePrincipalLocked(t *testing.T) {
testAccAzureBackend(t) testAccAzureBackend(t)
rs := acctest.RandString(4) rs := acctest.RandString(4)
res := testResourceNames(rs, "testState") res := testResourceNames(rs, "testState")

View File

@ -39,6 +39,15 @@ func testAccAzureBackendRunningInAzure(t *testing.T) {
} }
} }
// these kind of tests can only run when within GitHub Actions (e.g. OIDC)
func testAccAzureBackendRunningInGitHubActions(t *testing.T) {
testAccAzureBackend(t)
if os.Getenv("TF_RUNNING_IN_GITHUB_ACTIONS") == "" {
t.Skip("Skipping test since not running in GitHub Actions")
}
}
func buildTestClient(t *testing.T, res resourceNames) *ArmClient { func buildTestClient(t *testing.T, res resourceNames) *ArmClient {
subscriptionID := os.Getenv("ARM_SUBSCRIPTION_ID") subscriptionID := os.Getenv("ARM_SUBSCRIPTION_ID")
tenantID := os.Getenv("ARM_TENANT_ID") tenantID := os.Getenv("ARM_TENANT_ID")

View File

@ -46,6 +46,24 @@ terraform {
*** ***
When authenticating using OpenID Connect (OIDC):
```hcl
terraform {
backend "azurerm" {
resource_group_name = "StorageAccount-ResourceGroup"
storage_account_name = "abcd1234"
container_name = "tfstate"
key = "prod.terraform.tfstate"
use_oidc = true
subscription_id = "00000000-0000-0000-0000-000000000000"
tenant_id = "00000000-0000-0000-0000-000000000000"
}
}
```
***
When authenticating using Azure AD Authentication: When authenticating using Azure AD Authentication:
```hcl ```hcl
@ -137,6 +155,25 @@ data "terraform_remote_state" "foo" {
*** ***
When authenticating using OpenID Connect (OIDC):
```hcl
data "terraform_remote_state" "foo" {
backend = "azurerm"
config = {
resource_group_name = "StorageAccount-ResourceGroup"
storage_account_name = "terraform123abc"
container_name = "terraform-state"
key = "prod.terraform.tfstate"
use_oidc = true
subscription_id = "00000000-0000-0000-0000-000000000000"
tenant_id = "00000000-0000-0000-0000-000000000000"
}
}
```
***
When authenticating using AzureAD Authentication: When authenticating using AzureAD Authentication:
```hcl ```hcl
@ -231,6 +268,18 @@ When authenticating using the Managed Service Identity (MSI) - the following fie
*** ***
When authenticating using a Service Principal with OpenID Connect (OIDC) - the following fields are also supported:
* `oidc_request_url` - (Optional) The URL for the OIDC provider from which to request an ID token. This can also be sourced from the `ARM_OIDC_REQUEST_URL` or `ACTIONS_ID_TOKEN_REQUEST_URL` environment variables.
* `oidc_request_token` - (Optional) The bearer token for the request to the OIDC provider. This can also be sourced from the `ARM_OIDC_REQUEST_TOKEN` or `ACTIONS_ID_TOKEN_REQUEST_TOKEN` environment variables.
* `use_oidc` - (Optional) Should OIDC authentication be used? This can also be sourced from the `ARM_USE_OIDC` environment variable.
~> **Note:** When using OIDC for authentication, `use_microsoft_graph` must be set to `true` (which is the default).
***
When authenticating using a SAS Token associated with the Storage Account - the following fields are also supported: When authenticating using a SAS Token associated with the Storage Account - the following fields are also supported:
* `sas_token` - (Optional) The SAS Token used to access the Blob Storage Account. This can also be sourced from the `ARM_SAS_TOKEN` environment variable. * `sas_token` - (Optional) The SAS Token used to access the Blob Storage Account. This can also be sourced from the `ARM_SAS_TOKEN` environment variable.