From 45cb6e5b16f4e1e60c4d6845d3102e4d713728e7 Mon Sep 17 00:00:00 2001 From: James Stocker Date: Tue, 3 Dec 2024 12:40:01 +0100 Subject: [PATCH 1/2] [zitadel/terraform-provider-zitadel#205] Add idp_oidc --- docs/resources/idp_oidc.md | 57 ++++++++++ examples/provider/data-sources/idp_oidc.tf | 3 + .../provider/resources/idp_oidc-import.sh | 2 + examples/provider/resources/idp_oidc.tf | 12 ++ templates/data-sources/idp_oidc.md.tmpl | 16 +++ templates/resources/idp_oidc.md.tmpl | 20 ++++ zitadel/idp_oidc/datasource.go | 27 +++++ zitadel/idp_oidc/funcs.go | 105 ++++++++++++++++++ zitadel/idp_oidc/resource.go | 32 ++++++ zitadel/idp_oidc/resource_test.go | 12 ++ zitadel/idp_oidc/schema.go | 31 ++++++ zitadel/provider.go | 1 + 12 files changed, 318 insertions(+) create mode 100644 docs/resources/idp_oidc.md create mode 100644 examples/provider/data-sources/idp_oidc.tf create mode 100644 examples/provider/resources/idp_oidc-import.sh create mode 100644 examples/provider/resources/idp_oidc.tf create mode 100644 templates/data-sources/idp_oidc.md.tmpl create mode 100644 templates/resources/idp_oidc.md.tmpl create mode 100644 zitadel/idp_oidc/datasource.go create mode 100644 zitadel/idp_oidc/funcs.go create mode 100644 zitadel/idp_oidc/resource.go create mode 100644 zitadel/idp_oidc/resource_test.go create mode 100644 zitadel/idp_oidc/schema.go diff --git a/docs/resources/idp_oidc.md b/docs/resources/idp_oidc.md new file mode 100644 index 00000000..338630f2 --- /dev/null +++ b/docs/resources/idp_oidc.md @@ -0,0 +1,57 @@ +--- +page_title: "zitadel_idp_oidc Resource - terraform-provider-zitadel" +subcategory: "" +description: |- + Resource representing a generic OIDC IdP on the instance. +--- + +# zitadel_org_idp_oidc (Resource) + +Resource representing a generic OIDC IdP on the organization. + +## Example Usage + +```terraform +resource "zitadel_idp_oidc" "default" { + name = "My Generic OIDC IDP" + client_id = "a_client_id" + client_secret = "a_client_secret" + scopes = ["openid", "profile", "email"] + issuer = "https://example.com" + is_linking_allowed = false + is_creation_allowed = true + is_auto_creation = false + is_auto_update = true + is_id_token_mapping = true +} +``` + + +## Schema + +### Required + +- `client_id` (String) client id generated by the identity provider +- `client_secret` (String, Sensitive) client secret generated by the identity provider +- `is_auto_creation` (Boolean) enable if a new account in ZITADEL should be created automatically on login with an external account +- `is_auto_update` (Boolean) enable if a the ZITADEL account fields should be updated automatically on each login +- `is_creation_allowed` (Boolean) enable if users should be able to create a new account in ZITADEL when using an external account +- `is_id_token_mapping` (Boolean) if true, provider information get mapped from the id token, not from the userinfo endpoint +- `is_linking_allowed` (Boolean) enable if users should be able to link an existing ZITADEL user with an external account +- `issuer` (String) the issuer of the idp + +### Optional + +- `name` (String) Name of the IDP +- `scopes` (Set of String) the scopes requested by ZITADEL during the request on the identity provider + +### Read-Only + +- `id` (String) The ID of this resource. + +## Import + +```bash +# The resource can be imported using the ID format ``, e.g. +terraform import zitadel_idp_oidc.imported '123456789012345678:1234567890abcdef' +``` diff --git a/examples/provider/data-sources/idp_oidc.tf b/examples/provider/data-sources/idp_oidc.tf new file mode 100644 index 00000000..a129c673 --- /dev/null +++ b/examples/provider/data-sources/idp_oidc.tf @@ -0,0 +1,3 @@ +data "zitadel_idp_oidc" "default" { + id = "123456789012345678" +} diff --git a/examples/provider/resources/idp_oidc-import.sh b/examples/provider/resources/idp_oidc-import.sh new file mode 100644 index 00000000..81944628 --- /dev/null +++ b/examples/provider/resources/idp_oidc-import.sh @@ -0,0 +1,2 @@ +# The resource can be imported using the ID format ``, e.g. +terraform import zitadel_idp_oidc.imported '123456789012345678:1234567890abcdef' diff --git a/examples/provider/resources/idp_oidc.tf b/examples/provider/resources/idp_oidc.tf new file mode 100644 index 00000000..9c7657a1 --- /dev/null +++ b/examples/provider/resources/idp_oidc.tf @@ -0,0 +1,12 @@ +resource "zitadel_idp_oidc" "default" { + name = "My Generic OIDC IDP" + client_id = "a_client_id" + client_secret = "a_client_secret" + scopes = ["openid", "profile", "email"] + issuer = "https://example.com" + is_linking_allowed = false + is_creation_allowed = true + is_auto_creation = false + is_auto_update = true + is_id_token_mapping = true +} diff --git a/templates/data-sources/idp_oidc.md.tmpl b/templates/data-sources/idp_oidc.md.tmpl new file mode 100644 index 00000000..37779b85 --- /dev/null +++ b/templates/data-sources/idp_oidc.md.tmpl @@ -0,0 +1,16 @@ +--- +page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}" +subcategory: "" +description: |- +{{ .Description | plainmarkdown | trimspace | prefixlines " " }} +--- + +# {{.Name}} ({{.Type}}) + +{{ .Description | trimspace }} + +## Example Usage + +{{ tffile "examples/provider/data-sources/idp_oidc.tf" }} + +{{ .SchemaMarkdown | trimspace }} diff --git a/templates/resources/idp_oidc.md.tmpl b/templates/resources/idp_oidc.md.tmpl new file mode 100644 index 00000000..0b91010e --- /dev/null +++ b/templates/resources/idp_oidc.md.tmpl @@ -0,0 +1,20 @@ +--- +page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}" +subcategory: "" +description: |- +{{ .Description | plainmarkdown | trimspace | prefixlines " " }} +--- + +# {{.Name}} ({{.Type}}) + +{{ .Description | trimspace }} + +## Example Usage + +{{ tffile "examples/provider/resources/idp_oidc.tf" }} + +{{ .SchemaMarkdown | trimspace }} + +## Import + +{{ codefile "bash" "examples/provider/resources/idp_oidc-import.sh" }} diff --git a/zitadel/idp_oidc/datasource.go b/zitadel/idp_oidc/datasource.go new file mode 100644 index 00000000..db59fd1b --- /dev/null +++ b/zitadel/idp_oidc/datasource.go @@ -0,0 +1,27 @@ +package idp_oidc + +import ( + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/zitadel/terraform-provider-zitadel/v2/zitadel/idp_utils" +) + +func GetDatasource() *schema.Resource { + return &schema.Resource{ + Description: "Datasource representing a generic OIDC IdP on the instance.", + Schema: map[string]*schema.Schema{ + idp_utils.IdpIDVar: idp_utils.IdPIDDataSourceField, + idp_utils.NameVar: idp_utils.NameDataSourceField, + idp_utils.ClientIDVar: idp_utils.ClientIDDataSourceField, + idp_utils.ClientSecretVar: idp_utils.ClientSecretDataSourceField, + idp_utils.ScopesVar: idp_utils.ScopesDataSourceField, + idp_utils.IsLinkingAllowedVar: idp_utils.IsLinkingAllowedDataSourceField, + idp_utils.IsCreationAllowedVar: idp_utils.IsCreationAllowedDataSourceField, + idp_utils.IsAutoCreationVar: idp_utils.IsAutoCreationDataSourceField, + idp_utils.IsAutoUpdateVar: idp_utils.IsAutoUpdateDataSourceField, + IssuerVar: IssuerDatasourceField, + IsIdTokenMappingVar: IsIdTokenMappingDatasourceField, + }, + ReadContext: read, + } +} diff --git a/zitadel/idp_oidc/funcs.go b/zitadel/idp_oidc/funcs.go new file mode 100644 index 00000000..58e3603d --- /dev/null +++ b/zitadel/idp_oidc/funcs.go @@ -0,0 +1,105 @@ +package idp_oidc + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/zitadel/zitadel-go/v3/pkg/client/zitadel/admin" + + "github.com/zitadel/terraform-provider-zitadel/v2/zitadel/helper" + "github.com/zitadel/terraform-provider-zitadel/v2/zitadel/idp_utils" +) + +func create(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + clientinfo, ok := m.(*helper.ClientInfo) + if !ok { + return diag.Errorf("failed to get client") + } + client, err := helper.GetAdminClient(ctx, clientinfo) + if err != nil { + return diag.FromErr(err) + } + resp, err := client.AddGenericOIDCProvider(ctx, &admin.AddGenericOIDCProviderRequest{ + Name: idp_utils.StringValue(d, idp_utils.NameVar), + ClientId: idp_utils.StringValue(d, idp_utils.ClientIDVar), + ClientSecret: idp_utils.StringValue(d, idp_utils.ClientSecretVar), + Scopes: idp_utils.ScopesValue(d), + ProviderOptions: idp_utils.ProviderOptionsValue(d), + Issuer: idp_utils.StringValue(d, IssuerVar), + IsIdTokenMapping: idp_utils.BoolValue(d, IsIdTokenMappingVar), + }) + if err != nil { + return diag.Errorf("failed to create idp: %v", err) + } + d.SetId(resp.GetId()) + return nil +} + +func update(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + clientinfo, ok := m.(*helper.ClientInfo) + if !ok { + return diag.Errorf("failed to get client") + } + client, err := helper.GetAdminClient(ctx, clientinfo) + if err != nil { + return diag.FromErr(err) + } + _, err = client.UpdateGenericOIDCProvider(ctx, &admin.UpdateGenericOIDCProviderRequest{ + Id: d.Id(), + Name: idp_utils.StringValue(d, idp_utils.NameVar), + Issuer: idp_utils.StringValue(d, IssuerVar), + ClientId: idp_utils.StringValue(d, idp_utils.ClientIDVar), + ClientSecret: idp_utils.StringValue(d, idp_utils.ClientSecretVar), + Scopes: idp_utils.ScopesValue(d), + ProviderOptions: idp_utils.ProviderOptionsValue(d), + IsIdTokenMapping: idp_utils.BoolValue(d, IsIdTokenMappingVar), + }) + if err != nil { + return diag.Errorf("failed to update idp: %v", err) + } + return nil +} + +func read(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + clientinfo, ok := m.(*helper.ClientInfo) + if !ok { + return diag.Errorf("failed to get client") + } + client, err := helper.GetAdminClient(ctx, clientinfo) + if err != nil { + return diag.FromErr(err) + } + resp, err := client.GetProviderByID(ctx, &admin.GetProviderByIDRequest{Id: helper.GetID(d, idp_utils.IdpIDVar)}) + if err != nil && helper.IgnoreIfNotFoundError(err) == nil { + d.SetId("") + return nil + } + if err != nil { + return diag.Errorf("failed to get idp") + } + idp := resp.GetIdp() + cfg := idp.GetConfig() + specificCfg := cfg.GetOidc() + generalCfg := cfg.GetOptions() + set := map[string]interface{}{ + helper.OrgIDVar: idp.GetDetails().GetResourceOwner(), + idp_utils.NameVar: idp.GetName(), + idp_utils.ClientIDVar: specificCfg.GetClientId(), + idp_utils.ClientSecretVar: idp_utils.StringValue(d, idp_utils.ClientSecretVar), + idp_utils.ScopesVar: specificCfg.GetScopes(), + idp_utils.IsLinkingAllowedVar: generalCfg.GetIsLinkingAllowed(), + idp_utils.IsCreationAllowedVar: generalCfg.GetIsCreationAllowed(), + idp_utils.IsAutoCreationVar: generalCfg.GetIsAutoCreation(), + idp_utils.IsAutoUpdateVar: generalCfg.GetIsAutoUpdate(), + IssuerVar: specificCfg.GetIssuer(), + IsIdTokenMappingVar: specificCfg.GetIsIdTokenMapping(), + } + for k, v := range set { + if err := d.Set(k, v); err != nil { + return diag.Errorf("failed to set %s of oidc idp: %v", k, err) + } + } + d.SetId(idp.Id) + return nil +} diff --git a/zitadel/idp_oidc/resource.go b/zitadel/idp_oidc/resource.go new file mode 100644 index 00000000..7e2a2141 --- /dev/null +++ b/zitadel/idp_oidc/resource.go @@ -0,0 +1,32 @@ +package idp_oidc + +import ( + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/zitadel/terraform-provider-zitadel/v2/zitadel/helper" + "github.com/zitadel/terraform-provider-zitadel/v2/zitadel/idp_utils" +) + +func GetResource() *schema.Resource { + return &schema.Resource{ + Description: "Resource representing an OIDC IDP on the instance.", + Schema: map[string]*schema.Schema{ + helper.OrgIDVar: helper.OrgIDResourceField, + idp_utils.NameVar: idp_utils.NameResourceField, + idp_utils.ClientIDVar: idp_utils.ClientIDResourceField, + idp_utils.ClientSecretVar: idp_utils.ClientSecretResourceField, + idp_utils.ScopesVar: idp_utils.ScopesResourceField, + idp_utils.IsLinkingAllowedVar: idp_utils.IsLinkingAllowedResourceField, + idp_utils.IsCreationAllowedVar: idp_utils.IsCreationAllowedResourceField, + idp_utils.IsAutoCreationVar: idp_utils.IsAutoCreationResourceField, + idp_utils.IsAutoUpdateVar: idp_utils.IsAutoUpdateResourceField, + IssuerVar: IssuerResourceField, + IsIdTokenMappingVar: IsIdTokenMappingResourceField, + }, + ReadContext: read, + UpdateContext: update, + CreateContext: create, + DeleteContext: idp_utils.Delete, + Importer: helper.ImportWithIDAndOptionalOrgAndSecret(idp_utils.IdpIDVar, idp_utils.ClientSecretVar), + } +} diff --git a/zitadel/idp_oidc/resource_test.go b/zitadel/idp_oidc/resource_test.go new file mode 100644 index 00000000..86b240b6 --- /dev/null +++ b/zitadel/idp_oidc/resource_test.go @@ -0,0 +1,12 @@ +package idp_oidc_test + +import ( + "testing" + + "github.com/zitadel/terraform-provider-zitadel/v2/zitadel/idp_utils" + "github.com/zitadel/terraform-provider-zitadel/v2/zitadel/idp_utils/idp_test_utils" +) + +func TestAccInstanceIdPOIDC(t *testing.T) { + idp_test_utils.RunInstanceIDPLifecyleTest(t, "zitadel_idp_oidc", idp_utils.ClientSecretVar) +} diff --git a/zitadel/idp_oidc/schema.go b/zitadel/idp_oidc/schema.go new file mode 100644 index 00000000..3c248cf7 --- /dev/null +++ b/zitadel/idp_oidc/schema.go @@ -0,0 +1,31 @@ +package idp_oidc + +import "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + +const ( + IsIdTokenMappingVar = "is_id_token_mapping" + IssuerVar = "issuer" +) + +var ( + IsIdTokenMappingResourceField = &schema.Schema{ + Type: schema.TypeBool, + Required: true, + Description: "if true, provider information get mapped from the id token, not from the userinfo endpoint", + } + IsIdTokenMappingDatasourceField = &schema.Schema{ + Type: schema.TypeBool, + Computed: true, + Description: "if true, provider information get mapped from the id token, not from the userinfo endpoint.", + } + IssuerResourceField = &schema.Schema{ + Type: schema.TypeString, + Required: true, + Description: "the issuer of the idp", + } + IssuerDatasourceField = &schema.Schema{ + Type: schema.TypeString, + Computed: true, + Description: "the issuer of the idp", + } +) diff --git a/zitadel/provider.go b/zitadel/provider.go index daf4d837..83da8ac5 100644 --- a/zitadel/provider.go +++ b/zitadel/provider.go @@ -49,6 +49,7 @@ import ( "github.com/zitadel/terraform-provider-zitadel/v2/zitadel/idp_google" "github.com/zitadel/terraform-provider-zitadel/v2/zitadel/idp_ldap" "github.com/zitadel/terraform-provider-zitadel/v2/zitadel/idp_oauth" + "github.com/zitadel/terraform-provider-zitadel/v2/zitadel/idp_oidc" "github.com/zitadel/terraform-provider-zitadel/v2/zitadel/idp_saml" "github.com/zitadel/terraform-provider-zitadel/v2/zitadel/init_message_text" "github.com/zitadel/terraform-provider-zitadel/v2/zitadel/instance_member" From 24de230ff3654e50a89ea32bd7913ded583fb077 Mon Sep 17 00:00:00 2001 From: James Stocker Date: Mon, 9 Dec 2024 20:42:07 +0100 Subject: [PATCH 2/2] [#205] Update after review feedback --- zitadel/provider.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/zitadel/provider.go b/zitadel/provider.go index 83da8ac5..fca0baad 100644 --- a/zitadel/provider.go +++ b/zitadel/provider.go @@ -237,6 +237,7 @@ func Provider() *schema.Provider { "zitadel_idp_ldap": idp_ldap.GetDatasource(), "zitadel_idp_saml": idp_saml.GetDatasource(), "zitadel_idp_oauth": idp_oauth.GetDatasource(), + "zitadel_idp_oidc": idp_oidc.GetDatasource(), "zitadel_org_jwt_idp": org_idp_jwt.GetDatasource(), "zitadel_org_oidc_idp": org_idp_oidc.GetDatasource(), "zitadel_org_idp_github": org_idp_github.GetDatasource(), @@ -328,6 +329,7 @@ func Provider() *schema.Provider { "zitadel_idp_ldap": idp_ldap.GetResource(), "zitadel_idp_saml": idp_saml.GetResource(), "zitadel_idp_oauth": idp_oauth.GetResource(), + "zitadel_idp_oidc": idp_oidc.GetResource(), "zitadel_org_idp_jwt": org_idp_jwt.GetResource(), "zitadel_org_idp_oidc": org_idp_oidc.GetResource(), "zitadel_org_idp_github": org_idp_github.GetResource(),