Skip to content

Commit

Permalink
Azure Backend: support OIDC authentication
Browse files Browse the repository at this point in the history
  • Loading branch information
manicminer committed Apr 27, 2022
1 parent 807e7c0 commit d08bc44
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 18 deletions.
13 changes: 9 additions & 4 deletions internal/backend/remote-state/azure/arm_client.go
Expand Up @@ -81,11 +81,16 @@ func buildArmClient(ctx context.Context, config BackendConfig) (*ArmClient, erro
// Managed Service Identity
MsiEndpoint: config.MsiEndpoint,

// OIDC
IDTokenRequestURL: config.OIDCRequestURL,
IDTokenRequestToken: config.OIDCRequestToken,

// Feature Toggles
SupportsAzureCliToken: true,
SupportsClientCertAuth: true,
SupportsClientSecretAuth: true,
SupportsManagedServiceIdentity: config.UseMsi,
SupportsOIDCAuth: config.UseOIDC,
UseMicrosoftGraph: config.UseMicrosoftGraph,
}
armConfig, err := builder.Build()
Expand All @@ -106,13 +111,13 @@ func buildArmClient(ctx context.Context, config BackendConfig) (*ArmClient, erro
sender := sender.BuildSender("backend/remote-state/azure")
var auth autorest.Authorizer
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)
if err != nil {
return nil, err
}
} 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)
if err != nil {
return nil, err
Expand All @@ -121,14 +126,14 @@ func buildArmClient(ctx context.Context, config BackendConfig) (*ArmClient, erro

if config.UseAzureADAuthentication {
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)
if err != nil {
return nil, err
}
client.azureAdStorageAuth = &storageAuth
} 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)
if err != nil {
return nil, err
Expand Down
28 changes: 28 additions & 0 deletions internal/backend/remote-state/azure/backend.go
Expand Up @@ -135,6 +135,28 @@ func New() backend.Backend {
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
"use_azuread_auth": {
Type: schema.TypeBool,
Expand Down Expand Up @@ -182,11 +204,14 @@ type BackendConfig struct {
MetadataHost string
Environment string
MsiEndpoint string
OIDCRequestURL string
OIDCRequestToken string
ResourceGroupName string
SasToken string
SubscriptionID string
TenantID string
UseMsi bool
UseOIDC bool
UseAzureADAuthentication bool
UseMicrosoftGraph bool
}
Expand All @@ -213,12 +238,15 @@ func (b *Backend) configure(ctx context.Context) error {
MetadataHost: data.Get("metadata_host").(string),
Environment: data.Get("environment").(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),
SasToken: data.Get("sas_token").(string),
StorageAccountName: data.Get("storage_account_name").(string),
SubscriptionID: data.Get("subscription_id").(string),
TenantID: data.Get("tenant_id").(string),
UseMsi: data.Get("use_msi").(bool),
UseOIDC: data.Get("use_oidc").(bool),
UseAzureADAuthentication: data.Get("use_azuread_auth").(bool),
UseMicrosoftGraph: data.Get("use_microsoft_graph").(bool),
}
Expand Down
56 changes: 42 additions & 14 deletions internal/backend/remote-state/azure/backend_test.go
Expand Up @@ -39,7 +39,7 @@ func TestBackendConfig(t *testing.T) {
}
}

func TestBackendAccessKeyBasic(t *testing.T) {
func TestAccBackendAccessKeyBasic(t *testing.T) {
testAccAzureBackend(t)
rs := acctest.RandString(4)
res := testResourceNames(rs, "testState")
Expand All @@ -65,7 +65,7 @@ func TestBackendAccessKeyBasic(t *testing.T) {
backend.TestBackendStates(t, b)
}

func TestBackendSASTokenBasic(t *testing.T) {
func TestAccBackendSASTokenBasic(t *testing.T) {
testAccAzureBackend(t)
rs := acctest.RandString(4)
res := testResourceNames(rs, "testState")
Expand Down Expand Up @@ -95,7 +95,35 @@ func TestBackendSASTokenBasic(t *testing.T) {
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)
rs := acctest.RandString(4)
res := testResourceNames(rs, "testState")
Expand Down Expand Up @@ -123,7 +151,7 @@ func TestBackendADALAzureADAuthBasic(t *testing.T) {
backend.TestBackendStates(t, b)
}

func TestBackendADALManagedServiceIdentityBasic(t *testing.T) {
func TestAccBackendADALManagedServiceIdentityBasic(t *testing.T) {
testAccAzureBackendRunningInAzure(t)
rs := acctest.RandString(4)
res := testResourceNames(rs, "testState")
Expand Down Expand Up @@ -151,7 +179,7 @@ func TestBackendADALManagedServiceIdentityBasic(t *testing.T) {
backend.TestBackendStates(t, b)
}

func TestBackendADALServicePrincipalClientCertificateBasic(t *testing.T) {
func TestAccBackendADALServicePrincipalClientCertificateBasic(t *testing.T) {
testAccAzureBackend(t)

clientCertPassword := os.Getenv("ARM_CLIENT_CERTIFICATE_PASSWORD")
Expand Down Expand Up @@ -188,7 +216,7 @@ func TestBackendADALServicePrincipalClientCertificateBasic(t *testing.T) {
backend.TestBackendStates(t, b)
}

func TestBackendADALServicePrincipalClientSecretBasic(t *testing.T) {
func TestAccBackendADALServicePrincipalClientSecretBasic(t *testing.T) {
testAccAzureBackend(t)
rs := acctest.RandString(4)
res := testResourceNames(rs, "testState")
Expand Down Expand Up @@ -217,7 +245,7 @@ func TestBackendADALServicePrincipalClientSecretBasic(t *testing.T) {
backend.TestBackendStates(t, b)
}

func TestBackendADALServicePrincipalClientSecretCustomEndpoint(t *testing.T) {
func TestAccBackendADALServicePrincipalClientSecretCustomEndpoint(t *testing.T) {
testAccAzureBackend(t)

// this is only applicable for Azure Stack.
Expand Down Expand Up @@ -253,7 +281,7 @@ func TestBackendADALServicePrincipalClientSecretCustomEndpoint(t *testing.T) {
backend.TestBackendStates(t, b)
}

func TestBackendMSALAzureADAuthBasic(t *testing.T) {
func TestAccBackendMSALAzureADAuthBasic(t *testing.T) {
testAccAzureBackend(t)
rs := acctest.RandString(4)
res := testResourceNames(rs, "testState")
Expand Down Expand Up @@ -282,7 +310,7 @@ func TestBackendMSALAzureADAuthBasic(t *testing.T) {
backend.TestBackendStates(t, b)
}

func TestBackendMSALManagedServiceIdentityBasic(t *testing.T) {
func TestAccBackendMSALManagedServiceIdentityBasic(t *testing.T) {
testAccAzureBackendRunningInAzure(t)
rs := acctest.RandString(4)
res := testResourceNames(rs, "testState")
Expand Down Expand Up @@ -311,7 +339,7 @@ func TestBackendMSALManagedServiceIdentityBasic(t *testing.T) {
backend.TestBackendStates(t, b)
}

func TestBackendMSALServicePrincipalClientCertificateBasic(t *testing.T) {
func TestAccBackendMSALServicePrincipalClientCertificateBasic(t *testing.T) {
testAccAzureBackend(t)

clientCertPassword := os.Getenv("ARM_CLIENT_CERTIFICATE_PASSWORD")
Expand Down Expand Up @@ -349,7 +377,7 @@ func TestBackendMSALServicePrincipalClientCertificateBasic(t *testing.T) {
backend.TestBackendStates(t, b)
}

func TestBackendMSALServicePrincipalClientSecretBasic(t *testing.T) {
func TestAccBackendMSALServicePrincipalClientSecretBasic(t *testing.T) {
testAccAzureBackend(t)
rs := acctest.RandString(4)
res := testResourceNames(rs, "testState")
Expand Down Expand Up @@ -379,7 +407,7 @@ func TestBackendMSALServicePrincipalClientSecretBasic(t *testing.T) {
backend.TestBackendStates(t, b)
}

func TestBackendMSALServicePrincipalClientSecretCustomEndpoint(t *testing.T) {
func TestAccBackendMSALServicePrincipalClientSecretCustomEndpoint(t *testing.T) {
testAccAzureBackend(t)

// this is only applicable for Azure Stack.
Expand Down Expand Up @@ -416,7 +444,7 @@ func TestBackendMSALServicePrincipalClientSecretCustomEndpoint(t *testing.T) {
backend.TestBackendStates(t, b)
}

func TestBackendAccessKeyLocked(t *testing.T) {
func TestAccBackendAccessKeyLocked(t *testing.T) {
testAccAzureBackend(t)
rs := acctest.RandString(4)
res := testResourceNames(rs, "testState")
Expand Down Expand Up @@ -454,7 +482,7 @@ func TestBackendAccessKeyLocked(t *testing.T) {
backend.TestBackendStateForceUnlockInWS(t, b1, b2, "foo")
}

func TestBackendServicePrincipalLocked(t *testing.T) {
func TestAccBackendServicePrincipalLocked(t *testing.T) {
testAccAzureBackend(t)
rs := acctest.RandString(4)
res := testResourceNames(rs, "testState")
Expand Down
9 changes: 9 additions & 0 deletions internal/backend/remote-state/azure/helpers_test.go
Expand Up @@ -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 {
subscriptionID := os.Getenv("ARM_SUBSCRIPTION_ID")
tenantID := os.Getenv("ARM_TENANT_ID")
Expand Down
49 changes: 49 additions & 0 deletions website/docs/language/settings/backends/azurerm.mdx
Expand Up @@ -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:

```hcl
Expand Down Expand Up @@ -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:

```hcl
Expand Down Expand Up @@ -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:

* `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.
Expand Down

0 comments on commit d08bc44

Please sign in to comment.