Skip to content

Commit

Permalink
feat: Add authentication policy resource (#3098)
Browse files Browse the repository at this point in the history
Added the following resources:
- authentication_policy
- account_authentication_policy_attachment
- user_authentication_policy_attachment

## Test Plan
<!-- detail ways in which this PR has been tested or needs to be tested
-->
* [ ] acceptance tests (have been mostly added; could not be tested
locally due to difficulties with acceptance test setup
<!-- add more below if you think they are relevant -->
* [x] manual tests

## References
<!-- issues documentation links, etc  -->

*
#2880

---------

Co-authored-by: Arkadius Schuchhardt <[email protected]>
  • Loading branch information
Relativity74205 and AS-auxmoney authored Oct 25, 2024
1 parent 64ba674 commit ddea819
Show file tree
Hide file tree
Showing 24 changed files with 1,325 additions and 12 deletions.
35 changes: 35 additions & 0 deletions docs/resources/account_authentication_policy_attachment.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
page_title: "snowflake_account_authentication_policy_attachment Resource - terraform-provider-snowflake"
subcategory: ""
description: |-
Specifies the authentication policy to use for the current account. To set the authentication policy of a different account, use a provider alias.
---

# snowflake_account_authentication_policy_attachment (Resource)

Specifies the authentication policy to use for the current account. To set the authentication policy of a different account, use a provider alias.

## Example Usage

```terraform
resource "snowflake_authentication_policy" "default" {
database = "prod"
schema = "security"
name = "default_policy"
}
resource "snowflake_account_authentication_policy_attachment" "attachment" {
authentication_policy = snowflake_authentication_policy.default.fully_qualified_name
}
```

<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `authentication_policy` (String) Qualified name (`"db"."schema"."policy_name"`) of the authentication policy to apply to the current account.

### Read-Only

- `id` (String) The ID of this resource.
35 changes: 35 additions & 0 deletions docs/resources/authentication_policy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
page_title: "snowflake_authentication_policy Resource - terraform-provider-snowflake"
subcategory: ""
description: |-
---

# snowflake_authentication_policy (Resource)





<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `database` (String) The database in which to create the authentication policy.
- `name` (String) Specifies the identifier for the authentication policy.
- `schema` (String) The schema in which to create the authentication policy.

### Optional

- `authentication_methods` (Set of String) A list of authentication methods that are allowed during login. This parameter accepts one or more of the following values: ALL, SAML, PASSWORD, OAUTH, KEYPAIR.
- `client_types` (Set of String) A list of clients that can authenticate with Snowflake. If a client tries to connect, and the client is not one of the valid CLIENT_TYPES, then the login attempt fails. Allowed values are ALL, SNOWFLAKE_UI, DRIVERS, SNOWSQL. The CLIENT_TYPES property of an authentication policy is a best effort method to block user logins based on specific clients. It should not be used as the sole control to establish a security boundary.
- `comment` (String) Specifies a comment for the authentication policy.
- `mfa_authentication_methods` (Set of String) A list of authentication methods that enforce multi-factor authentication (MFA) during login. Authentication methods not listed in this parameter do not prompt for multi-factor authentication. Allowed values are SAML and PASSWORD.
- `mfa_enrollment` (String) Determines whether a user must enroll in multi-factor authentication. Allowed values are REQUIRED and OPTIONAL. When REQUIRED is specified, Enforces users to enroll in MFA. If this value is used, then the CLIENT_TYPES parameter must include SNOWFLAKE_UI, because Snowsight is the only place users can enroll in multi-factor authentication (MFA).
- `security_integrations` (Set of String) A list of security integrations the authentication policy is associated with. This parameter has no effect when SAML or OAUTH are not in the AUTHENTICATION_METHODS list. All values in the SECURITY_INTEGRATIONS list must be compatible with the values in the AUTHENTICATION_METHODS list. For example, if SECURITY_INTEGRATIONS contains a SAML security integration, and AUTHENTICATION_METHODS contains OAUTH, then you cannot create the authentication policy. To allow all security integrations use ALL as parameter.

### Read-Only

- `fully_qualified_name` (String) Fully qualified name of the resource. For more information, see [object name resolution](https://docs.snowflake.com/en/sql-reference/name-resolution).
- `id` (String) The ID of this resource.
39 changes: 39 additions & 0 deletions docs/resources/user_authentication_policy_attachment.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
---
page_title: "snowflake_user_authentication_policy_attachment Resource - terraform-provider-snowflake"
subcategory: ""
description: |-
Specifies the authentication policy to use for a certain user.
---

# snowflake_user_authentication_policy_attachment (Resource)

Specifies the authentication policy to use for a certain user.

## Example Usage

```terraform
resource "snowflake_user" "user" {
name = "USER_NAME"
}
resource "snowflake_authentication_policy" "ap" {
database = "prod"
schema = "security"
name = "default_policy"
}
resource "snowflake_user_authentication_policy_attachment" "apa" {
authentication_policy_name = snowflake_authentication_policy.ap.fully_qualified_name
user_name = snowflake_user.user.name
}
```
<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `authentication_policy_name` (String) Fully qualified name of the authentication policy
- `user_name` (String) User name of the user you want to attach the authentication policy to

### Read-Only

- `id` (String) The ID of this resource.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
terraform import snowflake_authentication_policy.example '"<database_name>"."<schema_name>"."<authentication_policy_name>"'
19 changes: 19 additions & 0 deletions examples/resources/snowflake_authentication_policy/resource.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
## Minimal
resource "snowflake_authentication_policy" "basic" {
database = "database_name"
schema = "schema_name"
name = "network_policy_name"
}

## Complete (with every optional set)
resource "snowflake_authentication_policy" "complete" {
database = "database_name"
schema = "schema_name"
name = "network_policy_name"
authentication_methods = ["ALL"]
mfa_authentication_methods = ["SAML", "PASSWORD"]
mfa_enrollment = "OPTIONAL"
client_types = ["ALL"]
security_integrations = ["ALL"]
comment = "My authentication policy."
}
2 changes: 1 addition & 1 deletion examples/resources/snowflake_network_policy/resource.tf
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ resource "snowflake_network_policy" "basic" {
}

## Complete (with every optional set)
resource "snowflake_network_policy" "basic" {
resource "snowflake_network_policy" "complete" {
name = "network_policy_name"
allowed_network_rule_list = ["<fully qualified network rule id>"]
blocked_network_rule_list = ["<fully qualified network rule id>"]
Expand Down
27 changes: 27 additions & 0 deletions pkg/acceptance/check_destroy.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ var showByIdFunctions = map[resources.Resource]showByIdFunc{
resources.ApiIntegration: func(ctx context.Context, client *sdk.Client, id sdk.ObjectIdentifier) error {
return runShowById(ctx, id, client.ApiIntegrations.ShowByID)
},
resources.AuthenticationPolicy: func(ctx context.Context, client *sdk.Client, id sdk.ObjectIdentifier) error {
return runShowById(ctx, id, client.AuthenticationPolicies.ShowByID)
},
resources.CortexSearchService: func(ctx context.Context, client *sdk.Client, id sdk.ObjectIdentifier) error {
return runShowById(ctx, id, client.CortexSearchServices.ShowByID)
},
Expand Down Expand Up @@ -457,6 +460,30 @@ func CheckUserPasswordPolicyAttachmentDestroy(t *testing.T) func(*terraform.Stat
}
}

// CheckUserAuthenticationPolicyAttachmentDestroy is a custom checks that should be later incorporated into generic CheckDestroy
func CheckUserAuthenticationPolicyAttachmentDestroy(t *testing.T) func(*terraform.State) error {
t.Helper()
return func(s *terraform.State) error {
for _, rs := range s.RootModule().Resources {
if rs.Type != "snowflake_user_authentication_policy_attachment" {
continue
}
policyReferences, err := TestClient().PolicyReferences.GetPolicyReferences(t, sdk.NewAccountObjectIdentifierFromFullyQualifiedName(rs.Primary.Attributes["user_name"]), sdk.PolicyEntityDomainUser)
if err != nil {
if strings.Contains(err.Error(), "does not exist or not authorized") {
// Note: this can happen if the Policy Reference or the User has been deleted as well; in this case, ignore the error
continue
}
return err
}
if len(policyReferences) > 0 {
return fmt.Errorf("user authentication policy attachment %v still exists", policyReferences[0].PolicyName)
}
}
return nil
}
}

func TestAccCheckGrantApplicationRoleDestroy(s *terraform.State) error {
client := TestAccProvider.Meta().(*provider.Context).Client
for _, rs := range s.RootModule().Resources {
Expand Down
13 changes: 8 additions & 5 deletions pkg/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -421,15 +421,17 @@ func Provider() *schema.Provider {

func getResources() map[string]*schema.Resource {
return map[string]*schema.Resource{
"snowflake_account": resources.Account(),
"snowflake_account_role": resources.AccountRole(),
"snowflake_account_password_policy_attachment": resources.AccountPasswordPolicyAttachment(),
"snowflake_account_parameter": resources.AccountParameter(),
"snowflake_alert": resources.Alert(),
"snowflake_account": resources.Account(),
"snowflake_account_authentication_policy_attachment": resources.AccountAuthenticationPolicyAttachment(),
"snowflake_account_role": resources.AccountRole(),
"snowflake_account_password_policy_attachment": resources.AccountPasswordPolicyAttachment(),
"snowflake_account_parameter": resources.AccountParameter(),
"snowflake_alert": resources.Alert(),
"snowflake_api_authentication_integration_with_authorization_code_grant": resources.ApiAuthenticationIntegrationWithAuthorizationCodeGrant(),
"snowflake_api_authentication_integration_with_client_credentials": resources.ApiAuthenticationIntegrationWithClientCredentials(),
"snowflake_api_authentication_integration_with_jwt_bearer": resources.ApiAuthenticationIntegrationWithJwtBearer(),
"snowflake_api_integration": resources.APIIntegration(),
"snowflake_authentication_policy": resources.AuthenticationPolicy(),
"snowflake_cortex_search_service": resources.CortexSearchService(),
"snowflake_database_old": resources.DatabaseOld(),
"snowflake_database": resources.Database(),
Expand Down Expand Up @@ -499,6 +501,7 @@ func getResources() map[string]*schema.Resource {
"snowflake_task": resources.Task(),
"snowflake_unsafe_execute": resources.UnsafeExecute(),
"snowflake_user": resources.User(),
"snowflake_user_authentication_policy_attachment": resources.UserAuthenticationPolicyAttachment(),
"snowflake_user_password_policy_attachment": resources.UserPasswordPolicyAttachment(),
"snowflake_user_public_keys": resources.UserPublicKeys(),
"snowflake_view": resources.View(),
Expand Down
1 change: 1 addition & 0 deletions pkg/provider/resources/resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const (
ApiAuthenticationIntegrationWithClientCredentials resource = "snowflake_api_authentication_integration_with_client_credentials"
ApiAuthenticationIntegrationWithJwtBearer resource = "snowflake_api_authentication_integration_with_jwt_bearer"
ApiIntegration resource = "snowflake_api_integration"
AuthenticationPolicy resource = "snowflake_authentication_policy"
CortexSearchService resource = "snowflake_cortex_search_service"
DatabaseOld resource = "snowflake_database_old"
Database resource = "snowflake_database"
Expand Down
88 changes: 88 additions & 0 deletions pkg/resources/account_authentication_policy_attachment.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package resources

import (
"context"
"fmt"

"github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/provider"

"github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/helpers"
"github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

var accountAuthenticationPolicyAttachmentSchema = map[string]*schema.Schema{
"authentication_policy": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: "Qualified name (`\"db\".\"schema\".\"policy_name\"`) of the authentication policy to apply to the current account.",
ValidateDiagFunc: IsValidIdentifier[sdk.SchemaObjectIdentifier](),
},
}

// AccountAuthenticationPolicyAttachment returns a pointer to the resource representing an account authentication policy attachment.
func AccountAuthenticationPolicyAttachment() *schema.Resource {
return &schema.Resource{
Description: "Specifies the authentication policy to use for the current account. To set the authentication policy of a different account, use a provider alias.",

Create: CreateAccountAuthenticationPolicyAttachment,
Read: ReadAccountAuthenticationPolicyAttachment,
Delete: DeleteAccountAuthenticationPolicyAttachment,

Schema: accountAuthenticationPolicyAttachmentSchema,
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
}
}

// CreateAccountAuthenticationPolicyAttachment implements schema.CreateFunc.
func CreateAccountAuthenticationPolicyAttachment(d *schema.ResourceData, meta interface{}) error {
client := meta.(*provider.Context).Client
ctx := context.Background()

authenticationPolicy, ok := sdk.NewObjectIdentifierFromFullyQualifiedName(d.Get("authentication_policy").(string)).(sdk.SchemaObjectIdentifier)
if !ok {
return fmt.Errorf("authentication_policy %s is not a valid authentication policy qualified name, expected format: `\"db\".\"schema\".\"policy\"`", d.Get("authentication_policy"))
}

err := client.Accounts.Alter(ctx, &sdk.AlterAccountOptions{
Set: &sdk.AccountSet{
AuthenticationPolicy: authenticationPolicy,
},
})
if err != nil {
return err
}

d.SetId(helpers.EncodeSnowflakeID(authenticationPolicy))

return ReadAccountAuthenticationPolicyAttachment(d, meta)
}

func ReadAccountAuthenticationPolicyAttachment(d *schema.ResourceData, meta interface{}) error {
authenticationPolicy := helpers.DecodeSnowflakeID(d.Id())
if err := d.Set("authentication_policy", authenticationPolicy.FullyQualifiedName()); err != nil {
return err
}

return nil
}

// DeleteAccountAuthenticationPolicyAttachment implements schema.DeleteFunc.
func DeleteAccountAuthenticationPolicyAttachment(d *schema.ResourceData, meta interface{}) error {
client := meta.(*provider.Context).Client
ctx := context.Background()

err := client.Accounts.Alter(ctx, &sdk.AlterAccountOptions{
Unset: &sdk.AccountUnset{
AuthenticationPolicy: sdk.Bool(true),
},
})
if err != nil {
return err
}

return nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package resources_test

import (
"fmt"
"testing"

acc "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance"

"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/hashicorp/terraform-plugin-testing/tfversion"
)

func TestAcc_AccountAuthenticationPolicyAttachment(t *testing.T) {
policyName := acc.TestClient().Ids.Alpha()

resource.Test(t, resource.TestCase{
ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories,
PreCheck: func() { acc.TestAccPreCheck(t) },
TerraformVersionChecks: []tfversion.TerraformVersionCheck{
tfversion.RequireAbove(tfversion.Version1_5_0),
},
CheckDestroy: nil,
Steps: []resource.TestStep{
{
Config: accountAuthenticationPolicyAttachmentConfig(acc.TestDatabaseName, acc.TestSchemaName, policyName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttrSet("snowflake_account_authentication_policy_attachment.att", "id"),
),
},
{
ResourceName: "snowflake_account_authentication_policy_attachment.att",
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func accountAuthenticationPolicyAttachmentConfig(databaseName, schemaName, policyName string) string {
s := `
resource "snowflake_authentication_policy" "pa" {
database = "%s"
schema = "%s"
name = "%v"
}
resource "snowflake_account_authentication_policy_attachment" "att" {
authentication_policy = snowflake_authentication_policy.pa.fully_qualified_name
}
`
return fmt.Sprintf(s, databaseName, schemaName, policyName)
}
2 changes: 1 addition & 1 deletion pkg/resources/account_password_policy_attachment.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ var accountPasswordPolicyAttachmentSchema = map[string]*schema.Schema{
},
}

// AccountPasswordPolicyAttachment returns a pointer to the resource representing an api integration.
// AccountPasswordPolicyAttachment returns a pointer to the resource representing an account password policy attachment.
func AccountPasswordPolicyAttachment() *schema.Resource {
return &schema.Resource{
Description: "Specifies the password policy to use for the current account. To set the password policy of a different account, use a provider alias.",
Expand Down
Loading

0 comments on commit ddea819

Please sign in to comment.