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 support for azuread_authentication_strength_policy #1171

Merged
merged 4 commits into from
Oct 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
```
7 changes: 4 additions & 3 deletions docs/resources/conditional_access_policy.md
Original file line number Diff line number Diff line change
Expand Up @@ -227,24 +227,25 @@ The following arguments are supported:

`grant_controls` block supports the following:

* `authentication_strength_policy_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 `authentication_strength_policy_id`, `built_in_controls` or `terms_of_use` must be specified.

---

`session_controls` block supports the following:

* `application_enforced_restrictions_enabled` - (Optional) Whether or not application enforced restrictions are enabled. Defaults to `false`.
* `application_enforced_restrictions_enabled` - (Optional) Whether application enforced restrictions are enabled. Defaults to `false`.

-> Only Office 365, Exchange Online and Sharepoint Online support application enforced restrictions.

* `cloud_app_security_policy` - (Optional) Enables cloud app security and specifies the cloud app security policy to use. Possible values are: `blockDownloads`, `mcasConfigured`, `monitorOnly` or `unknownFutureValue`.
* `disable_resilience_defaults` - (Optional) Disables [resilience defaults](https://learn.microsoft.com/en-us/azure/active-directory/conditional-access/resilience-defaults). Defaults to `false`.
* `persistent_browser_mode` - (Optional) Session control to define whether to persist cookies or not. Possible values are: `always` or `never`.
* `persistent_browser_mode` - (Optional) Session control to define whether to persist cookies. Possible values are: `always` or `never`.
* `sign_in_frequency` - (Optional) Number of days or hours to enforce sign-in frequency. Required when `sign_in_frequency_period` is specified. Due to an API issue, removing this property forces a new resource to be created.
* `sign_in_frequency_period` - (Optional) The time period to enforce sign-in frequency. Possible values are: `hours` or `days`. Required when `sign_in_frequency_period` is specified. Due to an API issue, removing this property forces a new resource to be created.

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-helpers/lang/pointer"
"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-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/tf/pluginsdk"
"github.com/hashicorp/terraform-provider-azuread/internal/tf/validation"
"github.com/manicminer/hamilton/msgraph"
)

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

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

Importer: pluginsdk.ImporterValidatingResourceId(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]*pluginsdk.Schema{
"display_name": {
Description: "The display name for the authentication strength policy",
Type: pluginsdk.TypeString,
Required: true,
ValidateFunc: validation.StringIsNotEmpty,
},

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

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

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

properties := msgraph.AuthenticationStrengthPolicy{
DisplayName: pointer.To(d.Get("display_name").(string)),
Description: pointer.To(d.Get("description").(string)),
AllowedCombinations: tf.ExpandStringSlicePtr(d.Get("allowed_combinations").(*pluginsdk.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 *pluginsdk.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*clients.Client).ConditionalAccess.AuthenticationStrengthPoliciesClient

properties := msgraph.AuthenticationStrengthPolicy{
ID: pointer.To(d.Id()),
DisplayName: pointer.To(d.Get("display_name").(string)),
Description: pointer.To(d.Get("description").(string)),
// AllowedCombinations: tf.ExpandStringSlicePtr(d.Get("allowed_combinations").(*pluginsdk.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").(*pluginsdk.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 *pluginsdk.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 *pluginsdk.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 pointer.To(false), nil
}
return nil, err
}
return pointer.To(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,136 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package conditionalaccess_test

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

"github.com/hashicorp/go-azure-helpers/lang/pointer"
"github.com/hashicorp/go-azure-sdk/sdk/odata"
"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/tf/pluginsdk"
)

type AuthenticationStrengthPolicyResource struct{}

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

data.ResourceTest(t, r, []acceptance.TestStep{
{
Config: r.basic(data),
Check: acceptance.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, []acceptance.TestStep{
{
Config: r.complete(data),
Check: acceptance.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, []acceptance.TestStep{
{
Config: r.basic(data),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
data.ImportStep(),
{
Config: r.complete(data),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
data.ImportStep(),
{
Config: r.basic(data),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
data.ImportStep(),
})
}

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

authstrengthpolicy, status, err := client.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 pointer.To(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
Loading