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 Aug 15, 2023
1 parent 1e65f1b commit dff78cf
Show file tree
Hide file tree
Showing 12 changed files with 482 additions and 12 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
```
5 changes: 4 additions & 1 deletion docs/resources/conditional_access_policy.md
Original file line number Diff line number Diff line change
Expand Up @@ -227,11 +227,14 @@ The following arguments are supported:

`grant_controls` block supports the following:

* `built_in_controls` - (Required) List of built-in controls required by the policy. Possible values are: `block`, `mfa`, `approvedApplication`, `compliantApplication`, `compliantDevice`, `domainJoinedDevice`, `passwordChange` or `unknownFutureValue`.
* `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.

~> One of `built_in_controls`, `authentication_strength_id` or `terms_of_use` must be specified.

---

`session_controls` block supports the following:
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,5 @@ require (
)

go 1.19

replace github.com/manicminer/hamilton => /home/alexwilcox/go/src/github.com/manicminer/hamilton
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 dff78cf

Please sign in to comment.