Skip to content

Commit

Permalink
feat: Add External Oauth security integration to sdk (#2835)
Browse files Browse the repository at this point in the history
<!-- Feel free to delete comments as you fill this in -->
This change adds Snowflake Oauth security integration to sdk.
<!-- summary of changes -->

## Test Plan
<!-- detail ways in which this PR has been tested or needs to be tested
-->
* [x] unit tests
<!-- add more below if you think they are relevant -->
* [x] integration tests
## References
<!-- issues documentation links, etc  -->

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

https://docs.snowflake.com/en/sql-reference/sql/alter-security-integration-oauth-external
  • Loading branch information
sfc-gh-jmichalak authored May 28, 2024
1 parent 36ead85 commit 82d1c09
Show file tree
Hide file tree
Showing 10 changed files with 1,141 additions and 32 deletions.
11 changes: 7 additions & 4 deletions pkg/sdk/poc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ but if we add one case, or modify more cases this becomes more challenging)
- in some cases we could need more filters -> see alerts.go (but we can implement it later)
- handle arrays
- handle more validation types
- write new `valueSet` function (see validations.go) that will have better defaults or more parameters that will determine
- write new `valueSet` function (see validations.go) that will have better defaults or more parameters that will determine
checking behaviour which should get rid of edge cases that may cause bugs in the future
- right now, we have `valueSet` function that doesn't take into consideration edge cases, e.g. with slice where sometimes
we would like to do something like `alter x set y = ()` (set empty array to unset `y`). Those edge cases have cause on our
Expand All @@ -66,7 +66,7 @@ checking behaviour which should get rid of edge cases that may cause bugs in the
- example implementation - https://go.dev/play/p/Cgt0sISlzwK
- divide implementation templates for Show, Describe and others
- check if SelfIdentifier implementation is correct (mostly type, because it's derived from interface obj) by checking
if there's a resource with different types of identifiers across queries (e.g. Create <AccountObjectIdentifier>, Alter <SchemaObjectIdentifier>)
if there's a resource with different types of identifiers across queries (e.g. Create <AccountObjectIdentifier>, Alter <SchemaObjectIdentifier>)
- we should specify prefix / postfix standard for top-level items in _def.go files to avoid any conflicts in the package
- remove name argument from QueryStruct in the Operation, because Opt structs in the Operation will have name from op name + interface field and not query struct itself
- Derive field name from QueryStruct, e.g. see network_policies_def where we can remove "Set" field, but we have to make a convention of creating nested struct with
Expand Down Expand Up @@ -101,10 +101,13 @@ find a better solution to solve the issue (add more logic to the templates ?)
- struct_to_builder is not supporting templated-like values. See stages_def.go where in SQL there could be value, where 'n' can be replaced with any number
- `SKIP_FILE_n` - this looks more like keyword without a space between SQL prefix and int
- `SKIP_FILE_n%` (e.g. `SKIP_FILE_123%`) - this is more template-like behaviour, notice that 'n' is inside the value (we cannot reproduce that right now with struct_to_builder capabilities)
- fix builder generation
- fix builder generation
- we can add `flatten` option in cases where some sql structs had to be nested to create correct sql representation
- for example encryption options in `stages_def.go` (instead of calling `.WithEncryption(NewEncryptionRequest(encryption))` we could call `.WithEncryption(encryption)`)
- operation names (or their sql struct names) should dictate more how constructors are made
- better handling of list of strings/identifiers
- there should be no need to define custom types every time
- more clear definition of lists that can be empty vs cannot be empty

##### Known issues
- generating two converts when Show and Desc use the same data structure
Expand All @@ -130,4 +133,4 @@ type SomeReq struct {

##### Known limitations
- automatic array conversion is not recursive, so we're only supporting one level mapping
- []Request1{ foo Request2, bar int } won't be converted, but []Request1{ foo string, bar int } will
- []Request1{ foo Request2, bar int } won't be converted, but []Request1{ foo string, bar int } will
130 changes: 129 additions & 1 deletion pkg/sdk/security_integrations_def.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,30 @@ import g "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk/poc/gen

//go:generate go run ./poc/main.go

type ExternalOauthSecurityIntegrationTypeOption string

const (
ExternalOauthSecurityIntegrationTypeOkta ExternalOauthSecurityIntegrationTypeOption = "OKTA"
ExternalOauthSecurityIntegrationTypeAzure ExternalOauthSecurityIntegrationTypeOption = "AZURE"
ExternalOauthSecurityIntegrationTypePingFederate ExternalOauthSecurityIntegrationTypeOption = "PING_FEDERATE"
ExternalOauthSecurityIntegrationTypeCustom ExternalOauthSecurityIntegrationTypeOption = "CUSTOM"
)

type ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeOption string

const (
ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeLoginName ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeOption = "LOGIN_NAME"
ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeEmailAddress ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeOption = "EMAIL_ADDRESS"
)

type ExternalOauthSecurityIntegrationAnyRoleModeOption string

const (
ExternalOauthSecurityIntegrationAnyRoleModeDisable ExternalOauthSecurityIntegrationAnyRoleModeOption = "DISABLE"
ExternalOauthSecurityIntegrationAnyRoleModeEnable ExternalOauthSecurityIntegrationAnyRoleModeOption = "ENABLE"
ExternalOauthSecurityIntegrationAnyRoleModeEnableForPrivilege ExternalOauthSecurityIntegrationAnyRoleModeOption = "ENABLE_FOR_PRIVILEGE"
)

type OauthSecurityIntegrationUseSecondaryRolesOption string

const (
Expand Down Expand Up @@ -48,7 +72,14 @@ var (
preAuthorizedRolesListDef = g.NewQueryStruct("PreAuthorizedRolesList").
List("PreAuthorizedRolesList", "AccountObjectIdentifier", g.ListOptions().MustParentheses())
blockedRolesListDef = g.NewQueryStruct("BlockedRolesList").
List("BlockedRolesList", "AccountObjectIdentifier", g.ListOptions().MustParentheses())
List("BlockedRolesList", "AccountObjectIdentifier", g.ListOptions().Required().MustParentheses())
allowedRolesListDef = g.NewQueryStruct("AllowedRolesList").
List("AllowedRolesList", "AccountObjectIdentifier", g.ListOptions().Required().MustParentheses())
jwsKeysUrlDef = g.NewQueryStruct("JwsKeysUrl").Text("JwsKeyUrl", g.KeywordOptions().SingleQuotes().Required())
audienceListItemDef = g.NewQueryStruct("AudienceListItem").Text("Item", g.KeywordOptions().SingleQuotes().Required())
audienceListDef = g.NewQueryStruct("AudienceList").
List("AudienceList", "AudienceListItem", g.ListOptions().Required().MustParentheses())
tokenUserMappingClaimDef = g.NewQueryStruct("TokenUserMappingClaim").Text("Claim", g.KeywordOptions().SingleQuotes().Required())
)

func createSecurityIntegrationOperation(structName string, opts func(qs *g.QueryStruct) *g.QueryStruct) *g.QueryStruct {
Expand Down Expand Up @@ -78,6 +109,45 @@ func alterSecurityIntegrationOperation(structName string, opts func(qs *g.QueryS
return qs
}

var externalOauthIntegrationSetDef = g.NewQueryStruct("ExternalOauthIntegrationSet").
OptionalBooleanAssignment("ENABLED", g.ParameterOptions()).
OptionalAssignment(
"EXTERNAL_OAUTH_TYPE",
g.KindOfT[ExternalOauthSecurityIntegrationTypeOption](),
g.ParameterOptions(),
).
OptionalTextAssignment("EXTERNAL_OAUTH_ISSUER", g.ParameterOptions().SingleQuotes()).
ListAssignment("EXTERNAL_OAUTH_TOKEN_USER_MAPPING_CLAIM", "TokenUserMappingClaim", g.ParameterOptions().Parentheses()).
OptionalAssignment(
"EXTERNAL_OAUTH_SNOWFLAKE_USER_MAPPING_ATTRIBUTE",
g.KindOfT[ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeOption](),
g.ParameterOptions().SingleQuotes(),
).
ListAssignment("EXTERNAL_OAUTH_JWS_KEYS_URL", "JwsKeysUrl", g.ParameterOptions().Parentheses()).
OptionalQueryStructField("ExternalOauthBlockedRolesList", blockedRolesListDef, g.ParameterOptions().SQL("EXTERNAL_OAUTH_BLOCKED_ROLES_LIST").Parentheses()).
OptionalQueryStructField("ExternalOauthAllowedRolesList", allowedRolesListDef, g.ParameterOptions().SQL("EXTERNAL_OAUTH_ALLOWED_ROLES_LIST").Parentheses()).
OptionalTextAssignment("EXTERNAL_OAUTH_RSA_PUBLIC_KEY", g.ParameterOptions().SingleQuotes()).
OptionalTextAssignment("EXTERNAL_OAUTH_RSA_PUBLIC_KEY_2", g.ParameterOptions().SingleQuotes()).
OptionalQueryStructField("ExternalOauthAudienceList", audienceListDef, g.ParameterOptions().SQL("EXTERNAL_OAUTH_AUDIENCE_LIST").Parentheses()).
OptionalAssignment(
"EXTERNAL_OAUTH_ANY_ROLE_MODE",
g.KindOfT[ExternalOauthSecurityIntegrationAnyRoleModeOption](),
g.ParameterOptions(),
).
OptionalTextAssignment("EXTERNAL_OAUTH_SCOPE_DELIMITER", g.ParameterOptions().SingleQuotes()).
OptionalComment().
WithValidation(g.ConflictingFields, "ExternalOauthBlockedRolesList", "ExternalOauthAllowedRolesList").
WithValidation(g.ConflictingFields, "ExternalOauthJwsKeysUrl", "ExternalOauthRsaPublicKey").
WithValidation(g.ConflictingFields, "ExternalOauthJwsKeysUrl", "ExternalOauthRsaPublicKey2").
WithValidation(g.AtLeastOneValueSet, "Enabled", "ExternalOauthType", "ExternalOauthIssuer", "ExternalOauthTokenUserMappingClaim", "ExternalOauthSnowflakeUserMappingAttribute",
"ExternalOauthJwsKeysUrl", "ExternalOauthBlockedRolesList", "ExternalOauthAllowedRolesList", "ExternalOauthRsaPublicKey", "ExternalOauthRsaPublicKey2",
"ExternalOauthAudienceList", "ExternalOauthAnyRoleMode", "ExternalOauthScopeDelimiter", "Comment")

var externalOauthIntegrationUnsetDef = g.NewQueryStruct("ExternalOauthIntegrationUnset").
OptionalSQL("ENABLED").
OptionalSQL("EXTERNAL_OAUTH_AUDIENCE_LIST").
WithValidation(g.AtLeastOneValueSet, "Enabled", "ExternalOauthAudienceList")

var oauthForPartnerApplicationsIntegrationSetDef = g.NewQueryStruct("OauthForPartnerApplicationsIntegrationSet").
OptionalBooleanAssignment("ENABLED", g.ParameterOptions()).
OptionalBooleanAssignment("OAUTH_ISSUE_REFRESH_TOKENS", g.ParameterOptions()).
Expand Down Expand Up @@ -176,6 +246,49 @@ var SecurityIntegrationsDef = g.NewInterface(
"SecurityIntegration",
g.KindOfT[AccountObjectIdentifier](),
).
CustomOperation(
"CreateExternalOauth",
"https://docs.snowflake.com/en/sql-reference/sql/create-security-integration-oauth-external",
createSecurityIntegrationOperation("CreateExternalOauth", func(qs *g.QueryStruct) *g.QueryStruct {
return qs.
PredefinedQueryStructField("integrationType", "string", g.StaticOptions().SQL("TYPE = EXTERNAL_OAUTH")).
BooleanAssignment("ENABLED", g.ParameterOptions().Required()).
Assignment(
"EXTERNAL_OAUTH_TYPE",
g.KindOfT[ExternalOauthSecurityIntegrationTypeOption](),
g.ParameterOptions().Required(),
).
TextAssignment("EXTERNAL_OAUTH_ISSUER", g.ParameterOptions().Required().SingleQuotes()).
ListAssignment("EXTERNAL_OAUTH_TOKEN_USER_MAPPING_CLAIM", "TokenUserMappingClaim", g.ParameterOptions().Required().Parentheses()).
Assignment(
"EXTERNAL_OAUTH_SNOWFLAKE_USER_MAPPING_ATTRIBUTE",
g.KindOfT[ExternalOauthSecurityIntegrationSnowflakeUserMappingAttributeOption](),
g.ParameterOptions().SingleQuotes().Required(),
).
ListAssignment("EXTERNAL_OAUTH_JWS_KEYS_URL", "JwsKeysUrl", g.ParameterOptions().Parentheses()).
OptionalQueryStructField("ExternalOauthBlockedRolesList", blockedRolesListDef, g.ParameterOptions().SQL("EXTERNAL_OAUTH_BLOCKED_ROLES_LIST").Parentheses()).
OptionalQueryStructField("ExternalOauthAllowedRolesList", allowedRolesListDef, g.ParameterOptions().SQL("EXTERNAL_OAUTH_ALLOWED_ROLES_LIST").Parentheses()).
OptionalTextAssignment("EXTERNAL_OAUTH_RSA_PUBLIC_KEY", g.ParameterOptions().SingleQuotes()).
OptionalTextAssignment("EXTERNAL_OAUTH_RSA_PUBLIC_KEY_2", g.ParameterOptions().SingleQuotes()).
OptionalQueryStructField("ExternalOauthAudienceList", audienceListDef, g.ParameterOptions().SQL("EXTERNAL_OAUTH_AUDIENCE_LIST").Parentheses()).
OptionalAssignment(
"EXTERNAL_OAUTH_ANY_ROLE_MODE",
g.KindOfT[ExternalOauthSecurityIntegrationAnyRoleModeOption](),
g.ParameterOptions(),
).
OptionalTextAssignment("EXTERNAL_OAUTH_SCOPE_DELIMITER", g.ParameterOptions().SingleQuotes()).
OptionalTextAssignment("EXTERNAL_OAUTH_SCOPE_MAPPING_ATTRIBUTE", g.ParameterOptions().SingleQuotes()).
WithValidation(g.ConflictingFields, "ExternalOauthBlockedRolesList", "ExternalOauthAllowedRolesList").
WithValidation(g.ExactlyOneValueSet, "ExternalOauthJwsKeysUrl", "ExternalOauthRsaPublicKey").
WithValidation(g.ConflictingFields, "ExternalOauthJwsKeysUrl", "ExternalOauthRsaPublicKey2")
}),
allowedRolesListDef,
blockedRolesListDef,
jwsKeysUrlDef,
audienceListDef,
audienceListItemDef,
tokenUserMappingClaimDef,
).
CustomOperation(
"CreateOauthForPartnerApplications",
"https://docs.snowflake.com/en/sql-reference/sql/create-security-integration-oauth-snowflake",
Expand Down Expand Up @@ -278,6 +391,21 @@ var SecurityIntegrationsDef = g.NewInterface(
OptionalBooleanAssignment("SYNC_PASSWORD", g.ParameterOptions())
}),
).
CustomOperation(
"AlterExternalOauth",
"https://docs.snowflake.com/en/sql-reference/sql/alter-security-integration-oauth-external",
alterSecurityIntegrationOperation("AlterExternalOauth", func(qs *g.QueryStruct) *g.QueryStruct {
return qs.OptionalQueryStructField(
"Set",
externalOauthIntegrationSetDef,
g.ListOptions().NoParentheses().SQL("SET"),
).OptionalQueryStructField(
"Unset",
externalOauthIntegrationUnsetDef,
g.ListOptions().NoParentheses().SQL("UNSET"),
).WithValidation(g.ExactlyOneValueSet, "Set", "Unset", "SetTags", "UnsetTags")
}),
).
CustomOperation(
"AlterOauthForPartnerApplications",
"https://docs.snowflake.com/en/sql-reference/sql/alter-security-integration-oauth-snowflake",
Expand Down
Loading

0 comments on commit 82d1c09

Please sign in to comment.