Skip to content

Commit

Permalink
Add authentication_strength_policy
Browse files Browse the repository at this point in the history
  • Loading branch information
alexwilcox9 committed Sep 26, 2023
1 parent efbb8f3 commit 5bd6cfd
Show file tree
Hide file tree
Showing 15 changed files with 487 additions and 13 deletions.
50 changes: 50 additions & 0 deletions docs/resources/authentication_strength_policy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
---
subcategory: "Conditional Access"
---

# Resource: azuread_authentication_strength_policy

Manages a Authentication Strength Policy within Azure Active Directory.

## API Permissions

The following API permissions are required in order to use this resource.

When authenticated with a service principal, this resource requires the following application roles: `Policy.ReadWrite.ConditionalAccess` and `Policy.Read.All`

When authenticated with a user principal, this resource requires one of the following directory roles: `Conditional Access Administrator` or `Global Administrator`

## Example Usage

```terraform
resource "azuread_authentication_strength_policy" "example" {
display_name = "Example Authentication Strength Policy"
description = "Policy for demo purposes"
allowed_combinations = [
"fido2",
"password",
]
}
```

## Argument Reference

The following arguments are supported:

- `allowed_combinations` - (Required) List of allowed authentication methods for this authentication strength policy.
- `description` - (Optional) The description for this authentication strength policy.
- `display_name` - (Required) The friendly name for this authentication strength policy.

## Attributes Reference

In addition to all arguments above, the following attributes are exported:

- `id` - The ID of the authentication strength policy.

## Import

Authentication Strength Policies can be imported using the `id`, e.g.

```shell
terraform import azuread_authentication_strength_policy.my_policy 00000000-0000-0000-0000-000000000000
```
3 changes: 2 additions & 1 deletion docs/resources/conditional_access_policy.md
Original file line number Diff line number Diff line change
Expand Up @@ -227,12 +227,13 @@ The following arguments are supported:

`grant_controls` block supports the following:

* `authentication_strength_id` - (Optional) ID of an Authentication Strength Policy to use in this policy.
* `built_in_controls` - (Optional) List of built-in controls required by the policy. Possible values are: `block`, `mfa`, `approvedApplication`, `compliantApplication`, `compliantDevice`, `domainJoinedDevice`, `passwordChange` or `unknownFutureValue`.
* `custom_authentication_factors` - (Optional) List of custom controls IDs required by the policy.
* `operator` - (Required) Defines the relationship of the grant controls. Possible values are: `AND`, `OR`.
* `terms_of_use` - (Optional) List of terms of use IDs required by the policy.

-> At least one of `built_in_controls` or `terms_of_use` must be specified.
-> At least one of `built_in_controls`, `authentication_strength_id` or `terms_of_use` must be specified.

---

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ require (
github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320
github.com/hashicorp/go-uuid v1.0.3
github.com/hashicorp/terraform-plugin-sdk/v2 v2.26.1
github.com/manicminer/hamilton v0.63.0
github.com/manicminer/hamilton v0.63.1-0.20230926094900-e40d39cee08b
golang.org/x/text v0.9.0
)

Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -204,8 +204,8 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/manicminer/hamilton v0.63.0 h1:Pxh+TvuRhGsKl29v3dnzAoNJYUwqn6SNp/TGddg3g7E=
github.com/manicminer/hamilton v0.63.0/go.mod h1:va/X2sztcgQ5+BSxc2eU3FTHYIyxLnHvB4LudlPUZdE=
github.com/manicminer/hamilton v0.63.1-0.20230926094900-e40d39cee08b h1:RSanAVsFITByNGvxLYJA4gWNT/9T1rBWY1TbLMEQaTc=
github.com/manicminer/hamilton v0.63.1-0.20230926094900-e40d39cee08b/go.mod h1:va/X2sztcgQ5+BSxc2eU3FTHYIyxLnHvB4LudlPUZdE=
github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package conditionalaccess

import (
"context"
"errors"
"fmt"
"log"
"net/http"
"time"

"github.com/hashicorp/go-azure-sdk/sdk/odata"
"github.com/hashicorp/go-uuid"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-provider-azuread/internal/clients"
"github.com/hashicorp/terraform-provider-azuread/internal/helpers"
"github.com/hashicorp/terraform-provider-azuread/internal/tf"
"github.com/hashicorp/terraform-provider-azuread/internal/utils"
"github.com/hashicorp/terraform-provider-azuread/internal/validate"
"github.com/manicminer/hamilton/msgraph"
)

func authenticationStrengthPolicyResource() *schema.Resource {
return &schema.Resource{
CreateContext: authenticationStrengthPolicyCreate,
ReadContext: authenticationStrengthPolicyRead,
UpdateContext: authenticationStrengthPolicyUpdate,
DeleteContext: authenticationStrengthPolicyDelete,

Timeouts: &schema.ResourceTimeout{
Create: schema.DefaultTimeout(5 * time.Minute),
Read: schema.DefaultTimeout(5 * time.Minute),
Update: schema.DefaultTimeout(5 * time.Minute),
Delete: schema.DefaultTimeout(5 * time.Minute),
},

Importer: tf.ValidateResourceIDPriorToImport(func(id string) error {
if _, err := uuid.ParseUUID(id); err != nil {
return fmt.Errorf("specified ID (%q) is not valid: %s", id, err)
}
return nil
}),

Schema: map[string]*schema.Schema{
"display_name": {
Description: "The display name for the authentication strength policy",
Type: schema.TypeString,
Required: true,
ValidateDiagFunc: validate.NoEmptyStrings,
},

"description": {
Description: "The description for the authentication strength policy",
Type: schema.TypeString,
Optional: true,
},

"allowed_combinations": {
Description: "The allowed MFA methods for this policy",
Type: schema.TypeSet,
Required: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
},
}
}

func authenticationStrengthPolicyCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*clients.Client).ConditionalAccess.AuthenticationStrengthPoliciesClient

properties := msgraph.AuthenticationStrengthPolicy{
DisplayName: utils.String(d.Get("display_name").(string)),
Description: utils.String(d.Get("description").(string)),
AllowedCombinations: tf.ExpandStringSlicePtr(d.Get("allowed_combinations").(*schema.Set).List()),
}

authenticationStrengthPolicy, _, err := client.Create(ctx, properties)
if err != nil {
return tf.ErrorDiagF(err, "Could not create authentication strength policy")
}

d.SetId(*authenticationStrengthPolicy.ID)

return authenticationStrengthPolicyRead(ctx, d, meta)
}

func authenticationStrengthPolicyUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*clients.Client).ConditionalAccess.AuthenticationStrengthPoliciesClient

properties := msgraph.AuthenticationStrengthPolicy{
ID: utils.String(d.Id()),
DisplayName: utils.String(d.Get("display_name").(string)),
Description: utils.String(d.Get("description").(string)),
// AllowedCombinations: tf.ExpandStringSlicePtr(d.Get("allowed_combinations").(*schema.Set).List()),
}

_, err := client.Update(ctx, properties)
if err != nil {
return tf.ErrorDiagF(err, "Could not update authentication strength policy")
}

if d.HasChange("allowed_combinations") {
properties.AllowedCombinations = tf.ExpandStringSlicePtr(d.Get("allowed_combinations").(*schema.Set).List())
_, err := client.UpdateAllowedCombinations(ctx, properties)
if err != nil {
return tf.ErrorDiagF(err, "Could not update authentication strength policy allowed combinations")
}
}

return authenticationStrengthPolicyRead(ctx, d, meta)
}

func authenticationStrengthPolicyRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*clients.Client).ConditionalAccess.AuthenticationStrengthPoliciesClient

authenticationStrengthPolicy, status, err := client.Get(ctx, d.Id(), odata.Query{})
if err != nil {
if status == http.StatusNotFound {
log.Printf("[DEBUG] Authentication Strength Policy with Object ID %q was not found - removing from state", d.Id())
d.SetId("")
return nil
}
}
if authenticationStrengthPolicy == nil {
return tf.ErrorDiagF(errors.New("Bad API response"), "Result is nil")
}

d.SetId(*authenticationStrengthPolicy.ID)
tf.Set(d, "display_name", authenticationStrengthPolicy.DisplayName)
tf.Set(d, "description", authenticationStrengthPolicy.Description)
tf.Set(d, "allowed_combinations", tf.FlattenStringSlicePtr(authenticationStrengthPolicy.AllowedCombinations))

return nil
}

func authenticationStrengthPolicyDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*clients.Client).ConditionalAccess.AuthenticationStrengthPoliciesClient
authenticationStrengthPolicyId := d.Id()

if _, status, err := client.Get(ctx, authenticationStrengthPolicyId, odata.Query{}); err != nil {
if status == http.StatusNotFound {
log.Printf("[DEBUG] Authentication Strength Policy with ID %q already deleted", authenticationStrengthPolicyId)
return nil
}

return tf.ErrorDiagPathF(err, "id", "Retrieving Authentication Strength Policy with ID %q", authenticationStrengthPolicyId)
}

status, err := client.Delete(ctx, authenticationStrengthPolicyId)
if err != nil {
return tf.ErrorDiagPathF(err, "id", "Deleting Authentication Strength Policy with ID %q, got status %d", authenticationStrengthPolicyId, status)
}

if err := helpers.WaitForDeletion(ctx, func(ctx context.Context) (*bool, error) {
defer func() { client.BaseClient.DisableRetries = false }()
client.BaseClient.DisableRetries = true
if _, status, err := client.Get(ctx, authenticationStrengthPolicyId, odata.Query{}); err != nil {
if status == http.StatusNotFound {
return utils.Bool(false), nil
}
return nil, err
}
return utils.Bool(true), nil
}); err != nil {
return tf.ErrorDiagF(err, "waiting for deletion of Authentication Strength Policy with ID %q", authenticationStrengthPolicyId)
}

return nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package conditionalaccess_test

import (
"context"
"fmt"
"net/http"
"testing"

"github.com/hashicorp/go-azure-sdk/sdk/odata"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
"github.com/hashicorp/terraform-provider-azuread/internal/acceptance"
"github.com/hashicorp/terraform-provider-azuread/internal/acceptance/check"
"github.com/hashicorp/terraform-provider-azuread/internal/clients"
"github.com/hashicorp/terraform-provider-azuread/internal/utils"
)

type AuthenticationStrengthPolicyResource struct{}

func TestAccAuthenticationStrengthPolicy_basic(t *testing.T) {
data := acceptance.BuildTestData(t, "azuread_authentication_strength_policy", "test")
r := AuthenticationStrengthPolicyResource{}

data.ResourceTest(t, r, []resource.TestStep{
{
Config: r.basic(data),
Check: resource.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
data.ImportStep(),
})
}

func TestAccAuthenticationStrengthPolicy_complete(t *testing.T) {
data := acceptance.BuildTestData(t, "azuread_authentication_strength_policy", "test")
r := AuthenticationStrengthPolicyResource{}

data.ResourceTest(t, r, []resource.TestStep{
{
Config: r.complete(data),
Check: resource.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
data.ImportStep(),
})
}

func TestAccAuthenticationStrengthPolicy_update(t *testing.T) {
data := acceptance.BuildTestData(t, "azuread_authentication_strength_policy", "test")
r := AuthenticationStrengthPolicyResource{}

data.ResourceTest(t, r, []resource.TestStep{
{
Config: r.basic(data),
Check: resource.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
data.ImportStep(),
{
Config: r.complete(data),
Check: resource.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
data.ImportStep(),
{
Config: r.basic(data),
Check: resource.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
data.ImportStep(),
})
}

func (r AuthenticationStrengthPolicyResource) Exists(ctx context.Context, clients *clients.Client, state *terraform.InstanceState) (*bool, error) {
var id *string

authstrengthpolicy, status, err := clients.ConditionalAccess.AuthenticationStrengthPoliciesClient.Get(ctx, state.ID, odata.Query{})
if err != nil {
if status == http.StatusNotFound {
return nil, fmt.Errorf("Authentication Strength Policy with ID %q does not exist", state.ID)
}
return nil, fmt.Errorf("failed to retrieve Authentication Strength Policy with ID %q: %+v", state.ID, err)
}
id = authstrengthpolicy.ID

return utils.Bool(id != nil && *id == state.ID), nil
}

func (AuthenticationStrengthPolicyResource) basic(data acceptance.TestData) string {
return fmt.Sprintf(`
resource "azuread_authentication_strength_policy" "test" {
display_name = "acctestASP-%[1]d"
description = "test"
allowed_combinations = ["password"]
}
`, data.RandomInteger)
}

func (AuthenticationStrengthPolicyResource) complete(data acceptance.TestData) string {
return fmt.Sprintf(`
resource "azuread_authentication_strength_policy" "test" {
display_name = "acctestASP-%[1]d"
description = "test"
allowed_combinations = [
"fido2",
"password",
"deviceBasedPush",
"temporaryAccessPassOneTime",
"federatedMultiFactor",
"federatedSingleFactor",
"hardwareOath,federatedSingleFactor",
"microsoftAuthenticatorPush,federatedSingleFactor",
"password,hardwareOath",
"password,microsoftAuthenticatorPush",
"password,sms",
"password,softwareOath",
"password,voice",
"sms",
"sms,federatedSingleFactor",
"softwareOath,federatedSingleFactor",
"temporaryAccessPassMultiUse",
"voice,federatedSingleFactor",
"windowsHelloForBusiness",
"x509CertificateMultiFactor",
"x509CertificateSingleFactor",
]
}
`, data.RandomInteger)
}
Loading

0 comments on commit 5bd6cfd

Please sign in to comment.