Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Azure Auth WIF fields #2254

Merged
merged 12 commits into from
Jun 6, 2024
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ FEATURES:
* Add support for new WIF fields in `vault_gcp_secret_backend`. Requires Vault 1.17+. *Available only for Vault Enterprise* ([#2249](https://github.com/hashicorp/terraform-provider-vault/pull/2249)).
* Add support for new WIF fields in `vault_aws_auth_backend_client`. Requires Vault 1.17+. *Available only for Vault Enterprise* ([#2243](https://github.com/hashicorp/terraform-provider-vault/pull/2243)).
* Add support for new WIF fields in `vault_azure_secret_backend`. Requires Vault 1.17+. *Available only for Vault Enterprise* ([#2250](https://github.com/hashicorp/terraform-provider-vault/pull/2250))
* Add support for new WIF fields in `vault_azure_auth_backend_config`. Requires Vault 1.17+. *Available only for Vault Enterprise* ([#2254](https://github.com/hashicorp/terraform-provider-vault/pull/2254)).
* Add new data source and resource `vault_pki_secret_backend_config_est`. Requires Vault 1.16+. *Available only for Vault Enterprise* ([#2246](https://github.com/hashicorp/terraform-provider-vault/pull/2246))

IMPROVEMENTS:
Expand Down
148 changes: 89 additions & 59 deletions vault/resource_azure_auth_backend_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,33 @@
package vault

import (
"context"
"fmt"

"log"
"regexp"
"strings"

"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"

"github.com/hashicorp/terraform-provider-vault/internal/consts"
"github.com/hashicorp/terraform-provider-vault/internal/provider"
)

var azureAuthBackendConfigFromPathRegex = regexp.MustCompile("^auth/(.+)/config$")

func azureAuthBackendConfigResource() *schema.Resource {
return &schema.Resource{
Create: azureAuthBackendWrite,
Read: provider.ReadWrapper(azureAuthBackendRead),
Update: azureAuthBackendWrite,
Delete: azureAuthBackendDelete,
Exists: azureAuthBackendExists,
CreateContext: azureAuthBackendWrite,
ReadContext: provider.ReadContextWrapper(azureAuthBackendRead),
UpdateContext: azureAuthBackendWrite,
DeleteContext: azureAuthBackendDelete,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
StateContext: schema.ImportStatePassthroughContext,
},

Schema: map[string]*schema.Schema{
"backend": {
consts.FieldBackend: {
Type: schema.TypeString,
Optional: true,
Description: "Unique name of the auth backend to configure.",
Expand All @@ -39,73 +41,92 @@ func azureAuthBackendConfigResource() *schema.Resource {
return strings.Trim(v.(string), "/")
},
},
"tenant_id": {
consts.FieldTenantID: {
Type: schema.TypeString,
Required: true,
Description: "The tenant id for the Azure Active Directory organization.",
Sensitive: true,
},
"client_id": {
consts.FieldClientID: {
Type: schema.TypeString,
Optional: true,
Description: "The client id for credentials to query the Azure APIs. Currently read permissions to query compute resources are required.",
Sensitive: true,
},
"client_secret": {
consts.FieldClientSecret: {
Type: schema.TypeString,
Optional: true,
Description: "The client secret for credentials to query the Azure APIs",
Sensitive: true,
},
"resource": {
consts.FieldResource: {
Type: schema.TypeString,
Required: true,
Description: "The configured URL for the application registered in Azure Active Directory.",
},
"environment": {
consts.FieldEnvironment: {
Type: schema.TypeString,
Optional: true,
Description: "The Azure cloud environment. Valid values: AzurePublicCloud, AzureUSGovernmentCloud, AzureChinaCloud, AzureGermanCloud.",
},
consts.FieldIdentityTokenAudience: {
Type: schema.TypeString,
Optional: true,
Description: "The audience claim value.",
},
consts.FieldIdentityTokenTTL: {
Type: schema.TypeInt,
Optional: true,
Computed: true,
Description: "The TTL of generated identity tokens in seconds.",
},
},
}
}

func azureAuthBackendWrite(d *schema.ResourceData, meta interface{}) error {
func azureAuthBackendWrite(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
config, e := provider.GetClient(d, meta)
if e != nil {
return e
return diag.FromErr(e)
}

// if backend comes from the config, it won't have the StateFunc
// applied yet, so we need to apply it again.
backend := d.Get("backend").(string)
tenantId := d.Get("tenant_id").(string)
clientId := d.Get("client_id").(string)
clientSecret := d.Get("client_secret").(string)
resource := d.Get("resource").(string)
environment := d.Get("environment").(string)
backend := d.Get(consts.FieldBackend).(string)
tenantId := d.Get(consts.FieldTenantID).(string)
clientId := d.Get(consts.FieldClientID).(string)
clientSecret := d.Get(consts.FieldClientSecret).(string)
resource := d.Get(consts.FieldResource).(string)
environment := d.Get(consts.FieldEnvironment).(string)
identityTokenAud := d.Get(consts.FieldIdentityTokenAudience).(string)
identityTokenTTL := d.Get(consts.FieldIdentityTokenTTL).(int)

path := azureAuthBackendConfigPath(backend)

data := map[string]interface{}{
"tenant_id": tenantId,
"client_id": clientId,
"client_secret": clientSecret,
"resource": resource,
"environment": environment,
consts.FieldTenantID: tenantId,
consts.FieldClientID: clientId,
consts.FieldClientSecret: clientSecret,
consts.FieldResource: resource,
consts.FieldEnvironment: environment,
}

useAPIVer117Ent := provider.IsAPISupported(meta, provider.VaultVersion117Ent)
if useAPIVer117Ent {
data[consts.FieldIdentityTokenAudience] = identityTokenAud
data[consts.FieldIdentityTokenTTL] = identityTokenTTL
}

log.Printf("[DEBUG] Writing Azure auth backend config to %q", path)
_, err := config.Logical().Write(path, data)
_, err := config.Logical().WriteWithContext(ctx, path, data)
if err != nil {
return fmt.Errorf("error writing to %q: %s", path, err)
return diag.Errorf("error writing to %q: %s", path, err)
}
log.Printf("[DEBUG] Wrote Azure auth backend config to %q", path)

d.SetId(path)

return azureAuthBackendRead(d, meta)
return azureAuthBackendRead(ctx, d, meta)
}

func azureAuthBackendConfigBackendFromPath(path string) (string, error) {
Expand All @@ -119,16 +140,16 @@ func azureAuthBackendConfigBackendFromPath(path string) (string, error) {
return res[1], nil
}

func azureAuthBackendRead(d *schema.ResourceData, meta interface{}) error {
func azureAuthBackendRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
config, e := provider.GetClient(d, meta)
if e != nil {
return e
return diag.FromErr(e)
}
path := d.Id()
log.Printf("[DEBUG] Reading Azure auth backend config")
secret, err := config.Logical().Read(path)
secret, err := config.Logical().ReadWithContext(ctx, path)
if err != nil {
return fmt.Errorf("error reading Azure auth backend config from %q: %s", path, err)
return diag.Errorf("error reading Azure auth backend config from %q: %s", path, err)
}
log.Printf("[DEBUG] Read Azure auth backend config")

Expand All @@ -139,49 +160,58 @@ func azureAuthBackendRead(d *schema.ResourceData, meta interface{}) error {
}
backend, err := azureAuthBackendConfigBackendFromPath(path)
if err != nil {
return fmt.Errorf("invalid path %q for azure auth backend config: %s", path, err)
return diag.Errorf("invalid path %q for azure auth backend config: %s", path, err)
}

d.Set("backend", backend)
d.Set("tenant_id", secret.Data["tenant_id"])
d.Set("client_id", secret.Data["client_id"])
if v, ok := secret.Data["client_secret"]; ok {
d.Set("client_secret", v)
d.Set(consts.FieldBackend, backend)
Zlaticanin marked this conversation as resolved.
Show resolved Hide resolved

fields := []string{
consts.FieldTenantID,
consts.FieldClientID,
consts.FieldClientSecret,
consts.FieldResource,
consts.FieldEnvironment,
}
for _, k := range fields {
if v, ok := secret.Data[k]; ok {
if err := d.Set(k, v); err != nil {
return diag.FromErr(err)
}
}
}
d.Set("resource", secret.Data["resource"])
d.Set("environment", secret.Data["environment"])

useAPIVer117Ent := provider.IsAPISupported(meta, provider.VaultVersion117Ent)
if useAPIVer117Ent {
if v, ok := secret.Data[consts.FieldIdentityTokenAudience]; ok {
if err := d.Set(consts.FieldIdentityTokenAudience, v); err != nil {
return diag.FromErr(err)
}
}
if v, ok := secret.Data[consts.FieldIdentityTokenTTL]; ok {
if err := d.Set(consts.FieldIdentityTokenTTL, v); err != nil {
return diag.FromErr(err)
}
}
}

return nil
}

func azureAuthBackendDelete(d *schema.ResourceData, meta interface{}) error {
func azureAuthBackendDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
config, e := provider.GetClient(d, meta)
if e != nil {
return e
return diag.FromErr(e)
}
log.Printf("[DEBUG] Deleting Azure auth backend config from %q", d.Id())
_, err := config.Logical().Delete(d.Id())
_, err := config.Logical().DeleteWithContext(ctx, d.Id())
if err != nil {
return fmt.Errorf("error deleting Azure auth backend config from %q: %s", d.Id(), err)
return diag.Errorf("error deleting Azure auth backend config from %q: %s", d.Id(), err)
}
log.Printf("[DEBUG] Deleted Azure auth backend config from %q", d.Id())

return nil
}

func azureAuthBackendExists(d *schema.ResourceData, meta interface{}) (bool, error) {
config, e := provider.GetClient(d, meta)
if e != nil {
return false, e
}
log.Printf("[DEBUG] Checking if Azure auth backend is configured at %q", d.Id())
secret, err := config.Logical().Read(d.Id())
if err != nil {
return true, fmt.Errorf("error checking if Azure auth backend is configured at %q: %s", d.Id(), err)
}
log.Printf("[DEBUG] Checked if Azure auth backend is configured at %q", d.Id())
return secret != nil, nil
}

func azureAuthBackendConfigPath(path string) string {
return "auth/" + strings.Trim(path, "/") + "/config"
}
88 changes: 81 additions & 7 deletions vault/resource_azure_auth_backend_config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ package vault

import (
"fmt"

"testing"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"

"github.com/hashicorp/terraform-provider-vault/internal/consts"
"github.com/hashicorp/terraform-provider-vault/internal/provider"
"github.com/hashicorp/terraform-provider-vault/testutil"
)
Expand All @@ -30,7 +31,7 @@ func TestAccAzureAuthBackendConfig_import(t *testing.T) {
ResourceName: "vault_azure_auth_backend_config.config",
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"client_secret"},
ImportStateVerifyIgnore: []string{consts.FieldClientSecret},
},
},
})
Expand All @@ -55,6 +56,46 @@ func TestAccAzureAuthBackendConfig_basic(t *testing.T) {
})
}

func TestAccAzureAuthBackend_wif(t *testing.T) {
backend := acctest.RandomWithPrefix("tf-test-azure")
updatedBackend := acctest.RandomWithPrefix("tf-test-azure-updated")

resourceType := "vault_azure_auth_backend"
resourceName := resourceType + ".test"
resource.Test(t, resource.TestCase{
ProviderFactories: providerFactories,
PreCheck: func() {
testutil.TestEntPreCheck(t)
SkipIfAPIVersionLT(t, testProvider.Meta(), provider.VaultVersion117Ent)
},
CheckDestroy: testCheckMountDestroyed(resourceType, consts.MountTypeAzure, consts.FieldPath),
Steps: []resource.TestStep{
{
Config: testAccAzureAuthBackendConfig_wifBasic(backend),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(resourceName, consts.FieldPath, backend),
resource.TestCheckResourceAttr(resourceName, consts.FieldSubscriptionID, "11111111-2222-3333-4444-111111111111"),
resource.TestCheckResourceAttr(resourceName, consts.FieldTenantID, "11111111-2222-3333-4444-222222222222"),
resource.TestCheckResourceAttr(resourceName, consts.FieldClientID, "11111111-2222-3333-4444-333333333333"),
resource.TestCheckResourceAttr(resourceName, consts.FieldIdentityTokenAudience, "wif-audience"),
resource.TestCheckResourceAttr(resourceName, consts.FieldIdentityTokenTTL, "600"),
),
},
{
Config: testAccAzureAuthBackendConfig_wifUpdated(updatedBackend),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(resourceName, consts.FieldPath, updatedBackend),
resource.TestCheckResourceAttr(resourceName, consts.FieldSubscriptionID, "11111111-2222-3333-4444-111111111111"),
resource.TestCheckResourceAttr(resourceName, consts.FieldTenantID, "22222222-3333-4444-5555-333333333333"),
resource.TestCheckResourceAttr(resourceName, consts.FieldClientID, "22222222-3333-4444-5555-444444444444"),
resource.TestCheckResourceAttr(resourceName, consts.FieldIdentityTokenAudience, "wif-audience-updated"),
resource.TestCheckResourceAttr(resourceName, consts.FieldIdentityTokenTTL, "1800"),
),
},
},
})
}

func testAccCheckAzureAuthBackendConfigDestroy(s *terraform.State) error {
config := testProvider.Meta().(*provider.ProviderMeta).MustGetClient()

Expand Down Expand Up @@ -118,11 +159,10 @@ func testAccAzureAuthBackendConfigCheck_attrs(backend string) resource.TestCheck
return fmt.Errorf("Azure auth not configured at %q", endpoint)
}
attrs := map[string]string{
"tenant_id": "tenant_id",
"client_id": "client_id",
//"client_secret": "client_secret",
"resource": "resource",
"environment": "environment",
consts.FieldTenantID: consts.FieldTenantID,
consts.FieldClientID: consts.FieldClientID,
consts.FieldResource: consts.FieldResource,
consts.FieldEnvironment: consts.FieldEnvironment,
}
for stateAttr, apiAttr := range attrs {
if resp.Data[apiAttr] == nil && instanceState.Attributes[stateAttr] == "" {
Expand Down Expand Up @@ -152,3 +192,37 @@ resource "vault_azure_auth_backend_config" "config" {
resource = "http://vault.hashicorp.com"
}`, backend)
}

func testAccAzureAuthBackendConfig_wifBasic(backend string) string {
return fmt.Sprintf(`
resource "vault_auth_backend" "azure" {
path = "%s"
type = "azure"
}

resource "vault_azure_auth_backend_config" "config" {
backend = vault_auth_backend.azure.path
subscription_id = "11111111-2222-3333-4444-111111111111"
tenant_id = "11111111-2222-3333-4444-222222222222"
client_id = "11111111-2222-3333-4444-333333333333"
identity_token_audience = "wif-audience"
identity_token_ttl = 600
}`, backend)
}

func testAccAzureAuthBackendConfig_wifUpdated(backend string) string {
return fmt.Sprintf(`
resource "vault_auth_backend" "azure" {
path = "%s"
type = "azure"
}

resource "vault_azure_auth_backend_config" "config" {
backend = vault_auth_backend.azure.path
subscription_id = "11111111-2222-3333-4444-111111111111"
tenant_id = "22222222-3333-4444-5555-333333333333"
client_id = "22222222-3333-4444-5555-444444444444"
identity_token_audience = "wif-audience-updated"
identity_token_ttl = 1800
}`, backend)
}
Loading