Skip to content

Commit

Permalink
feat: External Oauth integration v1 readiness (#2907)
Browse files Browse the repository at this point in the history
<!-- Feel free to delete comments as you fill this in -->

<!-- summary of changes -->
Add new resources covering External Oauth security integration. Also,
there are minor fixes in SDK to match actual behavior of Snowflake.


## Test Plan
<!-- detail ways in which this PR has been tested or needs to be tested
-->
* [x] acceptance tests


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

https://docs.snowflake.com/en/sql-reference/sql/create-security-integration-oauth-external

---------

Co-authored-by: Jan Cieślak <jan.cieslak@snowflake.com>
  • Loading branch information
sfc-gh-jmichalak and sfc-gh-jcieslak authored Jul 9, 2024
1 parent d9b557f commit ed237c3
Show file tree
Hide file tree
Showing 35 changed files with 2,273 additions and 593 deletions.
39 changes: 39 additions & 0 deletions MIGRATION_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,45 @@ Added a new datasource enabling querying and filtering all types of security int
The additional parameters call **DESC SECURITY INTEGRATION** (with `with_describe` turned on) **per security integration** returned by **SHOW SECURITY INTEGRATIONS**.
It's important to limit the records and calls to Snowflake to the minimum. That's why we recommend assessing which information you need from the data source and then providing strong filters and turning off additional fields for better plan performance.

### snowflake_external_oauth_integration resource changes

#### *(behavior change)* Renamed fields
Renamed fields:
- `type` to `external_oauth_type`
- `issuer` to `external_oauth_issuer`
- `token_user_mapping_claims` to `external_oauth_token_user_mapping_claim`
- `snowflake_user_mapping_attribute` to `external_oauth_snowflake_user_mapping_attribute`
- `scope_mapping_attribute` to `external_oauth_scope_mapping_attribute`
- `jws_keys_urls` to `external_oauth_jws_keys_url`
- `rsa_public_key` to `external_oauth_rsa_public_key`
- `rsa_public_key_2` to `external_oauth_rsa_public_key_2`
- `blocked_roles` to `external_oauth_blocked_roles_list`
- `allowed_roles` to `external_oauth_allowed_roles_list`
- `audience_urls` to `external_oauth_audience_list`
- `any_role_mode` to `external_oauth_any_role_mode`
- `scope_delimiter` to `external_oauth_scope_delimiter`
to align with Snowflake docs. Please rename this field in your configuration files. State will be migrated automatically.

#### *(behavior change)* Force new for multiple attributes after removing from config
Conditional force new was added for the following attributes when they are removed from config. There are no alter statements supporting UNSET on these fields.
- `external_oauth_rsa_public_key`
- `external_oauth_rsa_public_key_2`
- `external_oauth_scope_mapping_attribute`
- `external_oauth_jws_keys_url`
- `external_oauth_token_user_mapping_claim`

#### *(behavior change)* Conflicting fields
Fields listed below can not be set at the same time in Snowflake. They are marked as conflicting fields.
- `external_oauth_jws_keys_url` <-> `external_oauth_rsa_public_key`
- `external_oauth_jws_keys_url` <-> `external_oauth_rsa_public_key_2`
- `external_oauth_allowed_roles_list` <-> `external_oauth_blocked_roles_list`

#### *(behavior change)* Changed diff suppress for some fields
The fields listed below had diff suppress which removed '-' from strings. Now, this behavior is removed, so if you had '-' in these strings, please remove them. Note that '-' in these values is not allowed by Snowflake.
- `external_oauth_snowflake_user_mapping_attribute`
- `external_oauth_type`
- `external_oauth_any_role_mode`

### snowflake_scim_integration resource changes
#### *(behavior change)* Changed behavior of `sync_password`

Expand Down
283 changes: 257 additions & 26 deletions docs/resources/external_oauth_integration.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1 +1 @@
terraform import snowflake_external_oauth_integration.example name
terraform import snowflake_external_oauth_integration.example "name"
52 changes: 42 additions & 10 deletions examples/resources/snowflake_external_oauth_integration/resource.tf
Original file line number Diff line number Diff line change
@@ -1,10 +1,42 @@
resource "snowflake_external_oauth_integration" "azure" {
name = "AZURE_POWERBI"
type = "AZURE"
enabled = true
issuer = "https://sts.windows.net/00000000-0000-0000-0000-000000000000"
snowflake_user_mapping_attribute = "LOGIN_NAME"
jws_keys_urls = ["https://login.windows.net/common/discovery/keys"]
audience_urls = ["https://analysis.windows.net/powerbi/connector/Snowflake"]
token_user_mapping_claims = ["upn"]
}
# basic resource
resource "snowflake_external_oauth_integration" "test" {
enabled = true
external_oauth_issuer = "issuer"
external_oauth_snowflake_user_mapping_attribute = "LOGIN_NAME"
external_oauth_token_user_mapping_claim = ["upn"]
name = "test"
external_oauth_type = "CUSTOM"
}
# resource with all fields set (jws keys url and allowed roles)
resource "snowflake_external_oauth_integration" "test" {
comment = "comment"
enabled = true
external_oauth_allowed_roles_list = ["user1"]
external_oauth_any_role_mode = "ENABLED"
external_oauth_audience_list = ["https://example.com"]
external_oauth_issuer = "issuer"
external_oauth_jws_keys_url = ["https://example.com"]
external_oauth_scope_delimiter = ","
external_oauth_scope_mapping_attribute = "scope"
external_oauth_snowflake_user_mapping_attribute = "LOGIN_NAME"
external_oauth_token_user_mapping_claim = ["upn"]
name = "test"
external_oauth_type = "CUSTOM"
}
# resource with all fields set (rsa public keys and blocked roles)
resource "snowflake_external_oauth_integration" "test" {
comment = "comment"
enabled = true
external_oauth_any_role_mode = "ENABLED"
external_oauth_audience_list = ["https://example.com"]
external_oauth_blocked_roles_list = ["user1"]
external_oauth_issuer = "issuer"
external_oauth_rsa_public_key = file("key.pem")
external_oauth_rsa_public_key_2 = file("key2.pem")
external_oauth_scope_delimiter = ","
external_oauth_scope_mapping_attribute = "scope"
external_oauth_snowflake_user_mapping_attribute = "LOGIN_NAME"
external_oauth_token_user_mapping_claim = ["upn"]
name = "test"
external_oauth_type = "CUSTOM"
}
1 change: 0 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ require (
github.com/avast/retry-go v3.0.0+incompatible
github.com/brianvoe/gofakeit/v6 v6.28.0
github.com/buger/jsonparser v1.1.1
github.com/google/uuid v1.6.0
github.com/gookit/color v1.5.4
github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320
github.com/hashicorp/go-uuid v1.0.3
Expand Down
3 changes: 3 additions & 0 deletions pkg/acceptance/check_destroy.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@ var showByIdFunctions = map[resources.Resource]showByIdFunc{
resources.ExternalFunction: func(ctx context.Context, client *sdk.Client, id sdk.ObjectIdentifier) error {
return runShowById(ctx, id, client.ExternalFunctions.ShowByID)
},
resources.ExternalOauthSecurityIntegration: func(ctx context.Context, client *sdk.Client, id sdk.ObjectIdentifier) error {
return runShowById(ctx, id, client.SecurityIntegrations.ShowByID)
},
resources.ExternalTable: func(ctx context.Context, client *sdk.Client, id sdk.ObjectIdentifier) error {
return runShowById(ctx, id, client.ExternalTables.ShowByID)
},
Expand Down
8 changes: 8 additions & 0 deletions pkg/acceptance/helpers/security_integration_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@ func (c *SecurityIntegrationClient) client() sdk.SecurityIntegrations {
return c.context.client.SecurityIntegrations
}

func (c *SecurityIntegrationClient) UpdateExternalOauth(t *testing.T, request *sdk.AlterExternalOauthSecurityIntegrationRequest) {
t.Helper()
ctx := context.Background()

err := c.client().AlterExternalOauth(ctx, request)
require.NoError(t, err)
}

func (c *SecurityIntegrationClient) CreateSaml2(t *testing.T, id sdk.AccountObjectIdentifier) (*sdk.SecurityIntegration, func()) {
t.Helper()
return c.CreateSaml2WithRequest(t, sdk.NewCreateSaml2SecurityIntegrationRequest(id, c.ids.Alpha(), "https://example.com", "Custom", random.GenerateX509(t)))
Expand Down
1 change: 1 addition & 0 deletions pkg/provider/resources/resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const (
EmailNotificationIntegration resource = "snowflake_email_notification_integration"
ExternalFunction resource = "snowflake_external_function"
ExternalTable resource = "snowflake_external_table"
ExternalOauthSecurityIntegration resource = "snowflake_external_oauth_security_integration"
FailoverGroup resource = "snowflake_failover_group"
FileFormat resource = "snowflake_file_format"
Function resource = "snowflake_function"
Expand Down
1 change: 1 addition & 0 deletions pkg/resources/custom_diffs.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"strings"

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

"github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
Expand Down
36 changes: 36 additions & 0 deletions pkg/resources/diff_suppressions.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ package resources
import (
"fmt"
"log"
"slices"
"strings"

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

Expand Down Expand Up @@ -80,3 +83,36 @@ func SuppressIfAny(diffSuppressFunctions ...schema.SchemaDiffSuppressFunc) schem
return suppress
}
}

func IgnoreValuesFromSetIfParamSet(key, param string, values []string) schema.SchemaDiffSuppressFunc {
return func(k, old, new string, d *schema.ResourceData) bool {
params := d.Get(RelatedParametersAttributeName).([]any)
if len(params) == 0 {
return false
}
result := params[0].(map[string]any)
param := result[strings.ToLower(param)].([]any)
value := param[0].(map[string]any)["value"]
if !helpers.StringToBool(value.(string)) {
return false
}
if k == key+".#" {
old, new := d.GetChange(key)
var numOld, numNew int
oldList := expandStringList(old.(*schema.Set).List())
newList := expandStringList(new.(*schema.Set).List())
for _, v := range oldList {
if !slices.Contains(values, v) {
numOld++
}
}
for _, v := range newList {
if !slices.Contains(values, v) {
numNew++
}
}
return numOld == numNew
}
return slices.Contains(values, old)
}
}
Loading

0 comments on commit ed237c3

Please sign in to comment.