From e65a335fa1d9b7a9e4db8e5d7323f03a06dfe857 Mon Sep 17 00:00:00 2001 From: Arkadius Schuchhardt Date: Fri, 20 Sep 2024 22:22:39 +0200 Subject: [PATCH 01/15] feat: added authentication policy resource (WIP, test missing and adding to account user missing) --- pkg/resources/authentication_policy.go | 392 +++++++++++++++++++++++++ 1 file changed, 392 insertions(+) create mode 100644 pkg/resources/authentication_policy.go diff --git a/pkg/resources/authentication_policy.go b/pkg/resources/authentication_policy.go new file mode 100644 index 0000000000..601e68b74f --- /dev/null +++ b/pkg/resources/authentication_policy.go @@ -0,0 +1,392 @@ +package resources + +import ( + "context" + "errors" + "fmt" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/helpers" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/collections" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/provider" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/schemas" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +var authenticationPolicySchema = map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + Description: "Specifies the identifier for the authentication policy.", + ForceNew: true, + }, + "schema": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "The schema in which to create the authentication policy.", + }, + "database": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "The database in which to create the authentication policy.", + }, + "authentication_methods": { + Type: schema.TypeSet, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateDiagFunc: StringInSlice([]string{"ALL", "SAML", "PASSWORD", "OAUTH", "KEYPAIR"}, false), + }, + Optional: true, + Description: "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.", + Default: []string{"ALL"}, + }, + "mfa_authentication_methods": { + Type: schema.TypeSet, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateDiagFunc: StringInSlice([]string{"SAML", "PASSWORD"}, false), + }, + Optional: true, + Description: "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.", + Default: []string{"SAML", "PASSWORD"}, + }, + "mfa_enrollment": { + Type: schema.TypeString, + Optional: true, + Description: "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).", + ValidateFunc: validation.StringInSlice([]string{"REQUIRED", "OPTIONAL"}, false), + Default: "OPTIONAL", + }, + "client_types": { + Type: schema.TypeSet, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateDiagFunc: StringInSlice([]string{"ALL", "SNOWFLAKE_UI", "DRIVERS", "SNOWSQL"}, false), + }, + Optional: true, + Description: "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.", + Default: []string{"ALL"}, + }, + "security_integrations": { + Type: schema.TypeSet, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Optional: true, + Description: "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.", + Default: []string{"ALL"}, + }, + "comment": { + Type: schema.TypeString, + Optional: true, + Description: "Specifies a comment for the network rule.", + }, + FullyQualifiedNameAttributeName: schemas.FullyQualifiedNameSchema, +} + +// AuthenticationPolicy returns a pointer to the resource representing an authentication policy. +func AuthenticationPolicy() *schema.Resource { + return &schema.Resource{ + CreateContext: CreateContextAuthenticationPolicy, + ReadContext: ReadContextAuthenticationPolicy, + UpdateContext: UpdateContextAuthenticationPolicy, + DeleteContext: DeleteContextAuthenticationPolicy, + + Schema: authenticationPolicySchema, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + } +} + +func CreateContextAuthenticationPolicy(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + name := d.Get("name").(string) + databaseName := d.Get("database").(string) + schemaName := d.Get("schema").(string) + id := sdk.NewSchemaObjectIdentifier(databaseName, schemaName, name) + + req := sdk.NewCreateAuthenticationPolicyRequest(id) + + // Set optionals + authenticationMethodsRawList := expandStringList(d.Get("authentication_methods").(*schema.Set).List()) + authenticationMethods := make([]sdk.AuthenticationMethods, len(authenticationMethodsRawList)) + for i, v := range authenticationMethodsRawList { + authenticationMethods[i] = sdk.AuthenticationMethods{Method: sdk.AuthenticationMethodsOption(v)} + } + req.WithAuthenticationMethods(authenticationMethods) + + mfaAuthenticationMethodsRawList := expandStringList(d.Get("mfa_authentication_methods").(*schema.Set).List()) + mfaAuthenticationMethods := make([]sdk.MfaAuthenticationMethods, len(mfaAuthenticationMethodsRawList)) + for i, v := range authenticationMethodsRawList { + mfaAuthenticationMethods[i] = sdk.MfaAuthenticationMethods{Method: sdk.MfaAuthenticationMethodsOption(v)} + } + req.WithMfaAuthenticationMethods(mfaAuthenticationMethods) + + if v, ok := d.GetOk("mfa_enrollment"); ok { + req = req.WithMfaEnrollment(sdk.MfaEnrollmentOption(v.(string))) + } + + clientTypesRawList := expandStringList(d.Get("client_types").(*schema.Set).List()) + clientTypes := make([]sdk.ClientTypes, len(clientTypesRawList)) + for i, v := range authenticationMethodsRawList { + clientTypes[i] = sdk.ClientTypes{ClientType: sdk.ClientTypesOption(v)} + } + req.WithClientTypes(clientTypes) + + securityIntegrationsRawList := expandStringList(d.Get("security_integrations").(*schema.Set).List()) + securityIntegrations := make([]sdk.SecurityIntegrationsOption, len(securityIntegrationsRawList)) + for i, v := range authenticationMethodsRawList { + securityIntegrations[i] = sdk.SecurityIntegrationsOption{Name: sdk.NewAccountObjectIdentifier(v)} + } + req.WithSecurityIntegrations(securityIntegrations) + + if v, ok := d.GetOk("comment"); ok { + req = req.WithComment(v.(string)) + } + + client := meta.(*provider.Context).Client + if err := client.AuthenticationPolicies.Create(ctx, req); err != nil { + return diag.FromErr(err) + } + d.SetId(helpers.EncodeSnowflakeID(id)) + + return ReadContextAuthenticationPolicy(ctx, d, meta) +} + +func ReadContextAuthenticationPolicy(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + diags := diag.Diagnostics{} + client := meta.(*provider.Context).Client + id := helpers.DecodeSnowflakeID(d.Id()).(sdk.SchemaObjectIdentifier) + + authenticationPolicy, err := client.AuthenticationPolicies.ShowByID(ctx, id) + if authenticationPolicy == nil || err != nil { + if errors.Is(err, sdk.ErrObjectNotFound) { + d.SetId("") + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Warning, + Summary: "Failed to retrieve authentication policy. Target object not found. Marking the resource as removed.", + Detail: fmt.Sprintf("Id: %s", d.Id()), + }, + } + } + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "Failed to retrieve authentication policy", + Detail: fmt.Sprintf("Id: %s\nError: %s", d.Id(), err), + }, + } + } + + authenticationPolicyDescriptions, err := client.AuthenticationPolicies.Describe(ctx, id) + if err != nil { + return diag.FromErr(err) + } + if err = d.Set("name", authenticationPolicy.Name); err != nil { + return diag.FromErr(err) + } + if err = d.Set("database", authenticationPolicy.DatabaseName); err != nil { + return diag.FromErr(err) + } + if err = d.Set("schema", authenticationPolicy.SchemaName); err != nil { + return diag.FromErr(err) + } + + authenticationMethods := make([]string, 0) + if authenticationMethodsProperty, err := collections.FindFirst(authenticationPolicyDescriptions, func(prop sdk.AuthenticationPolicyDescription) bool { return prop.Property == "AUTHENTICATION_METHODS" }); err == nil { + authenticationMethods = append(authenticationMethods, sdk.ParseCommaSeparatedStringArray(authenticationMethodsProperty.Value, false)...) + } + if err = d.Set("authentication_methods", authenticationMethods); err != nil { + return diag.FromErr(err) + } + + mfaAuthenticationMethods := make([]string, 0) + if mfaAuthenticationMethodsProperty, err := collections.FindFirst(authenticationPolicyDescriptions, func(prop sdk.AuthenticationPolicyDescription) bool { + return prop.Property == "MFA_AUTHENTICATION_METHODS" + }); err == nil { + mfaAuthenticationMethods = append(mfaAuthenticationMethods, sdk.ParseCommaSeparatedStringArray(mfaAuthenticationMethodsProperty.Value, false)...) + } + if err = d.Set("authentication_methods", mfaAuthenticationMethods); err != nil { + return diag.FromErr(err) + } + + mfaEnrollment, err := collections.FindFirst(authenticationPolicyDescriptions, func(prop sdk.AuthenticationPolicyDescription) bool { return prop.Property == "MFA_ENROLLMENT" }) + if err == nil { + if err = d.Set("mfa_enrollment", mfaEnrollment.Value); err != nil { + return diag.FromErr(err) + } + } + + clientTypes := make([]string, 0) + if clientTypesProperty, err := collections.FindFirst(authenticationPolicyDescriptions, func(prop sdk.AuthenticationPolicyDescription) bool { return prop.Property == "CLIENT_TYPES" }); err == nil { + clientTypes = append(clientTypes, sdk.ParseCommaSeparatedStringArray(clientTypesProperty.Value, false)...) + } + if err = d.Set("client_types", clientTypes); err != nil { + return diag.FromErr(err) + } + + securityIntegrations := make([]string, 0) + if securityIntegrationsProperty, err := collections.FindFirst(authenticationPolicyDescriptions, func(prop sdk.AuthenticationPolicyDescription) bool { return prop.Property == "SECURITY_INTEGRATIONS" }); err == nil { + securityIntegrations = append(securityIntegrations, sdk.ParseCommaSeparatedStringArray(securityIntegrationsProperty.Value, false)...) + } + if err = d.Set("security_integrations", securityIntegrations); err != nil { + return diag.FromErr(err) + } + + if err = d.Set("comment", authenticationPolicy.Comment); err != nil { + return diag.FromErr(err) + } + if err := d.Set(FullyQualifiedNameAttributeName, id.FullyQualifiedName()); err != nil { + return diag.FromErr(err) + } + + return diags +} + +func UpdateContextAuthenticationPolicy(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*provider.Context).Client + id := helpers.DecodeSnowflakeID(d.Id()).(sdk.SchemaObjectIdentifier) + + // change to authentication methods + if d.HasChange("authentication_methods") { + authenticationMethods := expandStringList(d.Get("authentication_methods").(*schema.Set).List()) + authenticationMethodsValues := make([]sdk.AuthenticationMethods, len(authenticationMethods)) + for i, v := range authenticationMethods { + authenticationMethodsValues[i] = sdk.AuthenticationMethods{Method: sdk.AuthenticationMethodsOption(v)} + } + + baseReq := sdk.NewAlterAuthenticationPolicyRequest(id) + if len(authenticationMethodsValues) == 0 { + unsetReq := sdk.NewAuthenticationPolicyUnsetRequest().WithAuthenticationMethods(*sdk.Bool(true)) + baseReq.WithUnset(*unsetReq) + } else { + setReq := sdk.NewAuthenticationPolicySetRequest().WithAuthenticationMethods(authenticationMethodsValues) + baseReq.WithSet(*setReq) + } + + if err := client.AuthenticationPolicies.Alter(ctx, baseReq); err != nil { + return diag.FromErr(err) + } + } + + // change to mfa authentication methods + if d.HasChange("mfa_authentication_methods") { + mfaAuthenticationMethods := expandStringList(d.Get("mfa_authentication_methods").(*schema.Set).List()) + mfaAuthenticationMethodsValues := make([]sdk.MfaAuthenticationMethods, len(mfaAuthenticationMethods)) + for i, v := range mfaAuthenticationMethods { + mfaAuthenticationMethodsValues[i] = sdk.MfaAuthenticationMethods{Method: sdk.MfaAuthenticationMethodsOption(v)} + } + + baseReq := sdk.NewAlterAuthenticationPolicyRequest(id) + if len(mfaAuthenticationMethodsValues) == 0 { + unsetReq := sdk.NewAuthenticationPolicyUnsetRequest().WithMfaAuthenticationMethods(*sdk.Bool(true)) + baseReq.WithUnset(*unsetReq) + } else { + setReq := sdk.NewAuthenticationPolicySetRequest().WithMfaAuthenticationMethods(mfaAuthenticationMethodsValues) + baseReq.WithSet(*setReq) + } + + if err := client.AuthenticationPolicies.Alter(ctx, baseReq); err != nil { + return diag.FromErr(err) + } + } + + // change to mfa enrollment + if d.HasChange("mfa_enrollment") { + mfaEnrollment := d.Get("mfa_enrollment").(string) + + baseReq := sdk.NewAlterAuthenticationPolicyRequest(id) + if len(mfaEnrollment) == 0 { + unsetReq := sdk.NewAuthenticationPolicyUnsetRequest().WithMfaEnrollment(*sdk.Bool(true)) + baseReq.WithUnset(*unsetReq) + } else { + setReq := sdk.NewAuthenticationPolicySetRequest().WithMfaEnrollment(sdk.MfaEnrollmentOption(mfaEnrollment)) + baseReq.WithSet(*setReq) + } + + if err := client.AuthenticationPolicies.Alter(ctx, baseReq); err != nil { + return diag.FromErr(err) + } + } + + // change to client types + if d.HasChange("client_types") { + clientTypes := expandStringList(d.Get("client_types").(*schema.Set).List()) + clientTypesValues := make([]sdk.ClientTypes, len(clientTypes)) + for i, v := range clientTypes { + clientTypesValues[i] = sdk.ClientTypes{ClientType: sdk.ClientTypesOption(v)} + } + + baseReq := sdk.NewAlterAuthenticationPolicyRequest(id) + if len(clientTypesValues) == 0 { + unsetReq := sdk.NewAuthenticationPolicyUnsetRequest().WithClientTypes(*sdk.Bool(true)) + baseReq.WithUnset(*unsetReq) + } else { + setReq := sdk.NewAuthenticationPolicySetRequest().WithClientTypes(clientTypesValues) + baseReq.WithSet(*setReq) + } + + if err := client.AuthenticationPolicies.Alter(ctx, baseReq); err != nil { + return diag.FromErr(err) + } + } + + // change to security integrations + if d.HasChange("security_integrations") { + securityIntegrations := expandStringList(d.Get("security_integrations").(*schema.Set).List()) + securityIntegrationsValues := make([]sdk.SecurityIntegrationsOption, len(securityIntegrations)) + for i, v := range securityIntegrations { + securityIntegrationsValues[i] = sdk.SecurityIntegrationsOption{Name: sdk.NewAccountObjectIdentifier(v)} + } + + baseReq := sdk.NewAlterAuthenticationPolicyRequest(id) + if len(securityIntegrationsValues) == 0 { + unsetReq := sdk.NewAuthenticationPolicyUnsetRequest().WithSecurityIntegrations(*sdk.Bool(true)) + baseReq.WithUnset(*unsetReq) + } else { + setReq := sdk.NewAuthenticationPolicySetRequest().WithSecurityIntegrations(securityIntegrationsValues) + baseReq.WithSet(*setReq) + } + + if err := client.AuthenticationPolicies.Alter(ctx, baseReq); err != nil { + return diag.FromErr(err) + } + } + + // change to comment + if d.HasChange("comment") { + comment := d.Get("comment").(string) + baseReq := sdk.NewAlterAuthenticationPolicyRequest(id) + if len(comment) == 0 { + unsetReq := sdk.NewAuthenticationPolicyUnsetRequest().WithComment(*sdk.Bool(true)) + baseReq.WithUnset(*unsetReq) + } else { + setReq := sdk.NewAuthenticationPolicySetRequest().WithComment(comment) + baseReq.WithSet(*setReq) + } + + if err := client.AuthenticationPolicies.Alter(ctx, baseReq); err != nil { + return diag.FromErr(err) + } + } + + return ReadContextAuthenticationPolicy(ctx, d, meta) +} + +func DeleteContextAuthenticationPolicy(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + name := d.Id() + client := meta.(*provider.Context).Client + id := helpers.DecodeSnowflakeID(name).(sdk.SchemaObjectIdentifier) + + if err := client.AuthenticationPolicies.Drop(ctx, sdk.NewDropAuthenticationPolicyRequest(id).WithIfExists(*sdk.Bool(true))); err != nil { + diag.FromErr(err) + } + + d.SetId("") + return nil +} From be647f852a61fbba6d310434036055d6dc40ea0c Mon Sep 17 00:00:00 2001 From: Arkadius Schuchhardt Date: Sat, 21 Sep 2024 08:04:40 +0200 Subject: [PATCH 02/15] feat: added authentication_policy attachments and added first tests --- pkg/acceptance/check_destroy.go | 24 ++++ pkg/provider/provider.go | 13 +- ...ccount_authentication_policy_attachment.go | 88 ++++++++++++ ...ation_policy_attachment_acceptance_test.go | 60 ++++++++ .../account_password_policy_attachment.go | 2 +- ...sword_policy_attachment_acceptance_test.go | 8 +- pkg/resources/authentication_policy.go | 2 +- .../user_authentication_policy_attachment.go | 134 ++++++++++++++++++ ...ation_policy_attachment_acceptance_test.go | 74 ++++++++++ .../user_password_policy_attachment.go | 1 + 10 files changed, 395 insertions(+), 11 deletions(-) create mode 100644 pkg/resources/account_authentication_policy_attachment.go create mode 100644 pkg/resources/account_authentication_policy_attachment_acceptance_test.go create mode 100644 pkg/resources/user_authentication_policy_attachment.go create mode 100644 pkg/resources/user_authentication_policy_attachment_acceptance_test.go diff --git a/pkg/acceptance/check_destroy.go b/pkg/acceptance/check_destroy.go index c39bd71174..4ed1be7ec6 100644 --- a/pkg/acceptance/check_destroy.go +++ b/pkg/acceptance/check_destroy.go @@ -435,6 +435,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 { diff --git a/pkg/provider/provider.go b/pkg/provider/provider.go index cb28e3eba6..31e4405387 100644 --- a/pkg/provider/provider.go +++ b/pkg/provider/provider.go @@ -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(), @@ -496,6 +498,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(), diff --git a/pkg/resources/account_authentication_policy_attachment.go b/pkg/resources/account_authentication_policy_attachment.go new file mode 100644 index 0000000000..628cee492c --- /dev/null +++ b/pkg/resources/account_authentication_policy_attachment.go @@ -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 +} diff --git a/pkg/resources/account_authentication_policy_attachment_acceptance_test.go b/pkg/resources/account_authentication_policy_attachment_acceptance_test.go new file mode 100644 index 0000000000..833fdad631 --- /dev/null +++ b/pkg/resources/account_authentication_policy_attachment_acceptance_test.go @@ -0,0 +1,60 @@ +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, + ImportStateVerifyIgnore: []string{ + "initially_suspended", + "wait_for_provisioning", + "query_acceleration_max_scale_factor", + "max_concurrency_level", + "statement_queued_timeout_in_seconds", + "statement_timeout_in_seconds", + }, + }, + }, + }) +} + +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) +} diff --git a/pkg/resources/account_password_policy_attachment.go b/pkg/resources/account_password_policy_attachment.go index f362ff1629..245b2d33c2 100644 --- a/pkg/resources/account_password_policy_attachment.go +++ b/pkg/resources/account_password_policy_attachment.go @@ -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.", diff --git a/pkg/resources/account_password_policy_attachment_acceptance_test.go b/pkg/resources/account_password_policy_attachment_acceptance_test.go index dc43401416..a2ff233595 100644 --- a/pkg/resources/account_password_policy_attachment_acceptance_test.go +++ b/pkg/resources/account_password_policy_attachment_acceptance_test.go @@ -11,7 +11,7 @@ import ( ) func TestAcc_AccountPasswordPolicyAttachment(t *testing.T) { - prefix := acc.TestClient().Ids.Alpha() + policyName := acc.TestClient().Ids.Alpha() resource.Test(t, resource.TestCase{ ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, @@ -22,7 +22,7 @@ func TestAcc_AccountPasswordPolicyAttachment(t *testing.T) { CheckDestroy: nil, Steps: []resource.TestStep{ { - Config: accountPasswordPolicyAttachmentConfig(acc.TestDatabaseName, acc.TestSchemaName, prefix), + Config: accountPasswordPolicyAttachmentConfig(acc.TestDatabaseName, acc.TestSchemaName, policyName), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttrSet("snowflake_account_password_policy_attachment.att", "id"), ), @@ -44,7 +44,7 @@ func TestAcc_AccountPasswordPolicyAttachment(t *testing.T) { }) } -func accountPasswordPolicyAttachmentConfig(databaseName, schemaName, prefix string) string { +func accountPasswordPolicyAttachmentConfig(databaseName, schemaName, policyName string) string { s := ` resource "snowflake_password_policy" "pa" { database = "%s" @@ -56,5 +56,5 @@ resource "snowflake_account_password_policy_attachment" "att" { password_policy = snowflake_password_policy.pa.fully_qualified_name } ` - return fmt.Sprintf(s, databaseName, schemaName, prefix) + return fmt.Sprintf(s, databaseName, schemaName, policyName) } diff --git a/pkg/resources/authentication_policy.go b/pkg/resources/authentication_policy.go index 601e68b74f..7aeabf2a65 100644 --- a/pkg/resources/authentication_policy.go +++ b/pkg/resources/authentication_policy.go @@ -83,7 +83,7 @@ var authenticationPolicySchema = map[string]*schema.Schema{ "comment": { Type: schema.TypeString, Optional: true, - Description: "Specifies a comment for the network rule.", + Description: "Specifies a comment for the authentication policy.", }, FullyQualifiedNameAttributeName: schemas.FullyQualifiedNameSchema, } diff --git a/pkg/resources/user_authentication_policy_attachment.go b/pkg/resources/user_authentication_policy_attachment.go new file mode 100644 index 0000000000..c29b54880f --- /dev/null +++ b/pkg/resources/user_authentication_policy_attachment.go @@ -0,0 +1,134 @@ +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 userAuthenticationPolicyAttachmentSchema = map[string]*schema.Schema{ + "user_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "User name of the user you want to attach the authentication policy to", + ValidateDiagFunc: IsValidIdentifier[sdk.AccountObjectIdentifier](), + }, + "authentication_policy_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "Fully qualified name of the authentication policy", + ValidateDiagFunc: IsValidIdentifier[sdk.SchemaObjectIdentifier](), + }, +} + +// UserAuthenticationPolicyAttachment returns a pointer to the resource representing a user authentication policy attachment. +func UserAuthenticationPolicyAttachment() *schema.Resource { + return &schema.Resource{ + Description: "Specifies the authentication policy to use for a certain user.", + Create: CreateUserAuthenticationPolicyAttachment, + Read: ReadUserAuthenticationPolicyAttachment, + Delete: DeleteUserAuthenticationPolicyAttachment, + Schema: userAuthenticationPolicyAttachmentSchema, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + } +} + +func CreateUserAuthenticationPolicyAttachment(d *schema.ResourceData, meta any) error { + client := meta.(*provider.Context).Client + ctx := context.Background() + + userName := sdk.NewAccountObjectIdentifierFromFullyQualifiedName(d.Get("user_name").(string)) + authenticationPolicy := sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(d.Get("authentication_policy_name").(string)) + + err := client.Users.Alter(ctx, userName, &sdk.AlterUserOptions{ + Set: &sdk.UserSet{ + AuthenticationPolicy: &authenticationPolicy, + }, + }) + if err != nil { + return err + } + + d.SetId(helpers.EncodeResourceIdentifier(userName.FullyQualifiedName(), authenticationPolicy.FullyQualifiedName())) + + return ReadUserAuthenticationPolicyAttachment(d, meta) +} + +func ReadUserAuthenticationPolicyAttachment(d *schema.ResourceData, meta any) error { + client := meta.(*provider.Context).Client + ctx := context.Background() + + parts := helpers.ParseResourceIdentifier(d.Id()) + if len(parts) != 2 { + return fmt.Errorf("required id format 'user_name|authentication_policy_name', but got: '%s'", d.Id()) + } + + // Note: there is no alphanumeric id for an attachment, so we retrieve the authentication policies attached to a certain user. + userName := sdk.NewAccountObjectIdentifierFromFullyQualifiedName(parts[0]) + policyReferences, err := client.PolicyReferences.GetForEntity(ctx, sdk.NewGetForEntityPolicyReferenceRequest(userName, sdk.PolicyEntityDomainUser)) + if err != nil { + return err + } + + authenticationPolicyReferences := make([]sdk.PolicyReference, 0) + for _, policyReference := range policyReferences { + if policyReference.PolicyKind == sdk.PolicyKindAuthenticationPolicy { + authenticationPolicyReferences = append(authenticationPolicyReferences, policyReference) + } + } + + // Note: this should never happen, but just in case: so far, Snowflake only allows one Authentication Policy per user. + if len(authenticationPolicyReferences) > 1 { + return fmt.Errorf("internal error: multiple policy references attached to a user. This should never happen") + } + + // Note: this means the resource has been deleted outside of Terraform. + if len(authenticationPolicyReferences) == 0 { + d.SetId("") + return nil + } + + if err := d.Set("user_name", userName.Name()); err != nil { + return err + } + if err := d.Set( + "authentication_policy_name", + sdk.NewSchemaObjectIdentifier( + *authenticationPolicyReferences[0].PolicyDb, + *authenticationPolicyReferences[0].PolicySchema, + authenticationPolicyReferences[0].PolicyName, + ).FullyQualifiedName()); err != nil { + return err + } + + return err +} + +func DeleteUserAuthenticationPolicyAttachment(d *schema.ResourceData, meta any) error { + client := meta.(*provider.Context).Client + ctx := context.Background() + + userName := sdk.NewAccountObjectIdentifierFromFullyQualifiedName(d.Get("user_name").(string)) + + err := client.Users.Alter(ctx, userName, &sdk.AlterUserOptions{ + Unset: &sdk.UserUnset{ + AuthenticationPolicy: sdk.Bool(true), + }, + }) + if err != nil { + return err + } + + d.SetId("") + + return nil +} diff --git a/pkg/resources/user_authentication_policy_attachment_acceptance_test.go b/pkg/resources/user_authentication_policy_attachment_acceptance_test.go new file mode 100644 index 0000000000..f4d10cd34b --- /dev/null +++ b/pkg/resources/user_authentication_policy_attachment_acceptance_test.go @@ -0,0 +1,74 @@ +package resources_test + +import ( + "fmt" + "testing" + + acc "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAcc_UserAuthenticationPolicyAttachment(t *testing.T) { + // TODO [SNOW-1423486]: unskip + t.Skipf("Skip because error %s; will be fixed in SNOW-1423486", "Error: 000606 (57P03): No active warehouse selected in the current session. Select an active warehouse with the 'use warehouse' command.") + userId := acc.TestClient().Ids.RandomAccountObjectIdentifier() + userName := userId.Name() + newUserId := acc.TestClient().Ids.RandomAccountObjectIdentifier() + newUserName := newUserId.Name() + authenticationPolicyId := acc.TestClient().Ids.RandomSchemaObjectIdentifier() + authenticationPolicyName := authenticationPolicyId.Name() + newAuthenticationPolicyId := acc.TestClient().Ids.RandomSchemaObjectIdentifier() + newAuthenticationPolicyName := newAuthenticationPolicyId.Name() + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + CheckDestroy: acc.CheckUserAuthenticationPolicyAttachmentDestroy(t), + Steps: []resource.TestStep{ + // CREATE + { + Config: userAuthenticationPolicyAttachmentConfig(userName, acc.TestDatabaseName, acc.TestSchemaName, authenticationPolicyName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("snowflake_user_authentication_policy_attachment.ppa", "user_name", userName), + resource.TestCheckResourceAttr("snowflake_user_authentication_policy_attachment.ppa", "authentication_policy_name", authenticationPolicyId.FullyQualifiedName()), + resource.TestCheckResourceAttr("snowflake_user_authentication_policy_attachment.ppa", "id", fmt.Sprintf("%s|%s", userId.FullyQualifiedName(), authenticationPolicyId.FullyQualifiedName())), + ), + }, + // UPDATE + { + Config: userAuthenticationPolicyAttachmentConfig(newUserName, acc.TestDatabaseName, acc.TestSchemaName, newAuthenticationPolicyName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("snowflake_user_authentication_policy_attachment.ppa", "user_name", newUserName), + resource.TestCheckResourceAttr("snowflake_user_authentication_policy_attachment.ppa", "authentication_policy_name", newAuthenticationPolicyId.FullyQualifiedName()), + resource.TestCheckResourceAttr("snowflake_user_authentication_policy_attachment.ppa", "id", fmt.Sprintf("%s|%s", userId.FullyQualifiedName(), newAuthenticationPolicyId.FullyQualifiedName())), + ), + }, + // IMPORT + { + ResourceName: "snowflake_user_authentication_policy_attachment.ppa", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func userAuthenticationPolicyAttachmentConfig(userName, databaseName, schemaName, authenticationPolicyName string) string { + return fmt.Sprintf(` +resource "snowflake_user" "user" { + name = "%s" +} + +resource "snowflake_authentication_policy" "pp" { + database = "%s" + schema = "%s" + name = "%s" +} + +resource "snowflake_user_authentication_policy_attachment" "ppa" { + authentication_policy_name = snowflake_authentication_policy.pp.fully_qualified_name + user_name = snowflake_user.user.name +} +`, userName, databaseName, schemaName, authenticationPolicyName) +} diff --git a/pkg/resources/user_password_policy_attachment.go b/pkg/resources/user_password_policy_attachment.go index 5ec96deebb..96bac9523a 100644 --- a/pkg/resources/user_password_policy_attachment.go +++ b/pkg/resources/user_password_policy_attachment.go @@ -28,6 +28,7 @@ var userPasswordPolicyAttachmentSchema = map[string]*schema.Schema{ }, } +// UserPasswordPolicyAttachment returns a pointer to the resource representing a user password policy attachment. func UserPasswordPolicyAttachment() *schema.Resource { return &schema.Resource{ Description: "Specifies the password policy to use for a certain user.", From 930ec4dc44743ffe4a620b42384374ff11257c2b Mon Sep 17 00:00:00 2001 From: Arkadius Schuchhardt Date: Sat, 21 Sep 2024 21:42:04 +0200 Subject: [PATCH 03/15] feat: finalized authentication_policy incl. attachments --- ...ccount_authentication_policy_attachment.md | 35 +++++++++ docs/resources/authentication_policy.md | 35 +++++++++ .../user_authentication_policy_attachment.md | 39 ++++++++++ pkg/provider/resources/resources.go | 1 + pkg/resources/authentication_policy.go | 12 +-- .../authentication_policy_acceptance_test.go | 78 +++++++++++++++++++ .../TestAcc_AuthenticationPolicy/test.tf | 11 +++ .../TestAcc_AuthenticationPolicy/variables.tf | 35 +++++++++ ...ation_policy_attachment_acceptance_test.go | 6 +- 9 files changed, 241 insertions(+), 11 deletions(-) create mode 100644 docs/resources/account_authentication_policy_attachment.md create mode 100644 docs/resources/authentication_policy.md create mode 100644 docs/resources/user_authentication_policy_attachment.md create mode 100644 pkg/resources/authentication_policy_acceptance_test.go create mode 100644 pkg/resources/testdata/TestAcc_AuthenticationPolicy/test.tf create mode 100644 pkg/resources/testdata/TestAcc_AuthenticationPolicy/variables.tf diff --git a/docs/resources/account_authentication_policy_attachment.md b/docs/resources/account_authentication_policy_attachment.md new file mode 100644 index 0000000000..40a93a5ba1 --- /dev/null +++ b/docs/resources/account_authentication_policy_attachment.md @@ -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 + +### 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. diff --git a/docs/resources/authentication_policy.md b/docs/resources/authentication_policy.md new file mode 100644 index 0000000000..ec117e944c --- /dev/null +++ b/docs/resources/authentication_policy.md @@ -0,0 +1,35 @@ +--- +page_title: "snowflake_authentication_policy Resource - terraform-provider-snowflake" +subcategory: "" +description: |- + +--- + +# snowflake_authentication_policy (Resource) + + + + + + +## 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. diff --git a/docs/resources/user_authentication_policy_attachment.md b/docs/resources/user_authentication_policy_attachment.md new file mode 100644 index 0000000000..c4fe77a998 --- /dev/null +++ b/docs/resources/user_authentication_policy_attachment.md @@ -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 + +### 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. diff --git a/pkg/provider/resources/resources.go b/pkg/provider/resources/resources.go index 6730843d38..a34bbd4f4b 100644 --- a/pkg/provider/resources/resources.go +++ b/pkg/provider/resources/resources.go @@ -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" diff --git a/pkg/resources/authentication_policy.go b/pkg/resources/authentication_policy.go index 7aeabf2a65..756c079f04 100644 --- a/pkg/resources/authentication_policy.go +++ b/pkg/resources/authentication_policy.go @@ -42,7 +42,6 @@ var authenticationPolicySchema = map[string]*schema.Schema{ }, Optional: true, Description: "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.", - Default: []string{"ALL"}, }, "mfa_authentication_methods": { Type: schema.TypeSet, @@ -52,7 +51,6 @@ var authenticationPolicySchema = map[string]*schema.Schema{ }, Optional: true, Description: "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.", - Default: []string{"SAML", "PASSWORD"}, }, "mfa_enrollment": { Type: schema.TypeString, @@ -69,7 +67,6 @@ var authenticationPolicySchema = map[string]*schema.Schema{ }, Optional: true, Description: "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.", - Default: []string{"ALL"}, }, "security_integrations": { Type: schema.TypeSet, @@ -78,7 +75,6 @@ var authenticationPolicySchema = map[string]*schema.Schema{ }, Optional: true, Description: "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.", - Default: []string{"ALL"}, }, "comment": { Type: schema.TypeString, @@ -121,7 +117,7 @@ func CreateContextAuthenticationPolicy(ctx context.Context, d *schema.ResourceDa mfaAuthenticationMethodsRawList := expandStringList(d.Get("mfa_authentication_methods").(*schema.Set).List()) mfaAuthenticationMethods := make([]sdk.MfaAuthenticationMethods, len(mfaAuthenticationMethodsRawList)) - for i, v := range authenticationMethodsRawList { + for i, v := range mfaAuthenticationMethodsRawList { mfaAuthenticationMethods[i] = sdk.MfaAuthenticationMethods{Method: sdk.MfaAuthenticationMethodsOption(v)} } req.WithMfaAuthenticationMethods(mfaAuthenticationMethods) @@ -132,14 +128,14 @@ func CreateContextAuthenticationPolicy(ctx context.Context, d *schema.ResourceDa clientTypesRawList := expandStringList(d.Get("client_types").(*schema.Set).List()) clientTypes := make([]sdk.ClientTypes, len(clientTypesRawList)) - for i, v := range authenticationMethodsRawList { + for i, v := range clientTypesRawList { clientTypes[i] = sdk.ClientTypes{ClientType: sdk.ClientTypesOption(v)} } req.WithClientTypes(clientTypes) securityIntegrationsRawList := expandStringList(d.Get("security_integrations").(*schema.Set).List()) securityIntegrations := make([]sdk.SecurityIntegrationsOption, len(securityIntegrationsRawList)) - for i, v := range authenticationMethodsRawList { + for i, v := range securityIntegrationsRawList { securityIntegrations[i] = sdk.SecurityIntegrationsOption{Name: sdk.NewAccountObjectIdentifier(v)} } req.WithSecurityIntegrations(securityIntegrations) @@ -211,7 +207,7 @@ func ReadContextAuthenticationPolicy(ctx context.Context, d *schema.ResourceData }); err == nil { mfaAuthenticationMethods = append(mfaAuthenticationMethods, sdk.ParseCommaSeparatedStringArray(mfaAuthenticationMethodsProperty.Value, false)...) } - if err = d.Set("authentication_methods", mfaAuthenticationMethods); err != nil { + if err = d.Set("mfa_authentication_methods", mfaAuthenticationMethods); err != nil { return diag.FromErr(err) } diff --git a/pkg/resources/authentication_policy_acceptance_test.go b/pkg/resources/authentication_policy_acceptance_test.go new file mode 100644 index 0000000000..00338069f5 --- /dev/null +++ b/pkg/resources/authentication_policy_acceptance_test.go @@ -0,0 +1,78 @@ +package resources_test + +import ( + "testing" + + acc "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/provider/resources" + "github.com/hashicorp/terraform-plugin-testing/config" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/tfversion" +) + +func TestAcc_AuthenticationPolicy(t *testing.T) { + accName := acc.TestClient().Ids.Alpha() + comment := "This is a test resource" + m := func(authenticationMethods []string, mfaAuthenticationMethods []string, mfaEnrollment string, clientTypes []string, securityIntegrations []string) map[string]config.Variable { + authenticationMethodsStringVariables := make([]config.Variable, len(authenticationMethods)) + for i, v := range authenticationMethods { + authenticationMethodsStringVariables[i] = config.StringVariable(v) + } + mfaAuthenticationMethodsStringVariables := make([]config.Variable, len(mfaAuthenticationMethods)) + for i, v := range mfaAuthenticationMethods { + mfaAuthenticationMethodsStringVariables[i] = config.StringVariable(v) + } + clientTypesStringVariables := make([]config.Variable, len(clientTypes)) + for i, v := range clientTypes { + clientTypesStringVariables[i] = config.StringVariable(v) + } + securityIntegrationsStringVariables := make([]config.Variable, len(securityIntegrations)) + for i, v := range securityIntegrations { + securityIntegrationsStringVariables[i] = config.StringVariable(v) + } + + return map[string]config.Variable{ + "name": config.StringVariable(accName), + "database": config.StringVariable(acc.TestDatabaseName), + "schema": config.StringVariable(acc.TestSchemaName), + "authentication_methods": config.SetVariable(authenticationMethodsStringVariables...), + "mfa_authentication_methods": config.SetVariable(mfaAuthenticationMethodsStringVariables...), + "mfa_enrollment": config.StringVariable(mfaEnrollment), + "client_types": config.SetVariable(clientTypesStringVariables...), + "security_integrations": config.SetVariable(securityIntegrationsStringVariables...), + "comment": config.StringVariable(comment), + } + } + variables1 := m([]string{"PASSWORD"}, []string{"PASSWORD"}, "REQUIRED", []string{"SNOWFLAKE_UI"}, []string{"ALL"}) + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + CheckDestroy: acc.CheckDestroy(t, resources.AuthenticationPolicy), + Steps: []resource.TestStep{ + { + ConfigDirectory: config.TestNameDirectory(), + ConfigVariables: variables1, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("snowflake_password_policy.pa", "name", accName), + resource.TestCheckResourceAttr("snowflake_password_policy.pa", "authentication_methods.0", "PASSWORD"), + resource.TestCheckResourceAttr("snowflake_password_policy.pa", "mfa_authentication_methods.0", "PASSWORD"), + resource.TestCheckResourceAttr("snowflake_password_policy.pa", "mfa_enrollment", "REQUIRED"), + resource.TestCheckResourceAttr("snowflake_password_policy.pa", "client_types.0", "SNOWFLAKE_UI"), + resource.TestCheckResourceAttr("snowflake_password_policy.pa", "security_integrations.0", "ALL"), + resource.TestCheckResourceAttr("snowflake_password_policy.pa", "comment", comment), + ), + }, + { + ConfigDirectory: config.TestNameDirectory(), + ConfigVariables: variables1, + ResourceName: "snowflake_authentication_policy.authentication_policy", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} diff --git a/pkg/resources/testdata/TestAcc_AuthenticationPolicy/test.tf b/pkg/resources/testdata/TestAcc_AuthenticationPolicy/test.tf new file mode 100644 index 0000000000..b78725f2d7 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_AuthenticationPolicy/test.tf @@ -0,0 +1,11 @@ +resource "snowflake_authentication_policy" "authentication_policy" { + name = var.name + database = var.database + schema = var.schema + authentication_methods = var.authentication_methods + mfa_authentication_methods = var.mfa_authentication_methods + mfa_enrollment = var.mfa_enrollment + client_types = var.client_types + security_integrations = var.security_integrations + comment = var.comment +} \ No newline at end of file diff --git a/pkg/resources/testdata/TestAcc_AuthenticationPolicy/variables.tf b/pkg/resources/testdata/TestAcc_AuthenticationPolicy/variables.tf new file mode 100644 index 0000000000..4453375033 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_AuthenticationPolicy/variables.tf @@ -0,0 +1,35 @@ +variable "name" { + type = string +} + +variable "database" { + type = string +} + +variable "schema" { + type = string +} + +variable "authentication_methods" { + type = set(string) +} + +variable "mfa_authentication_methods" { + type = set(string) +} + +variable "mfa_enrollment" { + type = string +} + +variable "client_types" { + type = set(string) +} + +variable "security_integrations" { + type = set(string) +} + +variable "comment" { + type = string +} diff --git a/pkg/resources/user_authentication_policy_attachment_acceptance_test.go b/pkg/resources/user_authentication_policy_attachment_acceptance_test.go index f4d10cd34b..99222e2179 100644 --- a/pkg/resources/user_authentication_policy_attachment_acceptance_test.go +++ b/pkg/resources/user_authentication_policy_attachment_acceptance_test.go @@ -60,14 +60,14 @@ resource "snowflake_user" "user" { name = "%s" } -resource "snowflake_authentication_policy" "pp" { +resource "snowflake_authentication_policy" "ap" { database = "%s" schema = "%s" name = "%s" } -resource "snowflake_user_authentication_policy_attachment" "ppa" { - authentication_policy_name = snowflake_authentication_policy.pp.fully_qualified_name +resource "snowflake_user_authentication_policy_attachment" "apa" { + authentication_policy_name = snowflake_authentication_policy.ap.fully_qualified_name user_name = snowflake_user.user.name } `, userName, databaseName, schemaName, authenticationPolicyName) From 56e0b51ac05470ff13a8c636c4a5aae6b32fcd4d Mon Sep 17 00:00:00 2001 From: Arkadius Schuchhardt Date: Thu, 3 Oct 2024 15:15:37 +0200 Subject: [PATCH 04/15] test: fix --- ...count_password_policy_attachment_acceptance_test.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pkg/resources/account_password_policy_attachment_acceptance_test.go b/pkg/resources/account_password_policy_attachment_acceptance_test.go index a2ff233595..fa42811b22 100644 --- a/pkg/resources/account_password_policy_attachment_acceptance_test.go +++ b/pkg/resources/account_password_policy_attachment_acceptance_test.go @@ -4,6 +4,8 @@ import ( "fmt" "testing" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + acc "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance" "github.com/hashicorp/terraform-plugin-testing/helper/resource" @@ -11,7 +13,7 @@ import ( ) func TestAcc_AccountPasswordPolicyAttachment(t *testing.T) { - policyName := acc.TestClient().Ids.Alpha() + id := acc.TestClient().Ids.RandomSchemaObjectIdentifier() resource.Test(t, resource.TestCase{ ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, @@ -22,7 +24,7 @@ func TestAcc_AccountPasswordPolicyAttachment(t *testing.T) { CheckDestroy: nil, Steps: []resource.TestStep{ { - Config: accountPasswordPolicyAttachmentConfig(acc.TestDatabaseName, acc.TestSchemaName, policyName), + Config: accountPasswordPolicyAttachmentConfig(id), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttrSet("snowflake_account_password_policy_attachment.att", "id"), ), @@ -44,7 +46,7 @@ func TestAcc_AccountPasswordPolicyAttachment(t *testing.T) { }) } -func accountPasswordPolicyAttachmentConfig(databaseName, schemaName, policyName string) string { +func accountPasswordPolicyAttachmentConfig(id sdk.SchemaObjectIdentifier) string { s := ` resource "snowflake_password_policy" "pa" { database = "%s" @@ -56,5 +58,5 @@ resource "snowflake_account_password_policy_attachment" "att" { password_policy = snowflake_password_policy.pa.fully_qualified_name } ` - return fmt.Sprintf(s, databaseName, schemaName, policyName) + return fmt.Sprintf(s, id.DatabaseName(), id.SchemaName(), id.Name()) } From 8bb9ccbbb4d4bed509ef4e6b6e3db71890261a36 Mon Sep 17 00:00:00 2001 From: Arkadius Schuchhardt Date: Thu, 3 Oct 2024 15:20:15 +0200 Subject: [PATCH 05/15] fix: CR --- pkg/resources/authentication_policy.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/pkg/resources/authentication_policy.go b/pkg/resources/authentication_policy.go index 756c079f04..a53a7b740b 100644 --- a/pkg/resources/authentication_policy.go +++ b/pkg/resources/authentication_policy.go @@ -19,20 +19,20 @@ var authenticationPolicySchema = map[string]*schema.Schema{ "name": { Type: schema.TypeString, Required: true, - Description: "Specifies the identifier for the authentication policy.", + Description: blocklistedCharactersFieldDescription("Specifies the identifier for the authentication policy."), ForceNew: true, }, "schema": { Type: schema.TypeString, Required: true, ForceNew: true, - Description: "The schema in which to create the authentication policy.", + Description: blocklistedCharactersFieldDescription("The schema in which to create the authentication policy."), }, "database": { Type: schema.TypeString, Required: true, ForceNew: true, - Description: "The database in which to create the authentication policy.", + Description: blocklistedCharactersFieldDescription("The database in which to create the authentication policy."), }, "authentication_methods": { Type: schema.TypeSet, @@ -156,7 +156,10 @@ func CreateContextAuthenticationPolicy(ctx context.Context, d *schema.ResourceDa func ReadContextAuthenticationPolicy(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { diags := diag.Diagnostics{} client := meta.(*provider.Context).Client - id := helpers.DecodeSnowflakeID(d.Id()).(sdk.SchemaObjectIdentifier) + id, err := sdk.ParseSchemaObjectIdentifier(d.Id()) + if err != nil { + return diag.FromErr(err) + } authenticationPolicy, err := client.AuthenticationPolicies.ShowByID(ctx, id) if authenticationPolicy == nil || err != nil { From dfe03ff194cb71467a02efd67c5448fa60f4ba56 Mon Sep 17 00:00:00 2001 From: Arkadius Schuchhardt Date: Thu, 3 Oct 2024 22:46:22 +0200 Subject: [PATCH 06/15] fix: a lot of fixed/refactorings from CR --- pkg/resources/authentication_policy.go | 305 +++++++++++++------------ 1 file changed, 164 insertions(+), 141 deletions(-) diff --git a/pkg/resources/authentication_policy.go b/pkg/resources/authentication_policy.go index a53a7b740b..1d75944ff8 100644 --- a/pkg/resources/authentication_policy.go +++ b/pkg/resources/authentication_policy.go @@ -6,33 +6,37 @@ import ( "fmt" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/helpers" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/collections" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/logging" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/provider" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/schemas" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "reflect" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) var authenticationPolicySchema = map[string]*schema.Schema{ "name": { - Type: schema.TypeString, - Required: true, - Description: blocklistedCharactersFieldDescription("Specifies the identifier for the authentication policy."), - ForceNew: true, + Type: schema.TypeString, + Required: true, + Description: blocklistedCharactersFieldDescription("Specifies the identifier for the authentication policy."), + DiffSuppressFunc: suppressIdentifierQuoting, }, "schema": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - Description: blocklistedCharactersFieldDescription("The schema in which to create the authentication policy."), + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: blocklistedCharactersFieldDescription("The schema in which to create the authentication policy."), + DiffSuppressFunc: suppressIdentifierQuoting, }, "database": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - Description: blocklistedCharactersFieldDescription("The database in which to create the authentication policy."), + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: blocklistedCharactersFieldDescription("The database in which to create the authentication policy."), + DiffSuppressFunc: suppressIdentifierQuoting, }, "authentication_methods": { Type: schema.TypeSet, @@ -41,7 +45,7 @@ var authenticationPolicySchema = map[string]*schema.Schema{ ValidateDiagFunc: StringInSlice([]string{"ALL", "SAML", "PASSWORD", "OAUTH", "KEYPAIR"}, false), }, Optional: true, - Description: "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.", + Description: fmt.Sprintf("A list of authentication methods that are allowed during login. This parameter accepts one or more of the following values: %s", possibleValuesListed(sdk.AllAuthenticationMethods)), }, "mfa_authentication_methods": { Type: schema.TypeSet, @@ -50,7 +54,7 @@ var authenticationPolicySchema = map[string]*schema.Schema{ ValidateDiagFunc: StringInSlice([]string{"SAML", "PASSWORD"}, false), }, Optional: true, - Description: "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.", + Description: fmt.Sprintf("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 %s.", possibleValuesListed(sdk.AllMfaAuthenticationMethods)), }, "mfa_enrollment": { Type: schema.TypeString, @@ -66,7 +70,7 @@ var authenticationPolicySchema = map[string]*schema.Schema{ ValidateDiagFunc: StringInSlice([]string{"ALL", "SNOWFLAKE_UI", "DRIVERS", "SNOWSQL"}, false), }, Optional: true, - Description: "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.", + Description: fmt.Sprintf("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 %s. 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.", possibleValuesListed(sdk.AllClientTypes)), }, "security_integrations": { Type: schema.TypeSet, @@ -94,11 +98,38 @@ func AuthenticationPolicy() *schema.Resource { Schema: authenticationPolicySchema, Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, + StateContext: ImportAuthenticationPolicy, }, } } +func ImportAuthenticationPolicy(ctx context.Context, d *schema.ResourceData, meta any) ([]*schema.ResourceData, error) { + logging.DebugLogger.Printf("[DEBUG] Starting authentication policy import") + client := meta.(*provider.Context).Client + + id, err := sdk.ParseSchemaObjectIdentifier(d.Id()) + if err != nil { + return nil, err + } + + authenticationPolicy, err := client.AuthenticationPolicies.ShowByID(ctx, id) + if err != nil { + return nil, err + } + + if err = d.Set("name", authenticationPolicy.Name); err != nil { + return nil, err + } + if err = d.Set("database", authenticationPolicy.DatabaseName); err != nil { + return nil, err + } + if err = d.Set("schema", authenticationPolicy.SchemaName); err != nil { + return nil, err + } + + return []*schema.ResourceData{d}, nil +} + func CreateContextAuthenticationPolicy(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { name := d.Get("name").(string) databaseName := d.Get("database").(string) @@ -108,37 +139,45 @@ func CreateContextAuthenticationPolicy(ctx context.Context, d *schema.ResourceDa req := sdk.NewCreateAuthenticationPolicyRequest(id) // Set optionals - authenticationMethodsRawList := expandStringList(d.Get("authentication_methods").(*schema.Set).List()) - authenticationMethods := make([]sdk.AuthenticationMethods, len(authenticationMethodsRawList)) - for i, v := range authenticationMethodsRawList { - authenticationMethods[i] = sdk.AuthenticationMethods{Method: sdk.AuthenticationMethodsOption(v)} + if v, ok := d.GetOk("authentication_methods"); ok { + authenticationMethodsRawList := expandStringList(v.(*schema.Set).List()) + authenticationMethods := make([]sdk.AuthenticationMethods, len(authenticationMethodsRawList)) + for i, v := range authenticationMethodsRawList { + authenticationMethods[i] = sdk.AuthenticationMethods{Method: sdk.AuthenticationMethodsOption(v)} + } + req.WithAuthenticationMethods(authenticationMethods) } - req.WithAuthenticationMethods(authenticationMethods) - mfaAuthenticationMethodsRawList := expandStringList(d.Get("mfa_authentication_methods").(*schema.Set).List()) - mfaAuthenticationMethods := make([]sdk.MfaAuthenticationMethods, len(mfaAuthenticationMethodsRawList)) - for i, v := range mfaAuthenticationMethodsRawList { - mfaAuthenticationMethods[i] = sdk.MfaAuthenticationMethods{Method: sdk.MfaAuthenticationMethodsOption(v)} + if v, ok := d.GetOk("mfa_authentication_methods"); ok { + mfaAuthenticationMethodsRawList := expandStringList(v.(*schema.Set).List()) + mfaAuthenticationMethods := make([]sdk.MfaAuthenticationMethods, len(mfaAuthenticationMethodsRawList)) + for i, v := range mfaAuthenticationMethodsRawList { + mfaAuthenticationMethods[i] = sdk.MfaAuthenticationMethods{Method: sdk.MfaAuthenticationMethodsOption(v)} + } + req.WithMfaAuthenticationMethods(mfaAuthenticationMethods) } - req.WithMfaAuthenticationMethods(mfaAuthenticationMethods) if v, ok := d.GetOk("mfa_enrollment"); ok { req = req.WithMfaEnrollment(sdk.MfaEnrollmentOption(v.(string))) } - clientTypesRawList := expandStringList(d.Get("client_types").(*schema.Set).List()) - clientTypes := make([]sdk.ClientTypes, len(clientTypesRawList)) - for i, v := range clientTypesRawList { - clientTypes[i] = sdk.ClientTypes{ClientType: sdk.ClientTypesOption(v)} + if v, ok := d.GetOk("client_types"); ok { + clientTypesRawList := expandStringList(v.(*schema.Set).List()) + clientTypes := make([]sdk.ClientTypes, len(clientTypesRawList)) + for i, v := range clientTypesRawList { + clientTypes[i] = sdk.ClientTypes{ClientType: sdk.ClientTypesOption(v)} + } + req.WithClientTypes(clientTypes) } - req.WithClientTypes(clientTypes) - securityIntegrationsRawList := expandStringList(d.Get("security_integrations").(*schema.Set).List()) - securityIntegrations := make([]sdk.SecurityIntegrationsOption, len(securityIntegrationsRawList)) - for i, v := range securityIntegrationsRawList { - securityIntegrations[i] = sdk.SecurityIntegrationsOption{Name: sdk.NewAccountObjectIdentifier(v)} + if v, ok := d.GetOk("security_integrations"); ok { + securityIntegrationsRawList := expandStringList(v.(*schema.Set).List()) + securityIntegrations := make([]sdk.SecurityIntegrationsOption, len(securityIntegrationsRawList)) + for i, v := range securityIntegrationsRawList { + securityIntegrations[i] = sdk.SecurityIntegrationsOption{Name: sdk.NewAccountObjectIdentifier(v)} + } + req.WithSecurityIntegrations(securityIntegrations) } - req.WithSecurityIntegrations(securityIntegrations) if v, ok := d.GetOk("comment"); ok { req = req.WithComment(v.(string)) @@ -148,7 +187,7 @@ func CreateContextAuthenticationPolicy(ctx context.Context, d *schema.ResourceDa if err := client.AuthenticationPolicies.Create(ctx, req); err != nil { return diag.FromErr(err) } - d.SetId(helpers.EncodeSnowflakeID(id)) + d.SetId(helpers.EncodeResourceIdentifier(id)) return ReadContextAuthenticationPolicy(ctx, d, meta) } @@ -162,7 +201,7 @@ func ReadContextAuthenticationPolicy(ctx context.Context, d *schema.ResourceData } authenticationPolicy, err := client.AuthenticationPolicies.ShowByID(ctx, id) - if authenticationPolicy == nil || err != nil { + if err != nil { if errors.Is(err, sdk.ErrObjectNotFound) { d.SetId("") return diag.Diagnostics{ @@ -173,28 +212,13 @@ func ReadContextAuthenticationPolicy(ctx context.Context, d *schema.ResourceData }, } } - return diag.Diagnostics{ - diag.Diagnostic{ - Severity: diag.Error, - Summary: "Failed to retrieve authentication policy", - Detail: fmt.Sprintf("Id: %s\nError: %s", d.Id(), err), - }, - } + return diag.FromErr(err) } authenticationPolicyDescriptions, err := client.AuthenticationPolicies.Describe(ctx, id) if err != nil { return diag.FromErr(err) } - if err = d.Set("name", authenticationPolicy.Name); err != nil { - return diag.FromErr(err) - } - if err = d.Set("database", authenticationPolicy.DatabaseName); err != nil { - return diag.FromErr(err) - } - if err = d.Set("schema", authenticationPolicy.SchemaName); err != nil { - return diag.FromErr(err) - } authenticationMethods := make([]string, 0) if authenticationMethodsProperty, err := collections.FindFirst(authenticationPolicyDescriptions, func(prop sdk.AuthenticationPolicyDescription) bool { return prop.Property == "AUTHENTICATION_METHODS" }); err == nil { @@ -249,127 +273,124 @@ func ReadContextAuthenticationPolicy(ctx context.Context, d *schema.ResourceData func UpdateContextAuthenticationPolicy(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { client := meta.(*provider.Context).Client - id := helpers.DecodeSnowflakeID(d.Id()).(sdk.SchemaObjectIdentifier) + id, err := sdk.ParseSchemaObjectIdentifier(d.Id()) + if err != nil { + return diag.FromErr(err) + } - // change to authentication methods - if d.HasChange("authentication_methods") { - authenticationMethods := expandStringList(d.Get("authentication_methods").(*schema.Set).List()) - authenticationMethodsValues := make([]sdk.AuthenticationMethods, len(authenticationMethods)) - for i, v := range authenticationMethods { - authenticationMethodsValues[i] = sdk.AuthenticationMethods{Method: sdk.AuthenticationMethodsOption(v)} - } + set, unset := sdk.NewAuthenticationPolicySetRequest(), sdk.NewAuthenticationPolicyUnsetRequest() - baseReq := sdk.NewAlterAuthenticationPolicyRequest(id) - if len(authenticationMethodsValues) == 0 { - unsetReq := sdk.NewAuthenticationPolicyUnsetRequest().WithAuthenticationMethods(*sdk.Bool(true)) - baseReq.WithUnset(*unsetReq) - } else { - setReq := sdk.NewAuthenticationPolicySetRequest().WithAuthenticationMethods(authenticationMethodsValues) - baseReq.WithSet(*setReq) + // change to name + if d.HasChange("name") { + newId, err := sdk.ParseSchemaObjectIdentifier(d.Get("name").(string)) + if err != nil { + return diag.FromErr(err) } - if err := client.AuthenticationPolicies.Alter(ctx, baseReq); err != nil { + err = client.AuthenticationPolicies.Alter(ctx, sdk.NewAlterAuthenticationPolicyRequest(id).WithRenameTo(newId)) + if err != nil { return diag.FromErr(err) } + + d.SetId(helpers.EncodeResourceIdentifier(newId)) + id = newId } - // change to mfa authentication methods - if d.HasChange("mfa_authentication_methods") { - mfaAuthenticationMethods := expandStringList(d.Get("mfa_authentication_methods").(*schema.Set).List()) - mfaAuthenticationMethodsValues := make([]sdk.MfaAuthenticationMethods, len(mfaAuthenticationMethods)) - for i, v := range mfaAuthenticationMethods { - mfaAuthenticationMethodsValues[i] = sdk.MfaAuthenticationMethods{Method: sdk.MfaAuthenticationMethodsOption(v)} + // change to authentication methods + if d.HasChange("authentication_methods") { + if v, ok := d.GetOk("authentication_methods"); ok { + authenticationMethods := expandStringList(v.(*schema.Set).List()) + authenticationMethodsValues := make([]sdk.AuthenticationMethods, len(authenticationMethods)) + for i, v := range authenticationMethods { + authenticationMethodsValues[i] = sdk.AuthenticationMethods{Method: sdk.AuthenticationMethodsOption(v)} + } + if len(authenticationMethodsValues) == 0 { + unset.AuthenticationMethods = sdk.Bool(true) + } else { + set.AuthenticationMethods = authenticationMethodsValues + } } + } - baseReq := sdk.NewAlterAuthenticationPolicyRequest(id) - if len(mfaAuthenticationMethodsValues) == 0 { - unsetReq := sdk.NewAuthenticationPolicyUnsetRequest().WithMfaAuthenticationMethods(*sdk.Bool(true)) - baseReq.WithUnset(*unsetReq) - } else { - setReq := sdk.NewAuthenticationPolicySetRequest().WithMfaAuthenticationMethods(mfaAuthenticationMethodsValues) - baseReq.WithSet(*setReq) - } + // change to mfa authentication methods + if d.HasChange("mfa_authentication_methods") { + if v, ok := d.GetOk("mfa_authentication_methods"); ok { + mfaAuthenticationMethods := expandStringList(v.(*schema.Set).List()) + mfaAuthenticationMethodsValues := make([]sdk.MfaAuthenticationMethods, len(mfaAuthenticationMethods)) + for i, v := range mfaAuthenticationMethods { + mfaAuthenticationMethodsValues[i] = sdk.MfaAuthenticationMethods{Method: sdk.MfaAuthenticationMethodsOption(v)} + } - if err := client.AuthenticationPolicies.Alter(ctx, baseReq); err != nil { - return diag.FromErr(err) + if len(mfaAuthenticationMethodsValues) == 0 { + unset.MfaAuthenticationMethods = sdk.Bool(true) + } else { + set.MfaAuthenticationMethods = mfaAuthenticationMethodsValues + } } } // change to mfa enrollment if d.HasChange("mfa_enrollment") { - mfaEnrollment := d.Get("mfa_enrollment").(string) - - baseReq := sdk.NewAlterAuthenticationPolicyRequest(id) - if len(mfaEnrollment) == 0 { - unsetReq := sdk.NewAuthenticationPolicyUnsetRequest().WithMfaEnrollment(*sdk.Bool(true)) - baseReq.WithUnset(*unsetReq) + if v, ok := d.GetOk("mfa_enrollment"); ok { + set.MfaEnrollment = sdk.Pointer(sdk.MfaEnrollmentOption(v.(string))) } else { - setReq := sdk.NewAuthenticationPolicySetRequest().WithMfaEnrollment(sdk.MfaEnrollmentOption(mfaEnrollment)) - baseReq.WithSet(*setReq) - } - - if err := client.AuthenticationPolicies.Alter(ctx, baseReq); err != nil { - return diag.FromErr(err) + unset.MfaEnrollment = sdk.Bool(true) } } // change to client types if d.HasChange("client_types") { - clientTypes := expandStringList(d.Get("client_types").(*schema.Set).List()) - clientTypesValues := make([]sdk.ClientTypes, len(clientTypes)) - for i, v := range clientTypes { - clientTypesValues[i] = sdk.ClientTypes{ClientType: sdk.ClientTypesOption(v)} - } - - baseReq := sdk.NewAlterAuthenticationPolicyRequest(id) - if len(clientTypesValues) == 0 { - unsetReq := sdk.NewAuthenticationPolicyUnsetRequest().WithClientTypes(*sdk.Bool(true)) - baseReq.WithUnset(*unsetReq) - } else { - setReq := sdk.NewAuthenticationPolicySetRequest().WithClientTypes(clientTypesValues) - baseReq.WithSet(*setReq) - } + if v, ok := d.GetOk("client_types"); ok { + clientTypes := expandStringList(v.(*schema.Set).List()) + clientTypesValues := make([]sdk.ClientTypes, len(clientTypes)) + for i, v := range clientTypes { + clientTypesValues[i] = sdk.ClientTypes{ClientType: sdk.ClientTypesOption(v)} + } - if err := client.AuthenticationPolicies.Alter(ctx, baseReq); err != nil { - return diag.FromErr(err) + if len(clientTypesValues) == 0 { + unset.ClientTypes = sdk.Bool(true) + } else { + set.ClientTypes = clientTypesValues + } } } // change to security integrations if d.HasChange("security_integrations") { - securityIntegrations := expandStringList(d.Get("security_integrations").(*schema.Set).List()) - securityIntegrationsValues := make([]sdk.SecurityIntegrationsOption, len(securityIntegrations)) - for i, v := range securityIntegrations { - securityIntegrationsValues[i] = sdk.SecurityIntegrationsOption{Name: sdk.NewAccountObjectIdentifier(v)} - } - - baseReq := sdk.NewAlterAuthenticationPolicyRequest(id) - if len(securityIntegrationsValues) == 0 { - unsetReq := sdk.NewAuthenticationPolicyUnsetRequest().WithSecurityIntegrations(*sdk.Bool(true)) - baseReq.WithUnset(*unsetReq) - } else { - setReq := sdk.NewAuthenticationPolicySetRequest().WithSecurityIntegrations(securityIntegrationsValues) - baseReq.WithSet(*setReq) - } + if v, ok := d.GetOk("security_integrations"); ok { + securityIntegrations := expandStringList(v.(*schema.Set).List()) + securityIntegrationsValues := make([]sdk.SecurityIntegrationsOption, len(securityIntegrations)) + for i, v := range securityIntegrations { + securityIntegrationsValues[i] = sdk.SecurityIntegrationsOption{Name: sdk.NewAccountObjectIdentifier(v)} + } - if err := client.AuthenticationPolicies.Alter(ctx, baseReq); err != nil { - return diag.FromErr(err) + if len(securityIntegrationsValues) == 0 { + unset.SecurityIntegrations = sdk.Bool(true) + } else { + set.SecurityIntegrations = securityIntegrationsValues + } } } // change to comment if d.HasChange("comment") { - comment := d.Get("comment").(string) - baseReq := sdk.NewAlterAuthenticationPolicyRequest(id) - if len(comment) == 0 { - unsetReq := sdk.NewAuthenticationPolicyUnsetRequest().WithComment(*sdk.Bool(true)) - baseReq.WithUnset(*unsetReq) + if v, ok := d.GetOk("comment"); ok { + set.Comment = sdk.String(v.(string)) } else { - setReq := sdk.NewAuthenticationPolicySetRequest().WithComment(comment) - baseReq.WithSet(*setReq) + unset.Comment = sdk.Bool(true) + } + } + + if !reflect.DeepEqual(*set, *sdk.NewAuthenticationPolicySetRequest()) { + req := sdk.NewAlterAuthenticationPolicyRequest(id).WithSet(*set) + if err := client.AuthenticationPolicies.Alter(ctx, req); err != nil { + return diag.FromErr(err) } + } - if err := client.AuthenticationPolicies.Alter(ctx, baseReq); err != nil { + if !reflect.DeepEqual(*unset, *sdk.NewAuthenticationPolicyUnsetRequest()) { + req := sdk.NewAlterAuthenticationPolicyRequest(id).WithUnset(*unset) + if err := client.AuthenticationPolicies.Alter(ctx, req); err != nil { return diag.FromErr(err) } } @@ -378,12 +399,14 @@ func UpdateContextAuthenticationPolicy(ctx context.Context, d *schema.ResourceDa } func DeleteContextAuthenticationPolicy(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - name := d.Id() client := meta.(*provider.Context).Client - id := helpers.DecodeSnowflakeID(name).(sdk.SchemaObjectIdentifier) + id, err := sdk.ParseSchemaObjectIdentifier(d.Id()) + if err != nil { + return diag.FromErr(err) + } if err := client.AuthenticationPolicies.Drop(ctx, sdk.NewDropAuthenticationPolicyRequest(id).WithIfExists(*sdk.Bool(true))); err != nil { - diag.FromErr(err) + return diag.FromErr(err) } d.SetId("") From 1ad146165b47c59dbcc838e3924ed2cbd971a9a1 Mon Sep 17 00:00:00 2001 From: Arkadius Schuchhardt Date: Thu, 3 Oct 2024 23:03:15 +0200 Subject: [PATCH 07/15] docs: added example --- .../snowflake_authentication_policy/import.sh | 1 + .../resource.tf | 18 ++++++++++++++++++ .../snowflake_network_policy/resource.tf | 2 +- 3 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 examples/resources/snowflake_authentication_policy/import.sh create mode 100644 examples/resources/snowflake_authentication_policy/resource.tf diff --git a/examples/resources/snowflake_authentication_policy/import.sh b/examples/resources/snowflake_authentication_policy/import.sh new file mode 100644 index 0000000000..142e179238 --- /dev/null +++ b/examples/resources/snowflake_authentication_policy/import.sh @@ -0,0 +1 @@ +terraform import snowflake_authentication_policy.example "databaseName|schemaName|authenticationPolicyName" diff --git a/examples/resources/snowflake_authentication_policy/resource.tf b/examples/resources/snowflake_authentication_policy/resource.tf new file mode 100644 index 0000000000..a89b843720 --- /dev/null +++ b/examples/resources/snowflake_authentication_policy/resource.tf @@ -0,0 +1,18 @@ +## 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"] +} \ No newline at end of file diff --git a/examples/resources/snowflake_network_policy/resource.tf b/examples/resources/snowflake_network_policy/resource.tf index 17c0e70fcb..d6cfe4bb62 100644 --- a/examples/resources/snowflake_network_policy/resource.tf +++ b/examples/resources/snowflake_network_policy/resource.tf @@ -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 = [""] blocked_network_rule_list = [""] From a2f467a98521351eddc025e40700280b349cba37 Mon Sep 17 00:00:00 2001 From: Arkadius Schuchhardt Date: Thu, 3 Oct 2024 23:18:32 +0200 Subject: [PATCH 08/15] fix: added description --- pkg/resources/authentication_policy.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/resources/authentication_policy.go b/pkg/resources/authentication_policy.go index 1d75944ff8..bebeeda650 100644 --- a/pkg/resources/authentication_policy.go +++ b/pkg/resources/authentication_policy.go @@ -95,6 +95,7 @@ func AuthenticationPolicy() *schema.Resource { ReadContext: ReadContextAuthenticationPolicy, UpdateContext: UpdateContextAuthenticationPolicy, DeleteContext: DeleteContextAuthenticationPolicy, + Description: "Resource used to manage authentication policy objects. For more information, check [authentication policy documentation](https://docs.snowflake.com/en/sql-reference/sql/create-authentication-policy).", Schema: authenticationPolicySchema, Importer: &schema.ResourceImporter{ From f99731439546672c7748e1a60aed6c092a2dd73f Mon Sep 17 00:00:00 2001 From: Arkadius Schuchhardt Date: Thu, 17 Oct 2024 22:15:15 +0200 Subject: [PATCH 09/15] fix: small PR fixes --- .../snowflake_authentication_policy/import.sh | 2 +- .../resource.tf | 1 + pkg/resources/authentication_policy.go | 22 ++++++++++++------- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/examples/resources/snowflake_authentication_policy/import.sh b/examples/resources/snowflake_authentication_policy/import.sh index 142e179238..81ffd621e8 100644 --- a/examples/resources/snowflake_authentication_policy/import.sh +++ b/examples/resources/snowflake_authentication_policy/import.sh @@ -1 +1 @@ -terraform import snowflake_authentication_policy.example "databaseName|schemaName|authenticationPolicyName" +terraform import snowflake_authentication_policy.example '""."".""' diff --git a/examples/resources/snowflake_authentication_policy/resource.tf b/examples/resources/snowflake_authentication_policy/resource.tf index a89b843720..98745f4fe3 100644 --- a/examples/resources/snowflake_authentication_policy/resource.tf +++ b/examples/resources/snowflake_authentication_policy/resource.tf @@ -15,4 +15,5 @@ resource "snowflake_authentication_policy" "complete" { mfa_enrollment = "OPTIONAL" client_types = ["ALL"] security_integrations = ["ALL"] + comment = "My authentication policy." } \ No newline at end of file diff --git a/pkg/resources/authentication_policy.go b/pkg/resources/authentication_policy.go index bebeeda650..7351be8ffc 100644 --- a/pkg/resources/authentication_policy.go +++ b/pkg/resources/authentication_policy.go @@ -306,7 +306,7 @@ func UpdateContextAuthenticationPolicy(ctx context.Context, d *schema.ResourceDa authenticationMethodsValues[i] = sdk.AuthenticationMethods{Method: sdk.AuthenticationMethodsOption(v)} } if len(authenticationMethodsValues) == 0 { - unset.AuthenticationMethods = sdk.Bool(true) + unset.WithAuthenticationMethods(true) } else { set.AuthenticationMethods = authenticationMethodsValues } @@ -323,7 +323,7 @@ func UpdateContextAuthenticationPolicy(ctx context.Context, d *schema.ResourceDa } if len(mfaAuthenticationMethodsValues) == 0 { - unset.MfaAuthenticationMethods = sdk.Bool(true) + unset.WithMfaAuthenticationMethods(true) } else { set.MfaAuthenticationMethods = mfaAuthenticationMethodsValues } @@ -335,7 +335,7 @@ func UpdateContextAuthenticationPolicy(ctx context.Context, d *schema.ResourceDa if v, ok := d.GetOk("mfa_enrollment"); ok { set.MfaEnrollment = sdk.Pointer(sdk.MfaEnrollmentOption(v.(string))) } else { - unset.MfaEnrollment = sdk.Bool(true) + unset.WithMfaEnrollment(true) } } @@ -349,7 +349,7 @@ func UpdateContextAuthenticationPolicy(ctx context.Context, d *schema.ResourceDa } if len(clientTypesValues) == 0 { - unset.ClientTypes = sdk.Bool(true) + unset.WithClientTypes(true) } else { set.ClientTypes = clientTypesValues } @@ -366,7 +366,7 @@ func UpdateContextAuthenticationPolicy(ctx context.Context, d *schema.ResourceDa } if len(securityIntegrationsValues) == 0 { - unset.SecurityIntegrations = sdk.Bool(true) + unset.WithSecurityIntegrations(true) } else { set.SecurityIntegrations = securityIntegrationsValues } @@ -378,7 +378,7 @@ func UpdateContextAuthenticationPolicy(ctx context.Context, d *schema.ResourceDa if v, ok := d.GetOk("comment"); ok { set.Comment = sdk.String(v.(string)) } else { - unset.Comment = sdk.Bool(true) + unset.WithComment(true) } } @@ -403,10 +403,16 @@ func DeleteContextAuthenticationPolicy(ctx context.Context, d *schema.ResourceDa client := meta.(*provider.Context).Client id, err := sdk.ParseSchemaObjectIdentifier(d.Id()) if err != nil { - return diag.FromErr(err) + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "Error deleting authentication policy", + Detail: fmt.Sprintf("id %v err = %v", id.Name(), err), + }, + } } - if err := client.AuthenticationPolicies.Drop(ctx, sdk.NewDropAuthenticationPolicyRequest(id).WithIfExists(*sdk.Bool(true))); err != nil { + if err := client.AuthenticationPolicies.Drop(ctx, sdk.NewDropAuthenticationPolicyRequest(id).WithIfExists(true)); err != nil { return diag.FromErr(err) } From efd6d7c85aa9d8273d8344eb9faec303018f92d6 Mon Sep 17 00:00:00 2001 From: Arkadius Schuchhardt Date: Thu, 17 Oct 2024 22:29:54 +0200 Subject: [PATCH 10/15] fix: added To*Option functions in authentication_policies SDK --- pkg/resources/authentication_policy.go | 21 ++++++----- pkg/sdk/authentication_policies_def.go | 52 +++++++++++++++++++++++++- 2 files changed, 63 insertions(+), 10 deletions(-) diff --git a/pkg/resources/authentication_policy.go b/pkg/resources/authentication_policy.go index 7351be8ffc..1868f5be3f 100644 --- a/pkg/resources/authentication_policy.go +++ b/pkg/resources/authentication_policy.go @@ -11,7 +11,6 @@ import ( "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/schemas" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "reflect" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -42,7 +41,8 @@ var authenticationPolicySchema = map[string]*schema.Schema{ Type: schema.TypeSet, Elem: &schema.Schema{ Type: schema.TypeString, - ValidateDiagFunc: StringInSlice([]string{"ALL", "SAML", "PASSWORD", "OAUTH", "KEYPAIR"}, false), + ValidateDiagFunc: sdkValidation(sdk.ToAuthenticationMethodsOption), + DiffSuppressFunc: SuppressIfAny(NormalizeAndCompare(sdk.ToAuthenticationMethodsOption)), }, Optional: true, Description: fmt.Sprintf("A list of authentication methods that are allowed during login. This parameter accepts one or more of the following values: %s", possibleValuesListed(sdk.AllAuthenticationMethods)), @@ -51,23 +51,26 @@ var authenticationPolicySchema = map[string]*schema.Schema{ Type: schema.TypeSet, Elem: &schema.Schema{ Type: schema.TypeString, - ValidateDiagFunc: StringInSlice([]string{"SAML", "PASSWORD"}, false), + ValidateDiagFunc: sdkValidation(sdk.ToMfaAuthenticationMethodsOption), + DiffSuppressFunc: SuppressIfAny(NormalizeAndCompare(sdk.ToMfaAuthenticationMethodsOption)), }, Optional: true, Description: fmt.Sprintf("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 %s.", possibleValuesListed(sdk.AllMfaAuthenticationMethods)), }, "mfa_enrollment": { - Type: schema.TypeString, - Optional: true, - Description: "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).", - ValidateFunc: validation.StringInSlice([]string{"REQUIRED", "OPTIONAL"}, false), - Default: "OPTIONAL", + Type: schema.TypeString, + Optional: true, + Description: "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).", + ValidateDiagFunc: sdkValidation(sdk.ToMfaEnrollmentOption), + DiffSuppressFunc: SuppressIfAny(NormalizeAndCompare(sdk.ToMfaEnrollmentOption)), + Default: "OPTIONAL", }, "client_types": { Type: schema.TypeSet, Elem: &schema.Schema{ Type: schema.TypeString, - ValidateDiagFunc: StringInSlice([]string{"ALL", "SNOWFLAKE_UI", "DRIVERS", "SNOWSQL"}, false), + ValidateDiagFunc: sdkValidation(sdk.ToClientTypesOption), + DiffSuppressFunc: SuppressIfAny(NormalizeAndCompare(sdk.ToClientTypesOption)), }, Optional: true, Description: fmt.Sprintf("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 %s. 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.", possibleValuesListed(sdk.AllClientTypes)), diff --git a/pkg/sdk/authentication_policies_def.go b/pkg/sdk/authentication_policies_def.go index 2d283c45dd..c5aaef0b65 100644 --- a/pkg/sdk/authentication_policies_def.go +++ b/pkg/sdk/authentication_policies_def.go @@ -1,6 +1,10 @@ package sdk -import g "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk/poc/generator" +import ( + "fmt" + g "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk/poc/generator" + "strings" +) //go:generate go run ./poc/main.go @@ -185,3 +189,49 @@ var AuthenticationPoliciesDef = g.NewInterface( Name(). WithValidation(g.ValidIdentifier, "name"), ) + +func ToAuthenticationMethodsOption(s string) (*AuthenticationMethodsOption, error) { + switch authenticationMethodsOption := AuthenticationMethodsOption(strings.ToUpper(s)); authenticationMethodsOption { + case AuthenticationMethodsAll, + AuthenticationMethodsSaml, + AuthenticationMethodsPassword, + AuthenticationMethodsOauth, + AuthenticationMethodsKeyPair: + return &authenticationMethodsOption, nil + default: + return nil, fmt.Errorf("invalid frequency type: %s", s) + } +} + +func ToMfaAuthenticationMethodsOption(s string) (*MfaAuthenticationMethodsOption, error) { + switch mfaAuthenticationMethodsOption := MfaAuthenticationMethodsOption(strings.ToUpper(s)); mfaAuthenticationMethodsOption { + case MfaAuthenticationMethodsAll, + MfaAuthenticationMethodsSaml, + MfaAuthenticationMethodsPassword: + return &mfaAuthenticationMethodsOption, nil + default: + return nil, fmt.Errorf("invalid frequency type: %s", s) + } +} + +func ToMfaEnrollmentOption(s string) (*MfaEnrollmentOption, error) { + switch mfaEnrollmentOption := MfaEnrollmentOption(strings.ToUpper(s)); mfaEnrollmentOption { + case MfaEnrollmentRequired, + MfaEnrollmentOptional: + return &mfaEnrollmentOption, nil + default: + return nil, fmt.Errorf("invalid frequency type: %s", s) + } +} + +func ToClientTypesOption(s string) (*ClientTypesOption, error) { + switch clientTypesOption := ClientTypesOption(strings.ToUpper(s)); clientTypesOption { + case ClientTypesAll, + ClientTypesSnowflakeUi, + ClientTypesDrivers, + ClientTypesSnowSql: + return &clientTypesOption, nil + default: + return nil, fmt.Errorf("invalid frequency type: %s", s) + } +} From 71bb6053436a7dca31cb6fd75eb0cb80d62b96cf Mon Sep 17 00:00:00 2001 From: Arkadius Schuchhardt Date: Thu, 17 Oct 2024 22:55:46 +0200 Subject: [PATCH 11/15] refactor: PR comment --- pkg/resources/authentication_policy.go | 79 ++++++++++++++++---------- 1 file changed, 50 insertions(+), 29 deletions(-) diff --git a/pkg/resources/authentication_policy.go b/pkg/resources/authentication_policy.go index 1868f5be3f..f2dc80f9e3 100644 --- a/pkg/resources/authentication_policy.go +++ b/pkg/resources/authentication_policy.go @@ -147,7 +147,11 @@ func CreateContextAuthenticationPolicy(ctx context.Context, d *schema.ResourceDa authenticationMethodsRawList := expandStringList(v.(*schema.Set).List()) authenticationMethods := make([]sdk.AuthenticationMethods, len(authenticationMethodsRawList)) for i, v := range authenticationMethodsRawList { - authenticationMethods[i] = sdk.AuthenticationMethods{Method: sdk.AuthenticationMethodsOption(v)} + option, err := sdk.ToAuthenticationMethodsOption(v) + if err != nil { + return diag.FromErr(err) + } + authenticationMethods[i] = sdk.AuthenticationMethods{Method: *option} } req.WithAuthenticationMethods(authenticationMethods) } @@ -156,20 +160,32 @@ func CreateContextAuthenticationPolicy(ctx context.Context, d *schema.ResourceDa mfaAuthenticationMethodsRawList := expandStringList(v.(*schema.Set).List()) mfaAuthenticationMethods := make([]sdk.MfaAuthenticationMethods, len(mfaAuthenticationMethodsRawList)) for i, v := range mfaAuthenticationMethodsRawList { - mfaAuthenticationMethods[i] = sdk.MfaAuthenticationMethods{Method: sdk.MfaAuthenticationMethodsOption(v)} + option, err := sdk.ToMfaAuthenticationMethodsOption(v) + if err != nil { + return diag.FromErr(err) + } + mfaAuthenticationMethods[i] = sdk.MfaAuthenticationMethods{Method: *option} } req.WithMfaAuthenticationMethods(mfaAuthenticationMethods) } if v, ok := d.GetOk("mfa_enrollment"); ok { - req = req.WithMfaEnrollment(sdk.MfaEnrollmentOption(v.(string))) + option, err := sdk.ToMfaEnrollmentOption(v.(string)) + if err != nil { + return diag.FromErr(err) + } + req = req.WithMfaEnrollment(*option) } if v, ok := d.GetOk("client_types"); ok { clientTypesRawList := expandStringList(v.(*schema.Set).List()) clientTypes := make([]sdk.ClientTypes, len(clientTypesRawList)) for i, v := range clientTypesRawList { - clientTypes[i] = sdk.ClientTypes{ClientType: sdk.ClientTypesOption(v)} + option, err := sdk.ToClientTypesOption(v) + if err != nil { + return diag.FromErr(err) + } + clientTypes[i] = sdk.ClientTypes{ClientType: *option} } req.WithClientTypes(clientTypes) } @@ -306,13 +322,16 @@ func UpdateContextAuthenticationPolicy(ctx context.Context, d *schema.ResourceDa authenticationMethods := expandStringList(v.(*schema.Set).List()) authenticationMethodsValues := make([]sdk.AuthenticationMethods, len(authenticationMethods)) for i, v := range authenticationMethods { - authenticationMethodsValues[i] = sdk.AuthenticationMethods{Method: sdk.AuthenticationMethodsOption(v)} - } - if len(authenticationMethodsValues) == 0 { - unset.WithAuthenticationMethods(true) - } else { - set.AuthenticationMethods = authenticationMethodsValues + option, err := sdk.ToAuthenticationMethodsOption(v) + if err != nil { + return diag.FromErr(err) + } + authenticationMethodsValues[i] = sdk.AuthenticationMethods{Method: *option} } + + set.WithAuthenticationMethods(authenticationMethodsValues) + } else { + unset.WithAuthenticationMethods(true) } } @@ -322,21 +341,23 @@ func UpdateContextAuthenticationPolicy(ctx context.Context, d *schema.ResourceDa mfaAuthenticationMethods := expandStringList(v.(*schema.Set).List()) mfaAuthenticationMethodsValues := make([]sdk.MfaAuthenticationMethods, len(mfaAuthenticationMethods)) for i, v := range mfaAuthenticationMethods { - mfaAuthenticationMethodsValues[i] = sdk.MfaAuthenticationMethods{Method: sdk.MfaAuthenticationMethodsOption(v)} + option, err := sdk.ToMfaAuthenticationMethodsOption(v) + if err != nil { + return diag.FromErr(err) + } + mfaAuthenticationMethodsValues[i] = sdk.MfaAuthenticationMethods{Method: *option} } - if len(mfaAuthenticationMethodsValues) == 0 { - unset.WithMfaAuthenticationMethods(true) - } else { - set.MfaAuthenticationMethods = mfaAuthenticationMethodsValues - } + set.WithMfaAuthenticationMethods(mfaAuthenticationMethodsValues) + } else { + unset.WithMfaAuthenticationMethods(true) } } // change to mfa enrollment if d.HasChange("mfa_enrollment") { - if v, ok := d.GetOk("mfa_enrollment"); ok { - set.MfaEnrollment = sdk.Pointer(sdk.MfaEnrollmentOption(v.(string))) + if mfaEnrollmentOption, err := sdk.ToMfaEnrollmentOption(d.Get("mfa_enrollment").(string)); err == nil { + set.WithMfaEnrollment(*mfaEnrollmentOption) } else { unset.WithMfaEnrollment(true) } @@ -348,14 +369,16 @@ func UpdateContextAuthenticationPolicy(ctx context.Context, d *schema.ResourceDa clientTypes := expandStringList(v.(*schema.Set).List()) clientTypesValues := make([]sdk.ClientTypes, len(clientTypes)) for i, v := range clientTypes { - clientTypesValues[i] = sdk.ClientTypes{ClientType: sdk.ClientTypesOption(v)} + option, err := sdk.ToClientTypesOption(v) + if err != nil { + return diag.FromErr(err) + } + clientTypesValues[i] = sdk.ClientTypes{ClientType: *option} } - if len(clientTypesValues) == 0 { - unset.WithClientTypes(true) - } else { - set.ClientTypes = clientTypesValues - } + set.WithClientTypes(clientTypesValues) + } else { + unset.WithClientTypes(true) } } @@ -368,11 +391,9 @@ func UpdateContextAuthenticationPolicy(ctx context.Context, d *schema.ResourceDa securityIntegrationsValues[i] = sdk.SecurityIntegrationsOption{Name: sdk.NewAccountObjectIdentifier(v)} } - if len(securityIntegrationsValues) == 0 { - unset.WithSecurityIntegrations(true) - } else { - set.SecurityIntegrations = securityIntegrationsValues - } + set.WithSecurityIntegrations(securityIntegrationsValues) + } else { + unset.WithSecurityIntegrations(true) } } From d29425d3849dca09e3956d74adab79b2d586eee2 Mon Sep 17 00:00:00 2001 From: Arkadius Schuchhardt Date: Fri, 18 Oct 2024 11:10:08 +0200 Subject: [PATCH 12/15] feat: added show and describe schemas --- pkg/schemas/authentication_policy.go | 48 +++++++++++++++++ pkg/schemas/authentication_policy_gen.go | 61 ++++++++++++++++++++++ pkg/schemas/gen/sdk_show_result_structs.go | 1 + 3 files changed, 110 insertions(+) create mode 100644 pkg/schemas/authentication_policy.go create mode 100644 pkg/schemas/authentication_policy_gen.go diff --git a/pkg/schemas/authentication_policy.go b/pkg/schemas/authentication_policy.go new file mode 100644 index 0000000000..66b96a5eed --- /dev/null +++ b/pkg/schemas/authentication_policy.go @@ -0,0 +1,48 @@ +package schemas + +import ( + "log" + "slices" + "strings" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +// AuthenticationPolicyDescribeSchema represents output of DESCRIBE query for the single AuthenticationPolicy. +var AuthenticationPolicyDescribeSchema = map[string]*schema.Schema{ + "name": {Type: schema.TypeString, Computed: true}, + "owner": {Type: schema.TypeString, Computed: true}, + "authentication_methods": {Type: schema.TypeString, Computed: true}, + "mfa_authentication_methods": {Type: schema.TypeString, Computed: true}, + "mfa_enrollment": {Type: schema.TypeString, Computed: true}, + "client_types": {Type: schema.TypeString, Computed: true}, + "security_integrations": {Type: schema.TypeString, Computed: true}, + "comment": {Type: schema.TypeString, Computed: true}, +} + +var _ = AuthenticationPolicyDescribeSchema + +var AuthenticationPolicyNames = []string{ + "NAME", + "OWNER", + "COMMENT", + "AUTHENTICATION_METHODS", + "CLIENT_TYPES", + "SECURITY_INTEGRATIONS", + "MFA_ENROLLMENT", + "MFA_AUTHENTICATION_METHODS", +} + +func AuthenticationPolicyDescriptionToSchema(authenticationPolicyDescription []sdk.AuthenticationPolicyDescription) map[string]any { + authenticationPolicySchema := make(map[string]any) + for _, property := range authenticationPolicyDescription { + property := property + if slices.Contains(AuthenticationPolicyNames, property.Property) { + authenticationPolicySchema[strings.ToLower(property.Property)] = property.Value + } else { + log.Printf("[WARN] unexpected property %v in authentication policy returned from Snowflake", property.Value) + } + } + return authenticationPolicySchema +} diff --git a/pkg/schemas/authentication_policy_gen.go b/pkg/schemas/authentication_policy_gen.go new file mode 100644 index 0000000000..ed487e4439 --- /dev/null +++ b/pkg/schemas/authentication_policy_gen.go @@ -0,0 +1,61 @@ +// Code generated by sdk-to-schema generator; DO NOT EDIT. + +package schemas + +import ( + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +// ShowAuthenticationPolicySchema represents output of SHOW query for the single AuthenticationPolicy. +var ShowAuthenticationPolicySchema = map[string]*schema.Schema{ + "created_on": { + Type: schema.TypeString, + Computed: true, + }, + "name": { + Type: schema.TypeString, + Computed: true, + }, + "comment": { + Type: schema.TypeString, + Computed: true, + }, + "database_name": { + Type: schema.TypeString, + Computed: true, + }, + "schema_name": { + Type: schema.TypeString, + Computed: true, + }, + "owner": { + Type: schema.TypeString, + Computed: true, + }, + "owner_role_type": { + Type: schema.TypeString, + Computed: true, + }, + "options": { + Type: schema.TypeString, + Computed: true, + }, +} + +var _ = ShowAuthenticationPolicySchema + +func AuthenticationPolicyToSchema(authenticationPolicy *sdk.AuthenticationPolicy) map[string]any { + authenticationPolicySchema := make(map[string]any) + authenticationPolicySchema["created_on"] = authenticationPolicy.CreatedOn + authenticationPolicySchema["name"] = authenticationPolicy.Name + authenticationPolicySchema["comment"] = authenticationPolicy.Comment + authenticationPolicySchema["database_name"] = authenticationPolicy.DatabaseName + authenticationPolicySchema["schema_name"] = authenticationPolicy.SchemaName + authenticationPolicySchema["owner"] = authenticationPolicy.Owner + authenticationPolicySchema["owner_role_type"] = authenticationPolicy.OwnerRoleType + authenticationPolicySchema["options"] = authenticationPolicy.Options + return authenticationPolicySchema +} + +var _ = AuthenticationPolicyToSchema diff --git a/pkg/schemas/gen/sdk_show_result_structs.go b/pkg/schemas/gen/sdk_show_result_structs.go index 337367a112..f088d4be01 100644 --- a/pkg/schemas/gen/sdk_show_result_structs.go +++ b/pkg/schemas/gen/sdk_show_result_structs.go @@ -9,6 +9,7 @@ var SdkShowResultStructs = []any{ sdk.ApplicationPackage{}, sdk.ApplicationRole{}, sdk.Application{}, + sdk.AuthenticationPolicy{}, sdk.DatabaseRole{}, sdk.Database{}, sdk.DynamicTable{}, From 98e47ec32fb87c7583920194f61e809af0696ffe Mon Sep 17 00:00:00 2001 From: Arkadius Schuchhardt Date: Fri, 18 Oct 2024 11:10:39 +0200 Subject: [PATCH 13/15] fix: fixed read with list defaults --- pkg/resources/authentication_policy.go | 91 ++++++++++++++++++++++---- pkg/sdk/authentication_policies_def.go | 8 +-- 2 files changed, 83 insertions(+), 16 deletions(-) diff --git a/pkg/resources/authentication_policy.go b/pkg/resources/authentication_policy.go index f2dc80f9e3..6724543777 100644 --- a/pkg/resources/authentication_policy.go +++ b/pkg/resources/authentication_policy.go @@ -88,6 +88,22 @@ var authenticationPolicySchema = map[string]*schema.Schema{ Optional: true, Description: "Specifies a comment for the authentication policy.", }, + ShowOutputAttributeName: { + Type: schema.TypeList, + Computed: true, + Description: "Outputs the result of `SHOW AUTHENTICATION POLICIES` for the given integration.", + Elem: &schema.Resource{ + Schema: schemas.ShowAuthenticationPolicySchema, + }, + }, + DescribeOutputAttributeName: { + Type: schema.TypeList, + Computed: true, + Description: "Outputs the result of `DESCRIBE AUTHENTICATION POLICY` for the given integration.", + Elem: &schema.Resource{ + Schema: schemas.AuthenticationPolicyDescribeSchema, + }, + }, FullyQualifiedNameAttributeName: schemas.FullyQualifiedNameSchema, } @@ -240,21 +256,29 @@ func ReadContextAuthenticationPolicy(ctx context.Context, d *schema.ResourceData return diag.FromErr(err) } - authenticationMethods := make([]string, 0) + authenticationMethodsIs := make([]string, 0) if authenticationMethodsProperty, err := collections.FindFirst(authenticationPolicyDescriptions, func(prop sdk.AuthenticationPolicyDescription) bool { return prop.Property == "AUTHENTICATION_METHODS" }); err == nil { - authenticationMethods = append(authenticationMethods, sdk.ParseCommaSeparatedStringArray(authenticationMethodsProperty.Value, false)...) + authenticationMethodsIs = append(authenticationMethodsIs, sdk.ParseCommaSeparatedStringArray(authenticationMethodsProperty.Value, false)...) } - if err = d.Set("authentication_methods", authenticationMethods); err != nil { + authenticationMethodsShould := d.Get("authentication_methods").(*schema.Set).List() + if stringSlicesEqual(authenticationMethodsIs, []string{"ALL"}) && len(authenticationMethodsShould) == 0 { + authenticationMethodsIs = []string{} + } + if err = d.Set("authentication_methods", authenticationMethodsIs); err != nil { return diag.FromErr(err) } - mfaAuthenticationMethods := make([]string, 0) + mfaAuthenticationMethodsIs := make([]string, 0) if mfaAuthenticationMethodsProperty, err := collections.FindFirst(authenticationPolicyDescriptions, func(prop sdk.AuthenticationPolicyDescription) bool { return prop.Property == "MFA_AUTHENTICATION_METHODS" }); err == nil { - mfaAuthenticationMethods = append(mfaAuthenticationMethods, sdk.ParseCommaSeparatedStringArray(mfaAuthenticationMethodsProperty.Value, false)...) + mfaAuthenticationMethodsIs = append(mfaAuthenticationMethodsIs, sdk.ParseCommaSeparatedStringArray(mfaAuthenticationMethodsProperty.Value, false)...) + } + mfaAuthenticationMethodsShould := d.Get("mfa_authentication_methods").(*schema.Set).List() + if stringSlicesEqual(mfaAuthenticationMethodsIs, []string{"PASSWORD", "SAML"}) && len(mfaAuthenticationMethodsShould) == 0 { + mfaAuthenticationMethodsIs = []string{} } - if err = d.Set("mfa_authentication_methods", mfaAuthenticationMethods); err != nil { + if err = d.Set("mfa_authentication_methods", mfaAuthenticationMethodsIs); err != nil { return diag.FromErr(err) } @@ -265,19 +289,27 @@ func ReadContextAuthenticationPolicy(ctx context.Context, d *schema.ResourceData } } - clientTypes := make([]string, 0) + clientTypesIs := make([]string, 0) if clientTypesProperty, err := collections.FindFirst(authenticationPolicyDescriptions, func(prop sdk.AuthenticationPolicyDescription) bool { return prop.Property == "CLIENT_TYPES" }); err == nil { - clientTypes = append(clientTypes, sdk.ParseCommaSeparatedStringArray(clientTypesProperty.Value, false)...) + clientTypesIs = append(clientTypesIs, sdk.ParseCommaSeparatedStringArray(clientTypesProperty.Value, false)...) + } + clientTypesShould := d.Get("client_types").(*schema.Set).List() + if stringSlicesEqual(clientTypesIs, []string{"ALL"}) && len(clientTypesShould) == 0 { + clientTypesIs = []string{} } - if err = d.Set("client_types", clientTypes); err != nil { + if err = d.Set("client_types", clientTypesIs); err != nil { return diag.FromErr(err) } - securityIntegrations := make([]string, 0) + securityIntegrationsIs := make([]string, 0) if securityIntegrationsProperty, err := collections.FindFirst(authenticationPolicyDescriptions, func(prop sdk.AuthenticationPolicyDescription) bool { return prop.Property == "SECURITY_INTEGRATIONS" }); err == nil { - securityIntegrations = append(securityIntegrations, sdk.ParseCommaSeparatedStringArray(securityIntegrationsProperty.Value, false)...) + securityIntegrationsIs = append(securityIntegrationsIs, sdk.ParseCommaSeparatedStringArray(securityIntegrationsProperty.Value, false)...) } - if err = d.Set("security_integrations", securityIntegrations); err != nil { + securityIntegrationsIsShould := d.Get("security_integrations").(*schema.Set).List() + if stringSlicesEqual(securityIntegrationsIs, []string{"ALL"}) && len(securityIntegrationsIsShould) == 0 { + securityIntegrationsIs = []string{} + } + if err = d.Set("security_integrations", securityIntegrationsIs); err != nil { return diag.FromErr(err) } @@ -288,6 +320,14 @@ func ReadContextAuthenticationPolicy(ctx context.Context, d *schema.ResourceData return diag.FromErr(err) } + if err = d.Set(ShowOutputAttributeName, []map[string]any{schemas.AuthenticationPolicyToSchema(authenticationPolicy)}); err != nil { + return diag.FromErr(err) + } + + if err = d.Set(DescribeOutputAttributeName, []map[string]any{schemas.AuthenticationPolicyDescriptionToSchema(authenticationPolicyDescriptions)}); err != nil { + return diag.FromErr(err) + } + return diags } @@ -320,6 +360,9 @@ func UpdateContextAuthenticationPolicy(ctx context.Context, d *schema.ResourceDa if d.HasChange("authentication_methods") { if v, ok := d.GetOk("authentication_methods"); ok { authenticationMethods := expandStringList(v.(*schema.Set).List()) + for _, v := range authenticationMethods { + fmt.Println(v) + } authenticationMethodsValues := make([]sdk.AuthenticationMethods, len(authenticationMethods)) for i, v := range authenticationMethods { option, err := sdk.ToAuthenticationMethodsOption(v) @@ -443,3 +486,27 @@ func DeleteContextAuthenticationPolicy(ctx context.Context, d *schema.ResourceDa d.SetId("") return nil } + +func stringSlicesEqual(s1 []string, s2 []string) bool { + if len(s1) != len(s2) { + return false + } + + // convert slices to maps for easy comparison + set1 := make(map[string]bool) + for _, v := range s1 { + set1[v] = true + } + + set2 := make(map[string]bool) + for _, v := range s2 { + set2[v] = true + } + + for k, _ := range set1 { + if _, ok := set2[k]; !ok { + return false + } + } + return true +} diff --git a/pkg/sdk/authentication_policies_def.go b/pkg/sdk/authentication_policies_def.go index c5aaef0b65..1c53e54eeb 100644 --- a/pkg/sdk/authentication_policies_def.go +++ b/pkg/sdk/authentication_policies_def.go @@ -199,7 +199,7 @@ func ToAuthenticationMethodsOption(s string) (*AuthenticationMethodsOption, erro AuthenticationMethodsKeyPair: return &authenticationMethodsOption, nil default: - return nil, fmt.Errorf("invalid frequency type: %s", s) + return nil, fmt.Errorf("invalid authentication method type: %s", s) } } @@ -210,7 +210,7 @@ func ToMfaAuthenticationMethodsOption(s string) (*MfaAuthenticationMethodsOption MfaAuthenticationMethodsPassword: return &mfaAuthenticationMethodsOption, nil default: - return nil, fmt.Errorf("invalid frequency type: %s", s) + return nil, fmt.Errorf("invalid MFA authentication method type: %s", s) } } @@ -220,7 +220,7 @@ func ToMfaEnrollmentOption(s string) (*MfaEnrollmentOption, error) { MfaEnrollmentOptional: return &mfaEnrollmentOption, nil default: - return nil, fmt.Errorf("invalid frequency type: %s", s) + return nil, fmt.Errorf("invalid enrollment option type: %s", s) } } @@ -232,6 +232,6 @@ func ToClientTypesOption(s string) (*ClientTypesOption, error) { ClientTypesSnowSql: return &clientTypesOption, nil default: - return nil, fmt.Errorf("invalid frequency type: %s", s) + return nil, fmt.Errorf("invalid client type: %s", s) } } From 384b48bdce61b4230a87f9e446599bc6d1db0588 Mon Sep 17 00:00:00 2001 From: Arkadius Schuchhardt Date: Fri, 18 Oct 2024 12:41:44 +0200 Subject: [PATCH 14/15] fix: fixed imports --- pkg/acceptance/check_destroy.go | 3 + pkg/resources/authentication_policy.go | 88 +++++++++++-------- .../authentication_policy_acceptance_test.go | 16 ++-- 3 files changed, 61 insertions(+), 46 deletions(-) diff --git a/pkg/acceptance/check_destroy.go b/pkg/acceptance/check_destroy.go index 4ed1be7ec6..e1c6f7b126 100644 --- a/pkg/acceptance/check_destroy.go +++ b/pkg/acceptance/check_destroy.go @@ -86,6 +86,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) }, diff --git a/pkg/resources/authentication_policy.go b/pkg/resources/authentication_policy.go index 6724543777..8ca08f5b54 100644 --- a/pkg/resources/authentication_policy.go +++ b/pkg/resources/authentication_policy.go @@ -146,6 +146,28 @@ func ImportAuthenticationPolicy(ctx context.Context, d *schema.ResourceData, met if err = d.Set("schema", authenticationPolicy.SchemaName); err != nil { return nil, err } + if err = d.Set("comment", authenticationPolicy.Comment); err != nil { + return nil, err + } + + // needed as otherwise the resource will be incorrectly imported when a list-parameter value equals a default value + authenticationPolicyDescriptions, err := client.AuthenticationPolicies.Describe(ctx, id) + authenticationMethods := getListParameterFromDescribe(authenticationPolicyDescriptions, "AUTHENTICATION_METHODS") + if err = d.Set("authentication_methods", authenticationMethods); err != nil { + return nil, err + } + mfaAuthenticationMethods := getListParameterFromDescribe(authenticationPolicyDescriptions, "MFA_AUTHENTICATION_METHODS") + if err = d.Set("mfa_authentication_methods", mfaAuthenticationMethods); err != nil { + return nil, err + } + clientTypes := getListParameterFromDescribe(authenticationPolicyDescriptions, "CLIENT_TYPES") + if err = d.Set("client_types", clientTypes); err != nil { + return nil, err + } + securityIntegrations := getListParameterFromDescribe(authenticationPolicyDescriptions, "SECURITY_INTEGRATIONS") + if err = d.Set("security_integrations", securityIntegrations); err != nil { + return nil, err + } return []*schema.ResourceData{d}, nil } @@ -256,29 +278,13 @@ func ReadContextAuthenticationPolicy(ctx context.Context, d *schema.ResourceData return diag.FromErr(err) } - authenticationMethodsIs := make([]string, 0) - if authenticationMethodsProperty, err := collections.FindFirst(authenticationPolicyDescriptions, func(prop sdk.AuthenticationPolicyDescription) bool { return prop.Property == "AUTHENTICATION_METHODS" }); err == nil { - authenticationMethodsIs = append(authenticationMethodsIs, sdk.ParseCommaSeparatedStringArray(authenticationMethodsProperty.Value, false)...) - } - authenticationMethodsShould := d.Get("authentication_methods").(*schema.Set).List() - if stringSlicesEqual(authenticationMethodsIs, []string{"ALL"}) && len(authenticationMethodsShould) == 0 { - authenticationMethodsIs = []string{} - } - if err = d.Set("authentication_methods", authenticationMethodsIs); err != nil { + authenticationMethods := getListArgumentWithDefaults(d, "authentication_methods", getListParameterFromDescribe(authenticationPolicyDescriptions, "AUTHENTICATION_METHODS"), []string{"ALL"}) + if err = d.Set("authentication_methods", authenticationMethods); err != nil { return diag.FromErr(err) } - mfaAuthenticationMethodsIs := make([]string, 0) - if mfaAuthenticationMethodsProperty, err := collections.FindFirst(authenticationPolicyDescriptions, func(prop sdk.AuthenticationPolicyDescription) bool { - return prop.Property == "MFA_AUTHENTICATION_METHODS" - }); err == nil { - mfaAuthenticationMethodsIs = append(mfaAuthenticationMethodsIs, sdk.ParseCommaSeparatedStringArray(mfaAuthenticationMethodsProperty.Value, false)...) - } - mfaAuthenticationMethodsShould := d.Get("mfa_authentication_methods").(*schema.Set).List() - if stringSlicesEqual(mfaAuthenticationMethodsIs, []string{"PASSWORD", "SAML"}) && len(mfaAuthenticationMethodsShould) == 0 { - mfaAuthenticationMethodsIs = []string{} - } - if err = d.Set("mfa_authentication_methods", mfaAuthenticationMethodsIs); err != nil { + mfaAuthenticationMethods := getListArgumentWithDefaults(d, "mfa_authentication_methods", getListParameterFromDescribe(authenticationPolicyDescriptions, "MFA_AUTHENTICATION_METHODS"), []string{"PASSWORD", "SAML"}) + if err = d.Set("mfa_authentication_methods", mfaAuthenticationMethods); err != nil { return diag.FromErr(err) } @@ -289,27 +295,13 @@ func ReadContextAuthenticationPolicy(ctx context.Context, d *schema.ResourceData } } - clientTypesIs := make([]string, 0) - if clientTypesProperty, err := collections.FindFirst(authenticationPolicyDescriptions, func(prop sdk.AuthenticationPolicyDescription) bool { return prop.Property == "CLIENT_TYPES" }); err == nil { - clientTypesIs = append(clientTypesIs, sdk.ParseCommaSeparatedStringArray(clientTypesProperty.Value, false)...) - } - clientTypesShould := d.Get("client_types").(*schema.Set).List() - if stringSlicesEqual(clientTypesIs, []string{"ALL"}) && len(clientTypesShould) == 0 { - clientTypesIs = []string{} - } - if err = d.Set("client_types", clientTypesIs); err != nil { + clientTypes := getListArgumentWithDefaults(d, "client_types", getListParameterFromDescribe(authenticationPolicyDescriptions, "CLIENT_TYPES"), []string{"ALL"}) + if err = d.Set("client_types", clientTypes); err != nil { return diag.FromErr(err) } - securityIntegrationsIs := make([]string, 0) - if securityIntegrationsProperty, err := collections.FindFirst(authenticationPolicyDescriptions, func(prop sdk.AuthenticationPolicyDescription) bool { return prop.Property == "SECURITY_INTEGRATIONS" }); err == nil { - securityIntegrationsIs = append(securityIntegrationsIs, sdk.ParseCommaSeparatedStringArray(securityIntegrationsProperty.Value, false)...) - } - securityIntegrationsIsShould := d.Get("security_integrations").(*schema.Set).List() - if stringSlicesEqual(securityIntegrationsIs, []string{"ALL"}) && len(securityIntegrationsIsShould) == 0 { - securityIntegrationsIs = []string{} - } - if err = d.Set("security_integrations", securityIntegrationsIs); err != nil { + securityIntegrations := getListArgumentWithDefaults(d, "security_integrations", getListParameterFromDescribe(authenticationPolicyDescriptions, "SECURITY_INTEGRATIONS"), []string{"ALL"}) + if err = d.Set("security_integrations", securityIntegrations); err != nil { return diag.FromErr(err) } @@ -331,6 +323,26 @@ func ReadContextAuthenticationPolicy(ctx context.Context, d *schema.ResourceData return diags } +func getListParameterFromDescribe(authenticationPolicyDescriptions []sdk.AuthenticationPolicyDescription, parameterName string) []string { + parameterList := make([]string, 0) + if parameterProperty, err := collections.FindFirst(authenticationPolicyDescriptions, func(prop sdk.AuthenticationPolicyDescription) bool { + return prop.Property == parameterName + }); err == nil { + parameterList = append(parameterList, sdk.ParseCommaSeparatedStringArray(parameterProperty.Value, false)...) + } + return parameterList +} + +// getListArgumentWithDefaults returns the list of values for a given argument, with the defaults applied, if necessary. Otherwise, tf plan will always show a diff with a list parameter with defaults when no value is set. +func getListArgumentWithDefaults(d *schema.ResourceData, argumentName string, argumentIs []string, argumentDefaults []string) []string { + // in case nothing is set in the tf resource and the is equals the default, we set the is to empty + argumentShould := d.Get(argumentName).(*schema.Set).List() + if stringSlicesEqual(argumentIs, argumentDefaults) && len(argumentShould) == 0 { + argumentIs = []string{} + } + return argumentIs +} + func UpdateContextAuthenticationPolicy(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { client := meta.(*provider.Context).Client id, err := sdk.ParseSchemaObjectIdentifier(d.Id()) diff --git a/pkg/resources/authentication_policy_acceptance_test.go b/pkg/resources/authentication_policy_acceptance_test.go index 00338069f5..95b7cf7e92 100644 --- a/pkg/resources/authentication_policy_acceptance_test.go +++ b/pkg/resources/authentication_policy_acceptance_test.go @@ -1,10 +1,10 @@ package resources_test import ( + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/provider/resources" "testing" acc "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance" - "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/provider/resources" "github.com/hashicorp/terraform-plugin-testing/config" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/tfversion" @@ -57,13 +57,13 @@ func TestAcc_AuthenticationPolicy(t *testing.T) { ConfigDirectory: config.TestNameDirectory(), ConfigVariables: variables1, Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("snowflake_password_policy.pa", "name", accName), - resource.TestCheckResourceAttr("snowflake_password_policy.pa", "authentication_methods.0", "PASSWORD"), - resource.TestCheckResourceAttr("snowflake_password_policy.pa", "mfa_authentication_methods.0", "PASSWORD"), - resource.TestCheckResourceAttr("snowflake_password_policy.pa", "mfa_enrollment", "REQUIRED"), - resource.TestCheckResourceAttr("snowflake_password_policy.pa", "client_types.0", "SNOWFLAKE_UI"), - resource.TestCheckResourceAttr("snowflake_password_policy.pa", "security_integrations.0", "ALL"), - resource.TestCheckResourceAttr("snowflake_password_policy.pa", "comment", comment), + resource.TestCheckResourceAttr("snowflake_authentication_policy.authentication_policy", "name", accName), + resource.TestCheckResourceAttr("snowflake_authentication_policy.authentication_policy", "authentication_methods.0", "PASSWORD"), + resource.TestCheckResourceAttr("snowflake_authentication_policy.authentication_policy", "mfa_authentication_methods.0", "PASSWORD"), + resource.TestCheckResourceAttr("snowflake_authentication_policy.authentication_policy", "mfa_enrollment", "REQUIRED"), + resource.TestCheckResourceAttr("snowflake_authentication_policy.authentication_policy", "client_types.0", "SNOWFLAKE_UI"), + resource.TestCheckResourceAttr("snowflake_authentication_policy.authentication_policy", "security_integrations.0", "ALL"), + resource.TestCheckResourceAttr("snowflake_authentication_policy.authentication_policy", "comment", comment), ), }, { From 30ac8454e73a35afe30cea38fefe0fc4bfd0442e Mon Sep 17 00:00:00 2001 From: Arkadius Schuchhardt Date: Wed, 23 Oct 2024 12:02:41 +0200 Subject: [PATCH 15/15] fix: small stuff from code review --- ...entication_policy_attachment_acceptance_test.go | 8 -------- pkg/resources/authentication_policy.go | 14 ++++---------- 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/pkg/resources/account_authentication_policy_attachment_acceptance_test.go b/pkg/resources/account_authentication_policy_attachment_acceptance_test.go index 833fdad631..2ef5a658a4 100644 --- a/pkg/resources/account_authentication_policy_attachment_acceptance_test.go +++ b/pkg/resources/account_authentication_policy_attachment_acceptance_test.go @@ -31,14 +31,6 @@ func TestAcc_AccountAuthenticationPolicyAttachment(t *testing.T) { ResourceName: "snowflake_account_authentication_policy_attachment.att", ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{ - "initially_suspended", - "wait_for_provisioning", - "query_acceleration_max_scale_factor", - "max_concurrency_level", - "statement_queued_timeout_in_seconds", - "statement_timeout_in_seconds", - }, }, }, }) diff --git a/pkg/resources/authentication_policy.go b/pkg/resources/authentication_policy.go index 8ca08f5b54..9dbdff1098 100644 --- a/pkg/resources/authentication_policy.go +++ b/pkg/resources/authentication_policy.go @@ -42,7 +42,6 @@ var authenticationPolicySchema = map[string]*schema.Schema{ Elem: &schema.Schema{ Type: schema.TypeString, ValidateDiagFunc: sdkValidation(sdk.ToAuthenticationMethodsOption), - DiffSuppressFunc: SuppressIfAny(NormalizeAndCompare(sdk.ToAuthenticationMethodsOption)), }, Optional: true, Description: fmt.Sprintf("A list of authentication methods that are allowed during login. This parameter accepts one or more of the following values: %s", possibleValuesListed(sdk.AllAuthenticationMethods)), @@ -52,7 +51,6 @@ var authenticationPolicySchema = map[string]*schema.Schema{ Elem: &schema.Schema{ Type: schema.TypeString, ValidateDiagFunc: sdkValidation(sdk.ToMfaAuthenticationMethodsOption), - DiffSuppressFunc: SuppressIfAny(NormalizeAndCompare(sdk.ToMfaAuthenticationMethodsOption)), }, Optional: true, Description: fmt.Sprintf("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 %s.", possibleValuesListed(sdk.AllMfaAuthenticationMethods)), @@ -62,7 +60,6 @@ var authenticationPolicySchema = map[string]*schema.Schema{ Optional: true, Description: "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).", ValidateDiagFunc: sdkValidation(sdk.ToMfaEnrollmentOption), - DiffSuppressFunc: SuppressIfAny(NormalizeAndCompare(sdk.ToMfaEnrollmentOption)), Default: "OPTIONAL", }, "client_types": { @@ -70,7 +67,6 @@ var authenticationPolicySchema = map[string]*schema.Schema{ Elem: &schema.Schema{ Type: schema.TypeString, ValidateDiagFunc: sdkValidation(sdk.ToClientTypesOption), - DiffSuppressFunc: SuppressIfAny(NormalizeAndCompare(sdk.ToClientTypesOption)), }, Optional: true, Description: fmt.Sprintf("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 %s. 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.", possibleValuesListed(sdk.AllClientTypes)), @@ -78,7 +74,8 @@ var authenticationPolicySchema = map[string]*schema.Schema{ "security_integrations": { Type: schema.TypeSet, Elem: &schema.Schema{ - Type: schema.TypeString, + Type: schema.TypeString, + ValidateDiagFunc: IsValidIdentifier[sdk.AccountObjectIdentifier](), }, Optional: true, Description: "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.", @@ -91,7 +88,7 @@ var authenticationPolicySchema = map[string]*schema.Schema{ ShowOutputAttributeName: { Type: schema.TypeList, Computed: true, - Description: "Outputs the result of `SHOW AUTHENTICATION POLICIES` for the given integration.", + Description: "Outputs the result of `SHOW AUTHENTICATION POLICIES` for the given policy.", Elem: &schema.Resource{ Schema: schemas.ShowAuthenticationPolicySchema, }, @@ -99,7 +96,7 @@ var authenticationPolicySchema = map[string]*schema.Schema{ DescribeOutputAttributeName: { Type: schema.TypeList, Computed: true, - Description: "Outputs the result of `DESCRIBE AUTHENTICATION POLICY` for the given integration.", + Description: "Outputs the result of `DESCRIBE AUTHENTICATION POLICY` for the given policy.", Elem: &schema.Resource{ Schema: schemas.AuthenticationPolicyDescribeSchema, }, @@ -372,9 +369,6 @@ func UpdateContextAuthenticationPolicy(ctx context.Context, d *schema.ResourceDa if d.HasChange("authentication_methods") { if v, ok := d.GetOk("authentication_methods"); ok { authenticationMethods := expandStringList(v.(*schema.Set).List()) - for _, v := range authenticationMethods { - fmt.Println(v) - } authenticationMethodsValues := make([]sdk.AuthenticationMethods, len(authenticationMethods)) for i, v := range authenticationMethods { option, err := sdk.ToAuthenticationMethodsOption(v)