diff --git a/sysdig/internal/client/v2/sso_openid.go b/sysdig/internal/client/v2/sso_openid.go index 5a25c25c..655e45ec 100644 --- a/sysdig/internal/client/v2/sso_openid.go +++ b/sysdig/internal/client/v2/sso_openid.go @@ -14,23 +14,28 @@ const ( getSSOOpenIDPath = "%s/platform/v1/sso-settings/%d" updateSSOOpenIDPath = "%s/platform/v1/sso-settings/%d" deleteSSOOpenIDPath = "%s/platform/v1/sso-settings/%d" + + createSystemSSOOpenIDPath = "%s/platform/v1/system-sso-settings/" + getSystemSSOOpenIDPath = "%s/platform/v1/system-sso-settings/%d" + updateSystemSSOOpenIDPath = "%s/platform/v1/system-sso-settings/%d" + deleteSystemSSOOpenIDPath = "%s/platform/v1/system-sso-settings/%d" ) type SSOOpenIDInterface interface { Base - CreateSSOOpenID(ctx context.Context, sso *SSOOpenID) (*SSOOpenID, error) - GetSSOOpenID(ctx context.Context, id int) (*SSOOpenID, error) - UpdateSSOOpenID(ctx context.Context, id int, sso *SSOOpenID) (*SSOOpenID, error) - DeleteSSOOpenID(ctx context.Context, id int) error + CreateSSOOpenID(ctx context.Context, isSystem bool, sso *SSOOpenID) (*SSOOpenID, error) + GetSSOOpenID(ctx context.Context, isSystem bool, id int) (*SSOOpenID, error) + UpdateSSOOpenID(ctx context.Context, isSystem bool, id int, sso *SSOOpenID) (*SSOOpenID, error) + DeleteSSOOpenID(ctx context.Context, isSystem bool, id int) error } -func (c *Client) CreateSSOOpenID(ctx context.Context, sso *SSOOpenID) (result *SSOOpenID, err error) { +func (c *Client) CreateSSOOpenID(ctx context.Context, isSystem bool, sso *SSOOpenID) (result *SSOOpenID, err error) { payload, err := Marshal(sso) if err != nil { return nil, err } - response, err := c.requester.Request(ctx, http.MethodPost, c.createSSOOpenIDURL(), payload) + response, err := c.requester.Request(ctx, http.MethodPost, c.createSSOOpenIDURL(isSystem), payload) if err != nil { return nil, err } @@ -47,8 +52,8 @@ func (c *Client) CreateSSOOpenID(ctx context.Context, sso *SSOOpenID) (result *S return Unmarshal[*SSOOpenID](response.Body) } -func (c *Client) GetSSOOpenID(ctx context.Context, id int) (result *SSOOpenID, err error) { - response, err := c.requester.Request(ctx, http.MethodGet, c.getSSOOpenIDURL(id), nil) +func (c *Client) GetSSOOpenID(ctx context.Context, isSystem bool, id int) (result *SSOOpenID, err error) { + response, err := c.requester.Request(ctx, http.MethodGet, c.getSSOOpenIDURL(isSystem, id), nil) if err != nil { return nil, err } @@ -68,13 +73,13 @@ func (c *Client) GetSSOOpenID(ctx context.Context, id int) (result *SSOOpenID, e return Unmarshal[*SSOOpenID](response.Body) } -func (c *Client) UpdateSSOOpenID(ctx context.Context, id int, sso *SSOOpenID) (result *SSOOpenID, err error) { +func (c *Client) UpdateSSOOpenID(ctx context.Context, isSystem bool, id int, sso *SSOOpenID) (result *SSOOpenID, err error) { payload, err := Marshal(sso) if err != nil { return nil, err } - response, err := c.requester.Request(ctx, http.MethodPut, c.updateSSOOpenIDURL(id), payload) + response, err := c.requester.Request(ctx, http.MethodPut, c.updateSSOOpenIDURL(isSystem, id), payload) if err != nil { return nil, err } @@ -91,8 +96,8 @@ func (c *Client) UpdateSSOOpenID(ctx context.Context, id int, sso *SSOOpenID) (r return Unmarshal[*SSOOpenID](response.Body) } -func (c *Client) DeleteSSOOpenID(ctx context.Context, id int) (err error) { - response, err := c.requester.Request(ctx, http.MethodDelete, c.deleteSSOOpenIDURL(id), nil) +func (c *Client) DeleteSSOOpenID(ctx context.Context, isSystem bool, id int) (err error) { + response, err := c.requester.Request(ctx, http.MethodDelete, c.deleteSSOOpenIDURL(isSystem, id), nil) if err != nil { return err } @@ -109,18 +114,34 @@ func (c *Client) DeleteSSOOpenID(ctx context.Context, id int) (err error) { return nil } -func (c *Client) createSSOOpenIDURL() string { - return fmt.Sprintf(createSSOOpenIDPath, c.config.url) +func (c *Client) createSSOOpenIDURL(isSystem bool) string { + path := createSSOOpenIDPath + if isSystem { + path = createSystemSSOOpenIDPath + } + return fmt.Sprintf(path, c.config.url) } -func (c *Client) getSSOOpenIDURL(id int) string { - return fmt.Sprintf(getSSOOpenIDPath, c.config.url, id) +func (c *Client) getSSOOpenIDURL(isSystem bool, id int) string { + path := getSSOOpenIDPath + if isSystem { + path = getSystemSSOOpenIDPath + } + return fmt.Sprintf(path, c.config.url, id) } -func (c *Client) updateSSOOpenIDURL(id int) string { - return fmt.Sprintf(updateSSOOpenIDPath, c.config.url, id) +func (c *Client) updateSSOOpenIDURL(isSystem bool, id int) string { + path := updateSSOOpenIDPath + if isSystem { + path = updateSystemSSOOpenIDPath + } + return fmt.Sprintf(path, c.config.url, id) } -func (c *Client) deleteSSOOpenIDURL(id int) string { - return fmt.Sprintf(deleteSSOOpenIDPath, c.config.url, id) +func (c *Client) deleteSSOOpenIDURL(isSystem bool, id int) string { + path := deleteSSOOpenIDPath + if isSystem { + path = deleteSystemSSOOpenIDPath + } + return fmt.Sprintf(path, c.config.url, id) } diff --git a/sysdig/internal/client/v2/sso_saml.go b/sysdig/internal/client/v2/sso_saml.go index 5ba8f59c..33fbfb10 100644 --- a/sysdig/internal/client/v2/sso_saml.go +++ b/sysdig/internal/client/v2/sso_saml.go @@ -14,23 +14,28 @@ const ( getSSOSamlPath = "%s/platform/v1/sso-settings/%d" updateSSOSamlPath = "%s/platform/v1/sso-settings/%d" deleteSSOSamlPath = "%s/platform/v1/sso-settings/%d" + + createSystemSSOSamlPath = "%s/platform/v1/system-sso-settings/" + getSystemSSOSamlPath = "%s/platform/v1/system-sso-settings/%d" + updateSystemSSOSamlPath = "%s/platform/v1/system-sso-settings/%d" + deleteSystemSSOSamlPath = "%s/platform/v1/system-sso-settings/%d" ) type SSOSamlInterface interface { Base - CreateSSOSaml(ctx context.Context, sso *SSOSaml) (*SSOSaml, error) - GetSSOSaml(ctx context.Context, id int) (*SSOSaml, error) - UpdateSSOSaml(ctx context.Context, id int, sso *SSOSaml) (*SSOSaml, error) - DeleteSSOSaml(ctx context.Context, id int) error + CreateSSOSaml(ctx context.Context, isSystem bool, sso *SSOSaml) (*SSOSaml, error) + GetSSOSaml(ctx context.Context, isSystem bool, id int) (*SSOSaml, error) + UpdateSSOSaml(ctx context.Context, isSystem bool, id int, sso *SSOSaml) (*SSOSaml, error) + DeleteSSOSaml(ctx context.Context, isSystem bool, id int) error } -func (c *Client) CreateSSOSaml(ctx context.Context, sso *SSOSaml) (result *SSOSaml, err error) { +func (c *Client) CreateSSOSaml(ctx context.Context, isSystem bool, sso *SSOSaml) (result *SSOSaml, err error) { payload, err := Marshal(sso) if err != nil { return nil, err } - response, err := c.requester.Request(ctx, http.MethodPost, c.createSSOSamlURL(), payload) + response, err := c.requester.Request(ctx, http.MethodPost, c.createSSOSamlURL(isSystem), payload) if err != nil { return nil, err } @@ -47,8 +52,8 @@ func (c *Client) CreateSSOSaml(ctx context.Context, sso *SSOSaml) (result *SSOSa return Unmarshal[*SSOSaml](response.Body) } -func (c *Client) GetSSOSaml(ctx context.Context, id int) (result *SSOSaml, err error) { - response, err := c.requester.Request(ctx, http.MethodGet, c.getSSOSamlURL(id), nil) +func (c *Client) GetSSOSaml(ctx context.Context, isSystem bool, id int) (result *SSOSaml, err error) { + response, err := c.requester.Request(ctx, http.MethodGet, c.getSSOSamlURL(isSystem, id), nil) if err != nil { return nil, err } @@ -68,13 +73,13 @@ func (c *Client) GetSSOSaml(ctx context.Context, id int) (result *SSOSaml, err e return Unmarshal[*SSOSaml](response.Body) } -func (c *Client) UpdateSSOSaml(ctx context.Context, id int, sso *SSOSaml) (result *SSOSaml, err error) { +func (c *Client) UpdateSSOSaml(ctx context.Context, isSystem bool, id int, sso *SSOSaml) (result *SSOSaml, err error) { payload, err := Marshal(sso) if err != nil { return nil, err } - response, err := c.requester.Request(ctx, http.MethodPut, c.updateSSOSamlURL(id), payload) + response, err := c.requester.Request(ctx, http.MethodPut, c.updateSSOSamlURL(isSystem, id), payload) if err != nil { return nil, err } @@ -91,8 +96,8 @@ func (c *Client) UpdateSSOSaml(ctx context.Context, id int, sso *SSOSaml) (resul return Unmarshal[*SSOSaml](response.Body) } -func (c *Client) DeleteSSOSaml(ctx context.Context, id int) (err error) { - response, err := c.requester.Request(ctx, http.MethodDelete, c.deleteSSOSamlURL(id), nil) +func (c *Client) DeleteSSOSaml(ctx context.Context, isSystem bool, id int) (err error) { + response, err := c.requester.Request(ctx, http.MethodDelete, c.deleteSSOSamlURL(isSystem, id), nil) if err != nil { return err } @@ -109,18 +114,34 @@ func (c *Client) DeleteSSOSaml(ctx context.Context, id int) (err error) { return nil } -func (c *Client) createSSOSamlURL() string { - return fmt.Sprintf(createSSOSamlPath, c.config.url) +func (c *Client) createSSOSamlURL(isSystem bool) string { + path := createSSOSamlPath + if isSystem { + path = createSystemSSOSamlPath + } + return fmt.Sprintf(path, c.config.url) } -func (c *Client) getSSOSamlURL(id int) string { - return fmt.Sprintf(getSSOSamlPath, c.config.url, id) +func (c *Client) getSSOSamlURL(isSystem bool, id int) string { + path := getSSOSamlPath + if isSystem { + path = getSystemSSOSamlPath + } + return fmt.Sprintf(path, c.config.url, id) } -func (c *Client) updateSSOSamlURL(id int) string { - return fmt.Sprintf(updateSSOSamlPath, c.config.url, id) +func (c *Client) updateSSOSamlURL(isSystem bool, id int) string { + path := updateSSOSamlPath + if isSystem { + path = updateSystemSSOSamlPath + } + return fmt.Sprintf(path, c.config.url, id) } -func (c *Client) deleteSSOSamlURL(id int) string { - return fmt.Sprintf(deleteSSOSamlPath, c.config.url, id) +func (c *Client) deleteSSOSamlURL(isSystem bool, id int) string { + path := deleteSSOSamlPath + if isSystem { + path = deleteSystemSSOSamlPath + } + return fmt.Sprintf(path, c.config.url, id) } diff --git a/sysdig/resource_sysdig_sso_openid.go b/sysdig/resource_sysdig_sso_openid.go index 8eaaa74a..709ecdaa 100644 --- a/sysdig/resource_sysdig_sso_openid.go +++ b/sysdig/resource_sysdig_sso_openid.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "strconv" + "strings" "time" v2 "github.com/draios/terraform-provider-sysdig/sysdig/internal/client/v2" @@ -21,7 +22,7 @@ func resourceSysdigSSOOpenID() *schema.Resource { UpdateContext: resourceSysdigSSOOpenIDUpdate, DeleteContext: resourceSysdigSSOOpenIDDelete, Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, + StateContext: importSSOOpenIDState, }, Timeouts: &schema.ResourceTimeout{ Create: schema.DefaultTimeout(timeout), @@ -50,6 +51,13 @@ func resourceSysdigSSOOpenID() *schema.Resource { }, // Optional base SSO fields + "is_system": { + Type: schema.TypeBool, + Optional: true, + Default: false, + ForceNew: true, + Description: "Whether this is a system SSO configuration (Only applicable to on-prem installations)", + }, "product": { Type: schema.TypeString, Optional: true, @@ -185,6 +193,21 @@ func validateSSOOpenIDMetadata(_ context.Context, diff *schema.ResourceDiff, _ a return nil } +func importSSOOpenIDState(_ context.Context, d *schema.ResourceData, _ any) ([]*schema.ResourceData, error) { + importID := d.Id() + if strings.HasPrefix(importID, "system/") { + if err := d.Set("is_system", true); err != nil { + return nil, err + } + d.SetId(strings.TrimPrefix(importID, "system/")) + } else { + if err := d.Set("is_system", false); err != nil { + return nil, err + } + } + return []*schema.ResourceData{d}, nil +} + func resourceSysdigSSOOpenIDRead(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics { client, err := m.(SysdigClients).sysdigCommonClientV2() if err != nil { @@ -196,7 +219,9 @@ func resourceSysdigSSOOpenIDRead(ctx context.Context, d *schema.ResourceData, m return diag.FromErr(err) } - sso, err := client.GetSSOOpenID(ctx, id) + isSystem := d.Get("is_system").(bool) + + sso, err := client.GetSSOOpenID(ctx, isSystem, id) if err != nil { if err == v2.ErrSSOOpenIDNotFound { d.SetId("") @@ -214,9 +239,10 @@ func resourceSysdigSSOOpenIDCreate(ctx context.Context, d *schema.ResourceData, return diag.FromErr(err) } + isSystem := d.Get("is_system").(bool) sso := ssoOpenIDFromResourceData(d) - created, err := client.CreateSSOOpenID(ctx, sso) + created, err := client.CreateSSOOpenID(ctx, isSystem, sso) if err != nil { return diag.FromErr(err) } @@ -237,11 +263,12 @@ func resourceSysdigSSOOpenIDUpdate(ctx context.Context, d *schema.ResourceData, return diag.FromErr(err) } + isSystem := d.Get("is_system").(bool) sso := ssoOpenIDFromResourceData(d) sso.ID = id sso.Version = d.Get("version").(int) - _, err = client.UpdateSSOOpenID(ctx, id, sso) + _, err = client.UpdateSSOOpenID(ctx, isSystem, id, sso) if err != nil { return diag.FromErr(err) } @@ -260,6 +287,8 @@ func resourceSysdigSSOOpenIDDelete(ctx context.Context, d *schema.ResourceData, return diag.FromErr(err) } + isSystem := d.Get("is_system").(bool) + // API requires disabling SSO config before deletion // We need to build the object from ResourceData to include client_secret // (which is not returned by GET but is required for PUT) @@ -269,13 +298,13 @@ func resourceSysdigSSOOpenIDDelete(ctx context.Context, d *schema.ResourceData, sso.Version = d.Get("version").(int) sso.IsActive = false - _, err = client.UpdateSSOOpenID(ctx, id, sso) + _, err = client.UpdateSSOOpenID(ctx, isSystem, id, sso) if err != nil { return diag.Errorf("failed to disable SSO config before deletion: %s", err) } } - err = client.DeleteSSOOpenID(ctx, id) + err = client.DeleteSSOOpenID(ctx, isSystem, id) if err != nil { return diag.FromErr(err) } diff --git a/sysdig/resource_sysdig_sso_openid_onprem_test.go b/sysdig/resource_sysdig_sso_openid_onprem_test.go new file mode 100644 index 00000000..392b1f32 --- /dev/null +++ b/sysdig/resource_sysdig_sso_openid_onprem_test.go @@ -0,0 +1,301 @@ +//go:build tf_acc_onprem_monitor || tf_acc_onprem_secure + +package sysdig_test + +import ( + "fmt" + "testing" + + "github.com/draios/terraform-provider-sysdig/sysdig" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func TestAccSSOOpenIDOnprem_Basic(t *testing.T) { + integrationName := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: preCheckAnyEnv(t, SysdigMonitorApiTokenEnv, SysdigSecureApiTokenEnv), + ProviderFactories: map[string]func() (*schema.Provider, error){ + "sysdig": func() (*schema.Provider, error) { + return sysdig.Provider(), nil + }, + }, + Steps: []resource.TestStep{ + { + Config: ssoOpenIDOnpremBasicConfig(integrationName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "sysdig_sso_openid.test", + "is_system", + "true", + ), + resource.TestCheckResourceAttr( + "sysdig_sso_openid.test", + "issuer_url", + "https://accounts.google.com", + ), + resource.TestCheckResourceAttr( + "sysdig_sso_openid.test", + "client_id", + "test-client-id", + ), + resource.TestCheckResourceAttr( + "sysdig_sso_openid.test", + "integration_name", + integrationName, + ), + resource.TestCheckResourceAttr( + "sysdig_sso_openid.test", + "is_active", + "true", + ), + resource.TestCheckResourceAttr( + "sysdig_sso_openid.test", + "is_metadata_discovery_enabled", + "true", + ), + resource.TestCheckResourceAttrSet( + "sysdig_sso_openid.test", + "version", + ), + ), + }, + { + ResourceName: "sysdig_sso_openid.test", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"client_secret"}, + ImportStateIdFunc: func(s *terraform.State) (string, error) { + return "system/" + s.RootModule().Resources["sysdig_sso_openid.test"].Primary.ID, nil + }, + }, + }, + }) +} + +func TestAccSSOOpenIDOnprem_WithMetadata(t *testing.T) { + integrationName := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: preCheckAnyEnv(t, SysdigMonitorApiTokenEnv, SysdigSecureApiTokenEnv), + ProviderFactories: map[string]func() (*schema.Provider, error){ + "sysdig": func() (*schema.Provider, error) { + return sysdig.Provider(), nil + }, + }, + Steps: []resource.TestStep{ + { + Config: ssoOpenIDOnpremWithMetadataConfig(integrationName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "sysdig_sso_openid.test_metadata", + "is_system", + "true", + ), + resource.TestCheckResourceAttr( + "sysdig_sso_openid.test_metadata", + "is_metadata_discovery_enabled", + "false", + ), + resource.TestCheckResourceAttr( + "sysdig_sso_openid.test_metadata", + "metadata.0.issuer", + "https://idp.example.com", + ), + resource.TestCheckResourceAttr( + "sysdig_sso_openid.test_metadata", + "metadata.0.authorization_endpoint", + "https://idp.example.com/oauth2/authorize", + ), + resource.TestCheckResourceAttr( + "sysdig_sso_openid.test_metadata", + "metadata.0.token_endpoint", + "https://idp.example.com/oauth2/token", + ), + resource.TestCheckResourceAttr( + "sysdig_sso_openid.test_metadata", + "metadata.0.jwks_uri", + "https://idp.example.com/.well-known/jwks.json", + ), + resource.TestCheckResourceAttr( + "sysdig_sso_openid.test_metadata", + "metadata.0.token_auth_method", + "CLIENT_SECRET_BASIC", + ), + ), + }, + { + ResourceName: "sysdig_sso_openid.test_metadata", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"client_secret"}, + ImportStateIdFunc: func(s *terraform.State) (string, error) { + return "system/" + s.RootModule().Resources["sysdig_sso_openid.test_metadata"].Primary.ID, nil + }, + }, + }, + }) +} + +func TestAccSSOOpenIDOnprem_Update(t *testing.T) { + integrationName := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: preCheckAnyEnv(t, SysdigMonitorApiTokenEnv, SysdigSecureApiTokenEnv), + ProviderFactories: map[string]func() (*schema.Provider, error){ + "sysdig": func() (*schema.Provider, error) { + return sysdig.Provider(), nil + }, + }, + Steps: []resource.TestStep{ + { + Config: ssoOpenIDOnpremBasicConfig(integrationName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "sysdig_sso_openid.test", + "integration_name", + integrationName, + ), + resource.TestCheckResourceAttr( + "sysdig_sso_openid.test", + "is_group_mapping_enabled", + "false", + ), + ), + }, + { + Config: ssoOpenIDOnpremUpdatedConfig(integrationName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "sysdig_sso_openid.test", + "integration_name", + integrationName, // integration_name cannot be updated (ForceNew) + ), + resource.TestCheckResourceAttr( + "sysdig_sso_openid.test", + "is_group_mapping_enabled", + "true", + ), + resource.TestCheckResourceAttr( + "sysdig_sso_openid.test", + "group_mapping_attribute_name", + "custom_groups", + ), + ), + }, + }, + }) +} + +func TestAccSSOOpenIDOnprem_WithAdditionalScopes(t *testing.T) { + integrationName := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: preCheckAnyEnv(t, SysdigMonitorApiTokenEnv, SysdigSecureApiTokenEnv), + ProviderFactories: map[string]func() (*schema.Provider, error){ + "sysdig": func() (*schema.Provider, error) { + return sysdig.Provider(), nil + }, + }, + Steps: []resource.TestStep{ + { + Config: ssoOpenIDOnpremWithAdditionalScopesConfig(integrationName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "sysdig_sso_openid.test_scopes", + "is_system", + "true", + ), + resource.TestCheckResourceAttr( + "sysdig_sso_openid.test_scopes", + "is_additional_scopes_check_enabled", + "true", + ), + resource.TestCheckResourceAttr( + "sysdig_sso_openid.test_scopes", + "additional_scopes.#", + "2", + ), + resource.TestCheckResourceAttr( + "sysdig_sso_openid.test_scopes", + "additional_scopes.0", + "groups", + ), + resource.TestCheckResourceAttr( + "sysdig_sso_openid.test_scopes", + "additional_scopes.1", + "roles", + ), + ), + }, + }, + }) +} + +func ssoOpenIDOnpremBasicConfig(integrationName string) string { + return fmt.Sprintf(` +resource "sysdig_sso_openid" "test" { + is_system = true + issuer_url = "https://accounts.google.com" + client_id = "test-client-id" + client_secret = "test-client-secret" + integration_name = "%s" + is_active = true +} +`, integrationName) +} + +func ssoOpenIDOnpremUpdatedConfig(integrationName string) string { + return fmt.Sprintf(` +resource "sysdig_sso_openid" "test" { + is_system = true + issuer_url = "https://accounts.google.com" + client_id = "test-client-id" + client_secret = "test-client-secret" + integration_name = "%s" + is_active = true + is_group_mapping_enabled = true + group_mapping_attribute_name = "custom_groups" +} +`, integrationName) +} + +func ssoOpenIDOnpremWithMetadataConfig(integrationName string) string { + return fmt.Sprintf(` +resource "sysdig_sso_openid" "test_metadata" { + is_system = true + issuer_url = "https://idp.example.com" + client_id = "test-client-id" + client_secret = "test-client-secret" + integration_name = "%s" + is_metadata_discovery_enabled = false + + metadata { + issuer = "https://idp.example.com" + authorization_endpoint = "https://idp.example.com/oauth2/authorize" + token_endpoint = "https://idp.example.com/oauth2/token" + jwks_uri = "https://idp.example.com/.well-known/jwks.json" + token_auth_method = "CLIENT_SECRET_BASIC" + end_session_endpoint = "https://idp.example.com/oauth2/logout" + user_info_endpoint = "https://idp.example.com/userinfo" + } +} +`, integrationName) +} + +func ssoOpenIDOnpremWithAdditionalScopesConfig(integrationName string) string { + return fmt.Sprintf(` +resource "sysdig_sso_openid" "test_scopes" { + is_system = true + issuer_url = "https://accounts.google.com" + client_id = "test-client-id" + client_secret = "test-client-secret" + integration_name = "%s" + is_additional_scopes_check_enabled = true + additional_scopes = ["groups", "roles"] +} +`, integrationName) +} diff --git a/sysdig/resource_sysdig_sso_openid_test.go b/sysdig/resource_sysdig_sso_openid_test.go index f2c3ba01..375c4a1f 100644 --- a/sysdig/resource_sysdig_sso_openid_test.go +++ b/sysdig/resource_sysdig_sso_openid_test.go @@ -1,10 +1,9 @@ -//go:build tf_acc_sysdig_monitor || tf_acc_sysdig_secure || tf_acc_onprem_monitor || tf_acc_onprem_secure +//go:build tf_acc_sysdig_monitor || tf_acc_sysdig_secure package sysdig_test import ( "fmt" - "os" "testing" "github.com/draios/terraform-provider-sysdig/sysdig" @@ -17,13 +16,7 @@ func TestAccSSOOpenID_Basic(t *testing.T) { integrationName := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { - monitor := os.Getenv("SYSDIG_MONITOR_API_TOKEN") - secure := os.Getenv("SYSDIG_SECURE_API_TOKEN") - if monitor == "" && secure == "" { - t.Fatal("SYSDIG_MONITOR_API_TOKEN or SYSDIG_SECURE_API_TOKEN must be set for acceptance tests") - } - }, + PreCheck: preCheckAnyEnv(t, SysdigMonitorApiTokenEnv, SysdigSecureApiTokenEnv), ProviderFactories: map[string]func() (*schema.Provider, error){ "sysdig": func() (*schema.Provider, error) { return sysdig.Provider(), nil @@ -78,13 +71,7 @@ func TestAccSSOOpenID_WithMetadata(t *testing.T) { integrationName := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { - monitor := os.Getenv("SYSDIG_MONITOR_API_TOKEN") - secure := os.Getenv("SYSDIG_SECURE_API_TOKEN") - if monitor == "" && secure == "" { - t.Fatal("SYSDIG_MONITOR_API_TOKEN or SYSDIG_SECURE_API_TOKEN must be set for acceptance tests") - } - }, + PreCheck: preCheckAnyEnv(t, SysdigMonitorApiTokenEnv, SysdigSecureApiTokenEnv), ProviderFactories: map[string]func() (*schema.Provider, error){ "sysdig": func() (*schema.Provider, error) { return sysdig.Provider(), nil @@ -140,13 +127,7 @@ func TestAccSSOOpenID_Update(t *testing.T) { integrationName := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { - monitor := os.Getenv("SYSDIG_MONITOR_API_TOKEN") - secure := os.Getenv("SYSDIG_SECURE_API_TOKEN") - if monitor == "" && secure == "" { - t.Fatal("SYSDIG_MONITOR_API_TOKEN or SYSDIG_SECURE_API_TOKEN must be set for acceptance tests") - } - }, + PreCheck: preCheckAnyEnv(t, SysdigMonitorApiTokenEnv, SysdigSecureApiTokenEnv), ProviderFactories: map[string]func() (*schema.Provider, error){ "sysdig": func() (*schema.Provider, error) { return sysdig.Provider(), nil @@ -196,13 +177,7 @@ func TestAccSSOOpenID_WithAdditionalScopes(t *testing.T) { integrationName := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { - monitor := os.Getenv("SYSDIG_MONITOR_API_TOKEN") - secure := os.Getenv("SYSDIG_SECURE_API_TOKEN") - if monitor == "" && secure == "" { - t.Fatal("SYSDIG_MONITOR_API_TOKEN or SYSDIG_SECURE_API_TOKEN must be set for acceptance tests") - } - }, + PreCheck: preCheckAnyEnv(t, SysdigMonitorApiTokenEnv, SysdigSecureApiTokenEnv), ProviderFactories: map[string]func() (*schema.Provider, error){ "sysdig": func() (*schema.Provider, error) { return sysdig.Provider(), nil diff --git a/sysdig/resource_sysdig_sso_saml.go b/sysdig/resource_sysdig_sso_saml.go index 854bfa3d..5abd70c8 100644 --- a/sysdig/resource_sysdig_sso_saml.go +++ b/sysdig/resource_sysdig_sso_saml.go @@ -3,6 +3,7 @@ package sysdig import ( "context" "strconv" + "strings" "time" v2 "github.com/draios/terraform-provider-sysdig/sysdig/internal/client/v2" @@ -20,7 +21,7 @@ func resourceSysdigSSOSaml() *schema.Resource { UpdateContext: resourceSysdigSSOSamlUpdate, DeleteContext: resourceSysdigSSOSamlDelete, Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, + StateContext: importSSOSamlState, }, Timeouts: &schema.ResourceTimeout{ Create: schema.DefaultTimeout(timeout), @@ -51,6 +52,13 @@ func resourceSysdigSSOSaml() *schema.Resource { }, // Optional base SSO fields (shared with OpenID) + "is_system": { + Type: schema.TypeBool, + Optional: true, + Default: false, + ForceNew: true, + Description: "Whether this is a system SSO configuration (Only applicable to on-prem installations)", + }, "product": { Type: schema.TypeString, Optional: true, @@ -131,6 +139,21 @@ func resourceSysdigSSOSaml() *schema.Resource { } } +func importSSOSamlState(_ context.Context, d *schema.ResourceData, _ any) ([]*schema.ResourceData, error) { + importID := d.Id() + if strings.HasPrefix(importID, "system/") { + if err := d.Set("is_system", true); err != nil { + return nil, err + } + d.SetId(strings.TrimPrefix(importID, "system/")) + } else { + if err := d.Set("is_system", false); err != nil { + return nil, err + } + } + return []*schema.ResourceData{d}, nil +} + func resourceSysdigSSOSamlRead(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics { client, err := m.(SysdigClients).sysdigCommonClientV2() if err != nil { @@ -142,7 +165,9 @@ func resourceSysdigSSOSamlRead(ctx context.Context, d *schema.ResourceData, m an return diag.FromErr(err) } - sso, err := client.GetSSOSaml(ctx, id) + isSystem := d.Get("is_system").(bool) + + sso, err := client.GetSSOSaml(ctx, isSystem, id) if err != nil { if err == v2.ErrSSOSamlNotFound { d.SetId("") @@ -160,9 +185,10 @@ func resourceSysdigSSOSamlCreate(ctx context.Context, d *schema.ResourceData, m return diag.FromErr(err) } + isSystem := d.Get("is_system").(bool) sso := ssoSamlFromResourceData(d) - created, err := client.CreateSSOSaml(ctx, sso) + created, err := client.CreateSSOSaml(ctx, isSystem, sso) if err != nil { return diag.FromErr(err) } @@ -183,11 +209,12 @@ func resourceSysdigSSOSamlUpdate(ctx context.Context, d *schema.ResourceData, m return diag.FromErr(err) } + isSystem := d.Get("is_system").(bool) sso := ssoSamlFromResourceData(d) sso.ID = id sso.Version = d.Get("version").(int) - _, err = client.UpdateSSOSaml(ctx, id, sso) + _, err = client.UpdateSSOSaml(ctx, isSystem, id, sso) if err != nil { return diag.FromErr(err) } @@ -206,6 +233,8 @@ func resourceSysdigSSOSamlDelete(ctx context.Context, d *schema.ResourceData, m return diag.FromErr(err) } + isSystem := d.Get("is_system").(bool) + // API requires disabling SSO config before deletion if d.Get("is_active").(bool) { sso := ssoSamlFromResourceData(d) @@ -213,13 +242,13 @@ func resourceSysdigSSOSamlDelete(ctx context.Context, d *schema.ResourceData, m sso.Version = d.Get("version").(int) sso.IsActive = false - _, err = client.UpdateSSOSaml(ctx, id, sso) + _, err = client.UpdateSSOSaml(ctx, isSystem, id, sso) if err != nil { return diag.Errorf("failed to disable SSO config before deletion: %s", err) } } - err = client.DeleteSSOSaml(ctx, id) + err = client.DeleteSSOSaml(ctx, isSystem, id) if err != nil { return diag.FromErr(err) } diff --git a/sysdig/resource_sysdig_sso_saml_onprem_test.go b/sysdig/resource_sysdig_sso_saml_onprem_test.go new file mode 100644 index 00000000..0f445adf --- /dev/null +++ b/sysdig/resource_sysdig_sso_saml_onprem_test.go @@ -0,0 +1,279 @@ +//go:build tf_acc_onprem_monitor || tf_acc_onprem_secure + +package sysdig_test + +import ( + "fmt" + "testing" + + "github.com/draios/terraform-provider-sysdig/sysdig" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func TestAccSSOSamlOnprem_WithMetadataURL(t *testing.T) { + integrationName := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: preCheckAnyEnv(t, SysdigMonitorApiTokenEnv, SysdigSecureApiTokenEnv), + ProviderFactories: map[string]func() (*schema.Provider, error){ + "sysdig": func() (*schema.Provider, error) { + return sysdig.Provider(), nil + }, + }, + Steps: []resource.TestStep{ + { + Config: ssoSamlOnpremWithMetadataURLConfig(integrationName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "sysdig_sso_saml.test", + "is_system", + "true", + ), + resource.TestCheckResourceAttr( + "sysdig_sso_saml.test", + "metadata_url", + "https://idp.example.com/metadata", + ), + resource.TestCheckResourceAttr( + "sysdig_sso_saml.test", + "email_parameter", + "email", + ), + resource.TestCheckResourceAttr( + "sysdig_sso_saml.test", + "integration_name", + integrationName, + ), + resource.TestCheckResourceAttr( + "sysdig_sso_saml.test", + "is_active", + "true", + ), + resource.TestCheckResourceAttr( + "sysdig_sso_saml.test", + "is_signature_validation_enabled", + "true", + ), + resource.TestCheckResourceAttrSet( + "sysdig_sso_saml.test", + "version", + ), + ), + }, + { + ResourceName: "sysdig_sso_saml.test", + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: func(s *terraform.State) (string, error) { + return "system/" + s.RootModule().Resources["sysdig_sso_saml.test"].Primary.ID, nil + }, + }, + }, + }) +} + +func TestAccSSOSamlOnprem_WithMetadataXML(t *testing.T) { + integrationName := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: preCheckAnyEnv(t, SysdigMonitorApiTokenEnv, SysdigSecureApiTokenEnv), + ProviderFactories: map[string]func() (*schema.Provider, error){ + "sysdig": func() (*schema.Provider, error) { + return sysdig.Provider(), nil + }, + }, + Steps: []resource.TestStep{ + { + Config: ssoSamlOnpremWithMetadataXMLConfig(integrationName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "sysdig_sso_saml.test_xml", + "is_system", + "true", + ), + resource.TestCheckResourceAttrSet( + "sysdig_sso_saml.test_xml", + "metadata_xml", + ), + resource.TestCheckResourceAttr( + "sysdig_sso_saml.test_xml", + "email_parameter", + "email", + ), + resource.TestCheckResourceAttr( + "sysdig_sso_saml.test_xml", + "integration_name", + integrationName, + ), + ), + }, + { + ResourceName: "sysdig_sso_saml.test_xml", + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: func(s *terraform.State) (string, error) { + return "system/" + s.RootModule().Resources["sysdig_sso_saml.test_xml"].Primary.ID, nil + }, + }, + }, + }) +} + +func TestAccSSOSamlOnprem_Update(t *testing.T) { + integrationName := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: preCheckAnyEnv(t, SysdigMonitorApiTokenEnv, SysdigSecureApiTokenEnv), + ProviderFactories: map[string]func() (*schema.Provider, error){ + "sysdig": func() (*schema.Provider, error) { + return sysdig.Provider(), nil + }, + }, + Steps: []resource.TestStep{ + { + Config: ssoSamlOnpremWithMetadataURLConfig(integrationName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "sysdig_sso_saml.test", + "integration_name", + integrationName, + ), + resource.TestCheckResourceAttr( + "sysdig_sso_saml.test", + "is_group_mapping_enabled", + "false", + ), + ), + }, + { + Config: ssoSamlOnpremUpdatedConfig(integrationName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "sysdig_sso_saml.test", + "integration_name", + fmt.Sprintf("%s-updated", integrationName), + ), + resource.TestCheckResourceAttr( + "sysdig_sso_saml.test", + "is_group_mapping_enabled", + "true", + ), + resource.TestCheckResourceAttr( + "sysdig_sso_saml.test", + "group_mapping_attribute_name", + "custom_groups", + ), + ), + }, + }, + }) +} + +func TestAccSSOSamlOnprem_SecuritySettings(t *testing.T) { + integrationName := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: preCheckAnyEnv(t, SysdigMonitorApiTokenEnv, SysdigSecureApiTokenEnv), + ProviderFactories: map[string]func() (*schema.Provider, error){ + "sysdig": func() (*schema.Provider, error) { + return sysdig.Provider(), nil + }, + }, + Steps: []resource.TestStep{ + { + Config: ssoSamlOnpremWithSecuritySettingsConfig(integrationName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "sysdig_sso_saml.test_security", + "is_system", + "true", + ), + resource.TestCheckResourceAttr( + "sysdig_sso_saml.test_security", + "is_signature_validation_enabled", + "false", + ), + resource.TestCheckResourceAttr( + "sysdig_sso_saml.test_security", + "is_signed_assertion_enabled", + "false", + ), + resource.TestCheckResourceAttr( + "sysdig_sso_saml.test_security", + "is_destination_verification_enabled", + "false", + ), + resource.TestCheckResourceAttr( + "sysdig_sso_saml.test_security", + "is_encryption_support_enabled", + "true", + ), + ), + }, + }, + }) +} + +func ssoSamlOnpremWithMetadataURLConfig(integrationName string) string { + return fmt.Sprintf(` +resource "sysdig_sso_saml" "test" { + is_system = true + metadata_url = "https://idp.example.com/metadata" + email_parameter = "email" + integration_name = "%s" + is_active = true +} +`, integrationName) +} + +func ssoSamlOnpremWithMetadataXMLConfig(integrationName string) string { + return fmt.Sprintf(` +resource "sysdig_sso_saml" "test_xml" { + is_system = true + metadata_xml = <<-EOF + + + + + + +EOF + email_parameter = "email" + integration_name = "%s" + is_active = true +} +`, integrationName) +} + +func ssoSamlOnpremUpdatedConfig(integrationName string) string { + return fmt.Sprintf(` +resource "sysdig_sso_saml" "test" { + is_system = true + metadata_url = "https://idp.example.com/metadata" + email_parameter = "email" + integration_name = "%s-updated" + is_active = true + is_group_mapping_enabled = true + group_mapping_attribute_name = "custom_groups" +} +`, integrationName) +} + +func ssoSamlOnpremWithSecuritySettingsConfig(integrationName string) string { + return fmt.Sprintf(` +resource "sysdig_sso_saml" "test_security" { + is_system = true + metadata_url = "https://idp.example.com/metadata" + email_parameter = "email" + integration_name = "%s" + is_active = true + is_signature_validation_enabled = false + is_signed_assertion_enabled = false + is_destination_verification_enabled = false + is_encryption_support_enabled = true +} +`, integrationName) +} diff --git a/sysdig/resource_sysdig_sso_saml_test.go b/sysdig/resource_sysdig_sso_saml_test.go index 289bb6e2..ec66b93e 100644 --- a/sysdig/resource_sysdig_sso_saml_test.go +++ b/sysdig/resource_sysdig_sso_saml_test.go @@ -1,4 +1,4 @@ -//go:build tf_acc_sysdig_monitor || tf_acc_sysdig_secure || tf_acc_onprem_monitor || tf_acc_onprem_secure +//go:build tf_acc_sysdig_monitor || tf_acc_sysdig_secure package sysdig_test @@ -17,13 +17,7 @@ func TestAccSSOSaml_WithMetadataURL(t *testing.T) { integrationName := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { - monitor := os.Getenv("SYSDIG_MONITOR_API_TOKEN") - secure := os.Getenv("SYSDIG_SECURE_API_TOKEN") - if monitor == "" && secure == "" { - t.Fatal("SYSDIG_MONITOR_API_TOKEN or SYSDIG_SECURE_API_TOKEN must be set for acceptance tests") - } - }, + PreCheck: preCheckAnyEnv(t, SysdigMonitorApiTokenEnv, SysdigSecureApiTokenEnv), ProviderFactories: map[string]func() (*schema.Provider, error){ "sysdig": func() (*schema.Provider, error) { return sysdig.Provider(), nil @@ -77,13 +71,7 @@ func TestAccSSOSaml_WithMetadataXML(t *testing.T) { integrationName := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { - monitor := os.Getenv("SYSDIG_MONITOR_API_TOKEN") - secure := os.Getenv("SYSDIG_SECURE_API_TOKEN") - if monitor == "" && secure == "" { - t.Fatal("SYSDIG_MONITOR_API_TOKEN or SYSDIG_SECURE_API_TOKEN must be set for acceptance tests") - } - }, + PreCheck: preCheckAnyEnv(t, SysdigMonitorApiTokenEnv, SysdigSecureApiTokenEnv), ProviderFactories: map[string]func() (*schema.Provider, error){ "sysdig": func() (*schema.Provider, error) { return sysdig.Provider(), nil @@ -178,13 +166,7 @@ func TestAccSSOSaml_SecuritySettings(t *testing.T) { integrationName := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { - monitor := os.Getenv("SYSDIG_MONITOR_API_TOKEN") - secure := os.Getenv("SYSDIG_SECURE_API_TOKEN") - if monitor == "" && secure == "" { - t.Fatal("SYSDIG_MONITOR_API_TOKEN or SYSDIG_SECURE_API_TOKEN must be set for acceptance tests") - } - }, + PreCheck: preCheckAnyEnv(t, SysdigMonitorApiTokenEnv, SysdigSecureApiTokenEnv), ProviderFactories: map[string]func() (*schema.Provider, error){ "sysdig": func() (*schema.Provider, error) { return sysdig.Provider(), nil diff --git a/website/docs/r/sso_openid.md b/website/docs/r/sso_openid.md index f62c63c7..3c021a08 100644 --- a/website/docs/r/sso_openid.md +++ b/website/docs/r/sso_openid.md @@ -24,6 +24,7 @@ resource "sysdig_sso_openid" "google" { integration_name = "Google SSO" is_active = true + is_system = false create_user_on_login = true is_metadata_discovery_enabled = true } @@ -41,6 +42,7 @@ resource "sysdig_sso_openid" "custom_idp" { integration_name = "Custom IDP" is_active = true + is_system = false is_metadata_discovery_enabled = false metadata { @@ -65,6 +67,7 @@ resource "sysdig_sso_openid" "okta" { integration_name = "Okta SSO" is_active = true + is_system = false create_user_on_login = true is_group_mapping_enabled = true group_mapping_attribute_name = "groups" @@ -87,6 +90,8 @@ resource "sysdig_sso_openid" "okta" { ### Optional Arguments +* `is_system` - (Optional) Whether this is a system SSO configuration (Only applicable to on-prem installations). Default: `false`. + * `product` - (Optional) The Sysdig product to configure SSO for. Valid values are `monitor` or `secure`. Default is `secure`. * `is_active` - (Optional) Whether the SSO configuration is active. Default is `true`. @@ -141,4 +146,10 @@ Sysdig SSO OpenID configurations can be imported using the ID, e.g. $ terraform import sysdig_sso_openid.example 12345 ``` +For system-level SSO configurations (on-prem), prefix the ID with `system/`: + +``` +$ terraform import sysdig_sso_openid.example system/12345 +``` + ~> **Note:** The `client_secret` attribute cannot be imported and must be set in the configuration after import. diff --git a/website/docs/r/sso_saml.md b/website/docs/r/sso_saml.md index 016f9b2e..6cc5e776 100644 --- a/website/docs/r/sso_saml.md +++ b/website/docs/r/sso_saml.md @@ -22,6 +22,7 @@ resource "sysdig_sso_saml" "example" { email_parameter = "email" integration_name = "Corporate SAML SSO" is_active = true + is_system = false } ``` @@ -41,6 +42,7 @@ EOF email_parameter = "email" integration_name = "Corporate SAML SSO" is_active = true + is_system = false } ``` @@ -52,6 +54,7 @@ resource "sysdig_sso_saml" "example_groups" { email_parameter = "email" integration_name = "Corporate SAML SSO" is_active = true + is_system = false is_group_mapping_enabled = true group_mapping_attribute_name = "groups" } @@ -85,6 +88,7 @@ resource "sysdig_sso_saml" "example_security" { ### Optional +* `is_system` - (Optional) Whether this is a system SSO configuration (Only applicable to on-prem installations). Default: `false`. * `product` - (Optional) The Sysdig product to configure SSO for. Valid values are `monitor` or `secure`. Default: `secure`. * `is_active` - (Optional) Whether the SSO configuration is active. Default: `true`. * `create_user_on_login` - (Optional) Whether to automatically create a new user upon first login via SSO. Default: `false`. @@ -113,3 +117,9 @@ SAML SSO configurations can be imported using the SSO configuration ID: ``` $ terraform import sysdig_sso_saml.example 12345 ``` + +For system-level SSO configurations (on-prem), prefix the ID with `system/`: + +``` +$ terraform import sysdig_sso_saml.example system/12345 +```