Skip to content

Commit

Permalink
feat: masking policy application resource (#1739)
Browse files Browse the repository at this point in the history
* Sort resources and datasources.

* Render qualified names with double quotes.

* Add qualified_name computed field to table resource.

* Add table column masking policy manager.

* Add table column masking policy application resource.
  • Loading branch information
sfc-gh-ngaberel authored Apr 24, 2023
1 parent 338a19d commit ce80f57
Show file tree
Hide file tree
Showing 12 changed files with 590 additions and 61 deletions.
1 change: 1 addition & 0 deletions docs/resources/table.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ resource "snowflake_table" "table" {

- `id` (String) The ID of this resource.
- `owner` (String) Name of the role that owns the table.
- `qualified_name` (String) Qualified name of the table.

<a id="nestedblock--column"></a>
### Nested Schema for `column`
Expand Down
79 changes: 79 additions & 0 deletions docs/resources/table_column_masking_policy_application.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
---
page_title: "snowflake_table_column_masking_policy_application Resource - terraform-provider-snowflake"
subcategory: ""
description: |-
Applies a masking policy to a table column.
---

# snowflake_table_column_masking_policy_application (Resource)

Applies a masking policy to a table column.

Only one masking policy may be applied per table column, hence only one `snowflake_table_column_masking_policy_application` resources may be present per table column.
Using two or more `snowflake_table_column_masking_policy_application` resources for the same table column will result in the last one overriding any previously applied masking policies and unresolvable diffs in Terraform plan.

When using this resource to manage a table column's masking policy make sure to ignore changes to the column's masking policy in the table definition, otherwise the two resources would conflict. See example below.

## Example Usage

```terraform
# Default provider for most resources
provider "snowflake" {
role = "SYSADMIN"
}
# Alternative provider with masking_admin role
provider "snowflake" {
alias = "masking"
role = "MASKING_ADMIN"
}
resource "snowflake_masking_policy" "policy" {
provider = snowflake.masking # Create masking policy with masking_admin role
name = "EXAMPLE_MASKING_POLICY"
database = "EXAMPLE_DB"
schema = "EXAMPLE_SCHEMA"
value_data_type = "VARCHAR"
masking_expression = "case when current_role() in ('ANALYST') then val else sha2(val, 512) end"
return_data_type = "VARCHAR"
}
# Table is created by the default provider
resource "snowflake_table" "table" {
database = "EXAMPLE_DB"
schema = "EXAMPLE_SCHEMA"
name = "table"
column {
name = "secret"
type = "VARCHAR(16777216)"
}
lifecycle {
# Masking policy is managed by a standalone resource and shouldn't be changed by the table resource.
ignore_changes = [column[0].masking_policy]
}
}
resource "snowflake_table_column_masking_view_application" "application" {
provider = snowflake.masking # Apply masking policy with masking_admin role
table = snowflake_table.table.qualified_name
column = "age"
masking_policy = snowflake_masking_policy.policy.qualified_name
}
```

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

### Required

- `column` (String) The column to apply the masking policy to.
- `masking_policy` (String) Fully qualified name (`database.schema.policyname`) of the policy to apply.
- `table` (String) The fully qualified name (`database.schema.table`) of the table to apply the masking policy to.

### Read-Only

- `id` (String) The ID of this resource.
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Default provider for most resources
provider "snowflake" {
role = "SYSADMIN"
}

# Alternative provider with masking_admin role
provider "snowflake" {
alias = "masking"
role = "MASKING_ADMIN"
}

resource "snowflake_masking_policy" "policy" {
provider = snowflake.masking # Create masking policy with masking_admin role

name = "EXAMPLE_MASKING_POLICY"
database = "EXAMPLE_DB"
schema = "EXAMPLE_SCHEMA"
value_data_type = "VARCHAR"
masking_expression = "case when current_role() in ('ANALYST') then val else sha2(val, 512) end"
return_data_type = "VARCHAR"
}

# Table is created by the default provider
resource "snowflake_table" "table" {
database = "EXAMPLE_DB"
schema = "EXAMPLE_SCHEMA"
name = "table"

column {
name = "secret"
type = "VARCHAR(16777216)"
}

lifecycle {
# Masking policy is managed by a standalone resource and shouldn't be changed by the table resource.
ignore_changes = [column[0].masking_policy]
}
}

resource "snowflake_table_column_masking_view_application" "application" {
provider = snowflake.masking # Apply masking policy with masking_admin role

table = snowflake_table.table.qualified_name
column = "age"
masking_policy = snowflake_masking_policy.policy.qualified_name
}
99 changes: 50 additions & 49 deletions pkg/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,64 +204,65 @@ func GetGrantResources() resources.TerraformGrantResources {
"snowflake_table_grant": resources.TableGrant(),
"snowflake_tag_grant": resources.TagGrant(),
"snowflake_task_grant": resources.TaskGrant(),
"snowflake_user_grant": resources.UserGrant(),
"snowflake_view_grant": resources.ViewGrant(),
"snowflake_warehouse_grant": resources.WarehouseGrant(),
"snowflake_user_grant": resources.UserGrant(),
}
return grants
}

func getResources() map[string]*schema.Resource {
// NOTE(): do not add grant resources here
others := map[string]*schema.Resource{
"snowflake_account": resources.Account(),
"snowflake_account_parameter": resources.AccountParameter(),
"snowflake_alert": resources.Alert(),
"snowflake_api_integration": resources.APIIntegration(),
"snowflake_database": resources.Database(),
"snowflake_database_role": resources.DatabaseRole(),
"snowflake_external_function": resources.ExternalFunction(),
"snowflake_failover_group": resources.FailoverGroup(),
"snowflake_file_format": resources.FileFormat(),
"snowflake_function": resources.Function(),
"snowflake_managed_account": resources.ManagedAccount(),
"snowflake_masking_policy": resources.MaskingPolicy(),
"snowflake_materialized_view": resources.MaterializedView(),
"snowflake_network_policy_attachment": resources.NetworkPolicyAttachment(),
"snowflake_network_policy": resources.NetworkPolicy(),
"snowflake_oauth_integration": resources.OAuthIntegration(),
"snowflake_object_parameter": resources.ObjectParameter(),
"snowflake_external_oauth_integration": resources.ExternalOauthIntegration(),
"snowflake_password_policy": resources.PasswordPolicy(),
"snowflake_pipe": resources.Pipe(),
"snowflake_procedure": resources.Procedure(),
"snowflake_resource_monitor": resources.ResourceMonitor(),
"snowflake_role": resources.Role(),
"snowflake_role_grants": resources.RoleGrants(),
"snowflake_role_ownership_grant": resources.RoleOwnershipGrant(),
"snowflake_row_access_policy": resources.RowAccessPolicy(),
"snowflake_saml_integration": resources.SAMLIntegration(),
"snowflake_schema": resources.Schema(),
"snowflake_scim_integration": resources.SCIMIntegration(),
"snowflake_sequence": resources.Sequence(),
"snowflake_session_parameter": resources.SessionParameter(),
"snowflake_share": resources.Share(),
"snowflake_stage": resources.Stage(),
"snowflake_storage_integration": resources.StorageIntegration(),
"snowflake_notification_integration": resources.NotificationIntegration(),
"snowflake_stream": resources.Stream(),
"snowflake_table": resources.Table(),
"snowflake_table_constraint": resources.TableConstraint(),
"snowflake_external_table": resources.ExternalTable(),
"snowflake_tag": resources.Tag(),
"snowflake_tag_association": resources.TagAssociation(),
"snowflake_tag_masking_policy_association": resources.TagMaskingPolicyAssociation(),
"snowflake_task": resources.Task(),
"snowflake_user": resources.User(),
"snowflake_user_ownership_grant": resources.UserOwnershipGrant(),
"snowflake_user_public_keys": resources.UserPublicKeys(),
"snowflake_view": resources.View(),
"snowflake_warehouse": resources.Warehouse(),
"snowflake_account": resources.Account(),
"snowflake_account_parameter": resources.AccountParameter(),
"snowflake_alert": resources.Alert(),
"snowflake_api_integration": resources.APIIntegration(),
"snowflake_database": resources.Database(),
"snowflake_database_role": resources.DatabaseRole(),
"snowflake_external_function": resources.ExternalFunction(),
"snowflake_external_oauth_integration": resources.ExternalOauthIntegration(),
"snowflake_external_table": resources.ExternalTable(),
"snowflake_failover_group": resources.FailoverGroup(),
"snowflake_file_format": resources.FileFormat(),
"snowflake_function": resources.Function(),
"snowflake_managed_account": resources.ManagedAccount(),
"snowflake_masking_policy": resources.MaskingPolicy(),
"snowflake_materialized_view": resources.MaterializedView(),
"snowflake_network_policy": resources.NetworkPolicy(),
"snowflake_network_policy_attachment": resources.NetworkPolicyAttachment(),
"snowflake_notification_integration": resources.NotificationIntegration(),
"snowflake_oauth_integration": resources.OAuthIntegration(),
"snowflake_object_parameter": resources.ObjectParameter(),
"snowflake_password_policy": resources.PasswordPolicy(),
"snowflake_pipe": resources.Pipe(),
"snowflake_procedure": resources.Procedure(),
"snowflake_resource_monitor": resources.ResourceMonitor(),
"snowflake_role": resources.Role(),
"snowflake_role_grants": resources.RoleGrants(),
"snowflake_role_ownership_grant": resources.RoleOwnershipGrant(),
"snowflake_row_access_policy": resources.RowAccessPolicy(),
"snowflake_saml_integration": resources.SAMLIntegration(),
"snowflake_schema": resources.Schema(),
"snowflake_scim_integration": resources.SCIMIntegration(),
"snowflake_sequence": resources.Sequence(),
"snowflake_session_parameter": resources.SessionParameter(),
"snowflake_share": resources.Share(),
"snowflake_stage": resources.Stage(),
"snowflake_storage_integration": resources.StorageIntegration(),
"snowflake_stream": resources.Stream(),
"snowflake_table": resources.Table(),
"snowflake_table_column_masking_policy_application": resources.TableColumnMaskingPolicyApplication(),
"snowflake_table_constraint": resources.TableConstraint(),
"snowflake_tag": resources.Tag(),
"snowflake_tag_association": resources.TagAssociation(),
"snowflake_tag_masking_policy_association": resources.TagMaskingPolicyAssociation(),
"snowflake_task": resources.Task(),
"snowflake_user": resources.User(),
"snowflake_user_ownership_grant": resources.UserOwnershipGrant(),
"snowflake_user_public_keys": resources.UserPublicKeys(),
"snowflake_view": resources.View(),
"snowflake_warehouse": resources.Warehouse(),
}

return mergeSchemas(
Expand Down
6 changes: 6 additions & 0 deletions pkg/resources/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,11 @@ var tableSchema = map[string]*schema.Schema{
Default: false,
Description: "Specifies whether to enable change tracking on the table. Default false.",
},
"qualified_name": {
Type: schema.TypeString,
Computed: true,
Description: "Qualified name of the table.",
},
"tag": tagReferenceSchema,
}

Expand Down Expand Up @@ -591,6 +596,7 @@ func ReadTable(d *schema.ResourceData, meta interface{}) error {
// "primary_key": snowflake.FlattenTablePrimaryKey(pkDescription),
"data_retention_days": table.RetentionTime.Int32,
"change_tracking": (table.ChangeTracking.String == "ON"),
"qualified_name": fmt.Sprintf(`"%s"."%s"."%s"`, tableID.DatabaseName, tableID.SchemaName, table.TableName.String),
}

for key, val := range toSet {
Expand Down
Loading

0 comments on commit ce80f57

Please sign in to comment.