Skip to content

Commit

Permalink
feat: Add Snowflake Oauth security integration to sdk (#2830)
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. Also some
code is moved to helpers.
<!-- 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

https://docs.snowflake.com/en/sql-reference/sql/alter-security-integration
  • Loading branch information
sfc-gh-jmichalak authored May 24, 2024
1 parent c79fa29 commit b576f29
Show file tree
Hide file tree
Showing 12 changed files with 1,934 additions and 229 deletions.
13 changes: 13 additions & 0 deletions pkg/acceptance/helpers/context_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package helpers

import (
"context"
"fmt"
"testing"

"github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk"
Expand Down Expand Up @@ -71,3 +72,15 @@ func (c *ContextClient) IsRoleInSession(t *testing.T, id sdk.AccountObjectIdenti

return isInSession
}

// ACSURL returns Snowflake Assertion Consumer Service URL
func (c *ContextClient) ACSURL(t *testing.T) string {
t.Helper()
return fmt.Sprintf("https://%s.snowflakecomputing.com/fed/login", c.CurrentAccount(t))
}

// IssuerURL returns a URL containing the EntityID / Issuer for the Snowflake service provider
func (c *ContextClient) IssuerURL(t *testing.T) string {
t.Helper()
return fmt.Sprintf("https://%s.snowflakecomputing.com", c.CurrentAccount(t))
}
33 changes: 26 additions & 7 deletions pkg/acceptance/helpers/random/certs.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"math/big"
"strings"
"testing"
Expand Down Expand Up @@ -34,14 +35,32 @@ func GenerateX509(t *testing.T) string {
caBytes, err := x509.CreateCertificate(rand.Reader, ca, ca, &caPrivKey.PublicKey, caPrivKey)
require.NoError(t, err)

certPEM := new(bytes.Buffer)
err = pem.Encode(certPEM, &pem.Block{
Type: "CERTIFICATE",
Bytes: caBytes,
})
return encode(t, "CERTIFICATE", caBytes)
}

// GenerateRSA returns an RSA public key without BEGIN and END markers.
func GenerateRSAPublicKey(t *testing.T) string {
t.Helper()
key, err := rsa.GenerateKey(rand.Reader, 2048)
require.NoError(t, err)

cert := strings.TrimPrefix(certPEM.String(), "-----BEGIN CERTIFICATE-----\n")
cert = strings.TrimSuffix(cert, "-----END CERTIFICATE-----\n")
pub := key.Public()
b, err := x509.MarshalPKIXPublicKey(pub.(*rsa.PublicKey))
require.NoError(t, err)
return encode(t, "RSA PUBLIC KEY", b)
}

func encode(t *testing.T, pemType string, b []byte) string {
t.Helper()
buffer := new(bytes.Buffer)
err := pem.Encode(buffer,
&pem.Block{
Type: pemType,
Bytes: b,
},
)
require.NoError(t, err)
cert := strings.TrimPrefix(buffer.String(), fmt.Sprintf("-----BEGIN %s-----\n", pemType))
cert = strings.TrimSuffix(cert, fmt.Sprintf("-----END %s-----\n", pemType))
return cert
}
2 changes: 1 addition & 1 deletion pkg/acceptance/helpers/security_integration_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ func (c *SecurityIntegrationClient) DropSecurityIntegrationFunc(t *testing.T, id
ctx := context.Background()

return func() {
err := c.client().Drop(ctx, sdk.NewDropSecurityIntegrationRequest(id).WithIfExists(sdk.Bool(true)))
err := c.client().Drop(ctx, sdk.NewDropSecurityIntegrationRequest(id).WithIfExists(true))
require.NoError(t, err)
}
}
8 changes: 6 additions & 2 deletions pkg/internal/snowflakeroles/snowflake_predefined_roles.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ package snowflakeroles
import "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk"

var (
Orgadmin = sdk.NewAccountObjectIdentifier("ORGADMIN")
Accountadmin = sdk.NewAccountObjectIdentifier("ACCOUNTADMIN")
Orgadmin = sdk.NewAccountObjectIdentifier("ORGADMIN")
Accountadmin = sdk.NewAccountObjectIdentifier("ACCOUNTADMIN")
SecurityAdmin = sdk.NewAccountObjectIdentifier("SECURITYADMIN")

OktaProvisioner = sdk.NewAccountObjectIdentifier("OKTA_PROVISIONER")
AadProvisioner = sdk.NewAccountObjectIdentifier("AAD_PROVISIONER")
GenericScimProvisioner = sdk.NewAccountObjectIdentifier("GENERIC_SCIM_PROVISIONER")
)
177 changes: 169 additions & 8 deletions pkg/sdk/security_integrations_def.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,42 +4,68 @@ import g "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk/poc/gen

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

type OauthSecurityIntegrationUseSecondaryRolesOption string

const (
OauthSecurityIntegrationUseSecondaryRolesImplicit OauthSecurityIntegrationUseSecondaryRolesOption = "IMPLICIT"
OauthSecurityIntegrationUseSecondaryRolesNone OauthSecurityIntegrationUseSecondaryRolesOption = "NONE"
)

type OauthSecurityIntegrationClientTypeOption string

const (
OauthSecurityIntegrationClientTypePublic OauthSecurityIntegrationClientTypeOption = "PUBLIC"
OauthSecurityIntegrationClientTypeConfidential OauthSecurityIntegrationClientTypeOption = "CONFIDENTIAL"
)

type OauthSecurityIntegrationClientOption string

const (
OauthSecurityIntegrationClientLooker OauthSecurityIntegrationClientOption = "LOOKER"
OauthSecurityIntegrationClientTableauDesktop OauthSecurityIntegrationClientOption = "TABLEAU_DESKTOP"
OauthSecurityIntegrationClientTableauServer OauthSecurityIntegrationClientOption = "TABLEAU_SERVER"
)

type ScimSecurityIntegrationScimClientOption string

var (
const (
ScimSecurityIntegrationScimClientOkta ScimSecurityIntegrationScimClientOption = "OKTA"
ScimSecurityIntegrationScimClientAzure ScimSecurityIntegrationScimClientOption = "AZURE"
ScimSecurityIntegrationScimClientGeneric ScimSecurityIntegrationScimClientOption = "GENERIC"
)

type ScimSecurityIntegrationRunAsRoleOption string

var (
const (
ScimSecurityIntegrationRunAsRoleOktaProvisioner ScimSecurityIntegrationRunAsRoleOption = "OKTA_PROVISIONER"
ScimSecurityIntegrationRunAsRoleAadProvisioner ScimSecurityIntegrationRunAsRoleOption = "AAD_PROVISIONER"
ScimSecurityIntegrationRunAsRoleGenericScimProvisioner ScimSecurityIntegrationRunAsRoleOption = "GENERIC_SCIM_PROVISIONER"
)

var (
userDomainDef = g.NewQueryStruct("UserDomain").Text("Domain", g.KeywordOptions().SingleQuotes().Required())
emailPatternDef = g.NewQueryStruct("EmailPattern").Text("Pattern", g.KeywordOptions().SingleQuotes().Required())
userDomainDef = g.NewQueryStruct("UserDomain").Text("Domain", g.KeywordOptions().SingleQuotes().Required())
emailPatternDef = g.NewQueryStruct("EmailPattern").Text("Pattern", g.KeywordOptions().SingleQuotes().Required())
preAuthorizedRolesListDef = g.NewQueryStruct("PreAuthorizedRolesList").
List("PreAuthorizedRolesList", "AccountObjectIdentifier", g.ListOptions().MustParentheses())
blockedRolesListDef = g.NewQueryStruct("BlockedRolesList").
List("BlockedRolesList", "AccountObjectIdentifier", g.ListOptions().MustParentheses())
)

func createSecurityIntegrationOperation(structName string, apply func(qs *g.QueryStruct) *g.QueryStruct) *g.QueryStruct {
func createSecurityIntegrationOperation(structName string, opts func(qs *g.QueryStruct) *g.QueryStruct) *g.QueryStruct {
qs := g.NewQueryStruct(structName).
Create().
OrReplace().
SQL("SECURITY INTEGRATION").
IfNotExists().
Name()
qs = apply(qs)
qs = opts(qs)
return qs.
OptionalComment().
WithValidation(g.ValidIdentifier, "name").
WithValidation(g.ConflictingFields, "OrReplace", "IfNotExists")
}

func alterSecurityIntegrationOperation(structName string, apply func(qs *g.QueryStruct) *g.QueryStruct) *g.QueryStruct {
func alterSecurityIntegrationOperation(structName string, opts func(qs *g.QueryStruct) *g.QueryStruct) *g.QueryStruct {
qs := g.NewQueryStruct(structName).
Alter().
SQL("SECURITY INTEGRATION").
Expand All @@ -48,10 +74,60 @@ func alterSecurityIntegrationOperation(structName string, apply func(qs *g.Query
OptionalSetTags().
OptionalUnsetTags().
WithValidation(g.ValidIdentifier, "name")
qs = apply(qs)
qs = opts(qs)
return qs
}

var oauthForPartnerApplicationsIntegrationSetDef = g.NewQueryStruct("OauthForPartnerApplicationsIntegrationSet").
OptionalBooleanAssignment("ENABLED", g.ParameterOptions()).
OptionalBooleanAssignment("OAUTH_ISSUE_REFRESH_TOKENS", g.ParameterOptions()).
OptionalTextAssignment("OAUTH_REDIRECT_URI", g.ParameterOptions().SingleQuotes()).
OptionalNumberAssignment("OAUTH_REFRESH_TOKEN_VALIDITY", g.ParameterOptions()).
OptionalAssignment(
"OAUTH_USE_SECONDARY_ROLES",
g.KindOfT[OauthSecurityIntegrationUseSecondaryRolesOption](),
g.ParameterOptions(),
).
OptionalQueryStructField("BlockedRolesList", blockedRolesListDef, g.ParameterOptions().SQL("BLOCKED_ROLES_LIST").Parentheses()).
OptionalComment().
WithValidation(g.AtLeastOneValueSet, "Enabled", "OauthIssueRefreshTokens", "OauthRedirectUri", "OauthRefreshTokenValidity", "OauthUseSecondaryRoles",
"BlockedRolesList", "Comment")

var oauthForPartnerApplicationsIntegrationUnsetDef = g.NewQueryStruct("OauthForPartnerApplicationsIntegrationUnset").
OptionalSQL("ENABLED").
OptionalSQL("OAUTH_USE_SECONDARY_ROLES").
WithValidation(g.AtLeastOneValueSet, "Enabled", "OauthUseSecondaryRoles")

var oauthForCustomClientsIntegrationSetDef = g.NewQueryStruct("OauthForCustomClientsIntegrationSet").
OptionalBooleanAssignment("ENABLED", g.ParameterOptions()).
OptionalTextAssignment("OAUTH_REDIRECT_URI", g.ParameterOptions().SingleQuotes()).
OptionalBooleanAssignment("OAUTH_ALLOW_NON_TLS_REDIRECT_URI", g.ParameterOptions()).
OptionalBooleanAssignment("OAUTH_ENFORCE_PKCE", g.ParameterOptions()).
OptionalQueryStructField("PreAuthorizedRolesList", preAuthorizedRolesListDef, g.ParameterOptions().SQL("PRE_AUTHORIZED_ROLES_LIST").Parentheses()).
OptionalQueryStructField("BlockedRolesList", blockedRolesListDef, g.ParameterOptions().SQL("BLOCKED_ROLES_LIST").Parentheses()).
OptionalBooleanAssignment("OAUTH_ISSUE_REFRESH_TOKENS", g.ParameterOptions()).
OptionalNumberAssignment("OAUTH_REFRESH_TOKEN_VALIDITY", g.ParameterOptions()).
OptionalAssignment(
"OAUTH_USE_SECONDARY_ROLES",
g.KindOfT[OauthSecurityIntegrationUseSecondaryRolesOption](),
g.ParameterOptions(),
).
OptionalIdentifier("NetworkPolicy", g.KindOfT[AccountObjectIdentifier](), g.IdentifierOptions().Equals().SQL("NETWORK_POLICY")).
OptionalTextAssignment("OAUTH_CLIENT_RSA_PUBLIC_KEY", g.ParameterOptions().SingleQuotes()).
OptionalTextAssignment("OAUTH_CLIENT_RSA_PUBLIC_KEY_2", g.ParameterOptions().SingleQuotes()).
OptionalComment().
WithValidation(g.AtLeastOneValueSet, "Enabled", "OauthRedirectUri", "OauthAllowNonTlsRedirectUri", "OauthEnforcePkce", "PreAuthorizedRolesList",
"BlockedRolesList", "OauthIssueRefreshTokens", "OauthRefreshTokenValidity", "OauthUseSecondaryRoles", "NetworkPolicy", "OauthClientRsaPublicKey",
"OauthClientRsaPublicKey2", "Comment")

var oauthForCustomClientsIntegrationUnsetDef = g.NewQueryStruct("OauthForCustomClientsIntegrationUnset").
OptionalSQL("ENABLED").
OptionalSQL("NETWORK_POLICY").
OptionalSQL("OAUTH_CLIENT_RSA_PUBLIC_KEY").
OptionalSQL("OAUTH_CLIENT_RSA_PUBLIC_KEY_2").
OptionalSQL("OAUTH_USE_SECONDARY_ROLES").
WithValidation(g.AtLeastOneValueSet, "Enabled", "NetworkPolicy", "OauthUseSecondaryRoles", "OauthClientRsaPublicKey", "OauthClientRsaPublicKey2")

var saml2IntegrationSetDef = g.NewQueryStruct("Saml2IntegrationSet").
OptionalBooleanAssignment("ENABLED", g.ParameterOptions()).
OptionalTextAssignment("SAML2_ISSUER", g.ParameterOptions().SingleQuotes()).
Expand Down Expand Up @@ -100,6 +176,61 @@ var SecurityIntegrationsDef = g.NewInterface(
"SecurityIntegration",
g.KindOfT[AccountObjectIdentifier](),
).
CustomOperation(
"CreateOauthForPartnerApplications",
"https://docs.snowflake.com/en/sql-reference/sql/create-security-integration-oauth-snowflake",
createSecurityIntegrationOperation("CreateOauthForPartnerApplications", func(qs *g.QueryStruct) *g.QueryStruct {
return qs.
PredefinedQueryStructField("integrationType", "string", g.StaticOptions().SQL("TYPE = OAUTH")).
Assignment(
"OAUTH_CLIENT",
g.KindOfT[OauthSecurityIntegrationClientOption](),
g.ParameterOptions().Required(),
).
OptionalTextAssignment("OAUTH_REDIRECT_URI", g.ParameterOptions().SingleQuotes()).
OptionalBooleanAssignment("ENABLED", g.ParameterOptions()).
OptionalBooleanAssignment("OAUTH_ISSUE_REFRESH_TOKENS", g.ParameterOptions()).
OptionalNumberAssignment("OAUTH_REFRESH_TOKEN_VALIDITY", g.ParameterOptions()).
OptionalAssignment(
"OAUTH_USE_SECONDARY_ROLES",
g.KindOfT[OauthSecurityIntegrationUseSecondaryRolesOption](),
g.ParameterOptions(),
).
OptionalQueryStructField("BlockedRolesList", blockedRolesListDef, g.ParameterOptions().SQL("BLOCKED_ROLES_LIST").Parentheses())
}),
preAuthorizedRolesListDef,
blockedRolesListDef,
).
CustomOperation(
"CreateOauthForCustomClients",
"https://docs.snowflake.com/en/sql-reference/sql/create-security-integration-oauth-snowflake",
createSecurityIntegrationOperation("CreateOauthForCustomClients", func(qs *g.QueryStruct) *g.QueryStruct {
return qs.
PredefinedQueryStructField("integrationType", "string", g.StaticOptions().SQL("TYPE = OAUTH")).
PredefinedQueryStructField("oauthClient", "string", g.StaticOptions().SQL("OAUTH_CLIENT = CUSTOM")).
Assignment(
"OAUTH_CLIENT_TYPE",
g.KindOfT[OauthSecurityIntegrationClientTypeOption](),
g.ParameterOptions().Required().SingleQuotes(),
).
TextAssignment("OAUTH_REDIRECT_URI", g.ParameterOptions().Required().SingleQuotes()).
OptionalBooleanAssignment("ENABLED", g.ParameterOptions()).
OptionalBooleanAssignment("OAUTH_ALLOW_NON_TLS_REDIRECT_URI", g.ParameterOptions()).
OptionalBooleanAssignment("OAUTH_ENFORCE_PKCE", g.ParameterOptions()).
OptionalAssignment(
"OAUTH_USE_SECONDARY_ROLES",
g.KindOfT[OauthSecurityIntegrationUseSecondaryRolesOption](),
g.ParameterOptions(),
).
OptionalQueryStructField("PreAuthorizedRolesList", preAuthorizedRolesListDef, g.ParameterOptions().SQL("PRE_AUTHORIZED_ROLES_LIST").Parentheses()).
OptionalQueryStructField("BlockedRolesList", blockedRolesListDef, g.ParameterOptions().SQL("BLOCKED_ROLES_LIST").Parentheses()).
OptionalBooleanAssignment("OAUTH_ISSUE_REFRESH_TOKENS", g.ParameterOptions()).
OptionalNumberAssignment("OAUTH_REFRESH_TOKEN_VALIDITY", g.ParameterOptions()).
OptionalIdentifier("NetworkPolicy", g.KindOfT[AccountObjectIdentifier](), g.IdentifierOptions().Equals().SQL("NETWORK_POLICY")).
OptionalTextAssignment("OAUTH_CLIENT_RSA_PUBLIC_KEY", g.ParameterOptions().SingleQuotes()).
OptionalTextAssignment("OAUTH_CLIENT_RSA_PUBLIC_KEY_2", g.ParameterOptions().SingleQuotes())
}),
).
CustomOperation(
"CreateSaml2",
"https://docs.snowflake.com/en/sql-reference/sql/create-security-integration-saml2",
Expand Down Expand Up @@ -147,6 +278,36 @@ var SecurityIntegrationsDef = g.NewInterface(
OptionalBooleanAssignment("SYNC_PASSWORD", g.ParameterOptions())
}),
).
CustomOperation(
"AlterOauthForPartnerApplications",
"https://docs.snowflake.com/en/sql-reference/sql/alter-security-integration-oauth-snowflake",
alterSecurityIntegrationOperation("AlterOauthForPartnerApplications", func(qs *g.QueryStruct) *g.QueryStruct {
return qs.OptionalQueryStructField(
"Set",
oauthForPartnerApplicationsIntegrationSetDef,
g.ListOptions().NoParentheses().SQL("SET"),
).OptionalQueryStructField(
"Unset",
oauthForPartnerApplicationsIntegrationUnsetDef,
g.ListOptions().NoParentheses().SQL("UNSET"),
).WithValidation(g.ExactlyOneValueSet, "Set", "Unset", "SetTags", "UnsetTags")
}),
).
CustomOperation(
"AlterOauthForCustomClients",
"https://docs.snowflake.com/en/sql-reference/sql/alter-security-integration-oauth-snowflake",
alterSecurityIntegrationOperation("AlterOauthForCustomClients", func(qs *g.QueryStruct) *g.QueryStruct {
return qs.OptionalQueryStructField(
"Set",
oauthForCustomClientsIntegrationSetDef,
g.ListOptions().NoParentheses().SQL("SET"),
).OptionalQueryStructField(
"Unset",
oauthForCustomClientsIntegrationUnsetDef,
g.ListOptions().NoParentheses().SQL("UNSET"),
).WithValidation(g.ExactlyOneValueSet, "Set", "Unset", "SetTags", "UnsetTags")
}),
).
CustomOperation(
"AlterSaml2",
"https://docs.snowflake.com/en/sql-reference/sql/alter-security-integration-saml2",
Expand Down
Loading

0 comments on commit b576f29

Please sign in to comment.