diff --git a/github/provider.go b/github/provider.go index 2d5d6dc33a..f1c72082bf 100644 --- a/github/provider.go +++ b/github/provider.go @@ -180,6 +180,7 @@ func Provider() *schema.Provider { "github_organization_role_user": resourceGithubOrganizationRoleUser(), "github_organization_role_team_assignment": resourceGithubOrganizationRoleTeamAssignment(), "github_organization_ruleset": resourceGithubOrganizationRuleset(), + "github_organization_security_configuration": resourceGithubOrganizationSecurityConfiguration(), "github_organization_security_manager": resourceGithubOrganizationSecurityManager(), "github_organization_settings": resourceGithubOrganizationSettings(), "github_organization_webhook": resourceGithubOrganizationWebhook(), @@ -217,6 +218,7 @@ func Provider() *schema.Provider { "github_enterprise_actions_workflow_permissions": resourceGithubEnterpriseActionsWorkflowPermissions(), "github_actions_organization_workflow_permissions": resourceGithubActionsOrganizationWorkflowPermissions(), "github_enterprise_security_analysis_settings": resourceGithubEnterpriseSecurityAnalysisSettings(), + "github_enterprise_security_configuration": resourceGithubEnterpriseSecurityConfiguration(), "github_workflow_repository_permissions": resourceGithubWorkflowRepositoryPermissions(), }, diff --git a/github/resource_github_enterprise_security_configuration.go b/github/resource_github_enterprise_security_configuration.go new file mode 100644 index 0000000000..4f2d1d6019 --- /dev/null +++ b/github/resource_github_enterprise_security_configuration.go @@ -0,0 +1,605 @@ +package github + +import ( + "context" + "errors" + "fmt" + "net/http" + "strconv" + + "github.com/google/go-github/v82/github" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +func resourceGithubEnterpriseSecurityConfiguration() *schema.Resource { + return &schema.Resource{ + Description: "Manages a code security configuration for a GitHub Enterprise.", + CreateContext: resourceGithubEnterpriseSecurityConfigurationCreate, + ReadContext: resourceGithubEnterpriseSecurityConfigurationRead, + UpdateContext: resourceGithubEnterpriseSecurityConfigurationUpdate, + DeleteContext: resourceGithubEnterpriseSecurityConfigurationDelete, + Importer: &schema.ResourceImporter{ + StateContext: resourceGithubEnterpriseSecurityConfigurationImport, + }, + + Schema: map[string]*schema.Schema{ + "enterprise_slug": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "The slug of the enterprise.", + }, + "name": { + Type: schema.TypeString, + Required: true, + Description: "The name of the code security configuration.", + }, + "description": { + Type: schema.TypeString, + Required: true, + Description: "A description of the code security configuration.", + }, + "advanced_security": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "The advanced security configuration for the code security configuration. Can be one of 'enabled', 'disabled'.", + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{ + "enabled", "disabled", + }, false)), + }, + "dependency_graph": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "The dependency graph configuration for the code security configuration. Can be one of 'enabled', 'disabled', 'not_set'.", + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{ + "enabled", "disabled", "not_set", + }, false)), + }, + "dependency_graph_autosubmit_action": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "The dependency graph autosubmit action configuration for the code security configuration. Can be one of 'enabled', 'disabled', 'not_set'.", + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{ + "enabled", "disabled", "not_set", + }, false)), + }, + "dependency_graph_autosubmit_action_options": { + Type: schema.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Description: "The dependency graph autosubmit action options for the code security configuration.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "labeled_runners": { + Type: schema.TypeBool, + Optional: true, + Description: "Whether to use labeled runners for the dependency graph autosubmit action.", + }, + }, + }, + }, + "dependabot_alerts": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "The dependabot alerts configuration for the code security configuration. Can be one of 'enabled', 'disabled', 'not_set'.", + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{ + "enabled", "disabled", "not_set", + }, false)), + }, + "dependabot_security_updates": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "The dependabot security updates configuration for the code security configuration. Can be one of 'enabled', 'disabled', 'not_set'.", + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{ + "enabled", "disabled", "not_set", + }, false)), + }, + "code_scanning_default_setup": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "The code scanning default setup configuration for the code security configuration. Can be one of 'enabled', 'disabled', 'not_set'.", + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{ + "enabled", "disabled", "not_set", + }, false)), + }, + "code_scanning_default_setup_options": { + Type: schema.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Description: "The code scanning default setup options for the code security configuration.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "runner_type": { + Type: schema.TypeString, + Optional: true, + Description: "The type of runner to use for code scanning default setup. Can be one of 'standard', 'labeled'.", + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"standard", "labeled"}, false)), + }, + "runner_label": { + Type: schema.TypeString, + Optional: true, + Description: "The label of the runner to use for code scanning default setup.", + }, + }, + }, + }, + "code_scanning_options": { + Type: schema.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Description: "The code scanning options for the code security configuration.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "allow_advanced": { + Type: schema.TypeBool, + Optional: true, + Description: "Whether to allow advanced security for code scanning.", + }, + }, + }, + }, + "code_security": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "The code security configuration for the code security configuration. Can be one of 'enabled', 'disabled', 'not_set'.", + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{ + "enabled", "disabled", "not_set", + }, false)), + }, + "secret_scanning": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "The secret scanning configuration for the code security configuration. Can be one of 'enabled', 'disabled', 'not_set'.", + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{ + "enabled", "disabled", "not_set", + }, false)), + }, + "secret_scanning_push_protection": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "The secret scanning push protection configuration for the code security configuration. Can be one of 'enabled', 'disabled', 'not_set'.", + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{ + "enabled", "disabled", "not_set", + }, false)), + }, + "secret_scanning_validity_checks": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "The secret scanning validity checks configuration for the code security configuration. Can be one of 'enabled', 'disabled', 'not_set'.", + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{ + "enabled", "disabled", "not_set", + }, false)), + }, + "secret_scanning_non_provider_patterns": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "The secret scanning non provider patterns configuration for the code security configuration. Can be one of 'enabled', 'disabled', 'not_set'.", + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{ + "enabled", "disabled", "not_set", + }, false)), + }, + "secret_scanning_generic_secrets": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "The secret scanning generic secrets configuration for the code security configuration. Can be one of 'enabled', 'disabled', 'not_set'.", + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{ + "enabled", "disabled", "not_set", + }, false)), + }, + "secret_protection": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "The secret protection configuration for the code security configuration. Can be one of 'enabled', 'disabled', 'not_set'.", + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{ + "enabled", "disabled", "not_set", + }, false)), + }, + "private_vulnerability_reporting": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "The private vulnerability reporting configuration for the code security configuration. Can be one of 'enabled', 'disabled', 'not_set'.", + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{ + "enabled", "disabled", "not_set", + }, false)), + }, + "enforcement": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "The enforcement configuration for the code security configuration. Can be one of 'enforced', 'unenforced'.", + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{ + "enforced", "unenforced", + }, false)), + }, + "target_type": { + Type: schema.TypeString, + Computed: true, + Description: "The target type of the code security configuration.", + }, + }, + } +} + +func resourceGithubEnterpriseSecurityConfigurationCreate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + client := meta.(*Owner).v3client + enterprise := d.Get("enterprise_slug").(string) + name := d.Get("name").(string) + + tflog.Debug(ctx, fmt.Sprintf("Creating enterprise code security configuration: %s/%s", enterprise, name), map[string]any{ + "enterprise": enterprise, + "name": name, + }) + + config := expandEnterpriseCodeSecurityConfiguration(d) + + configuration, _, err := client.Enterprise.CreateCodeSecurityConfiguration(ctx, enterprise, config) + if err != nil { + tflog.Error(ctx, fmt.Sprintf("Failed to create enterprise code security configuration: %s/%s", enterprise, name), map[string]any{ + "enterprise": enterprise, + "name": name, + "error": err.Error(), + }) + return diag.FromErr(err) + } + + id, err := buildID(enterprise, strconv.FormatInt(configuration.GetID(), 10)) + if err != nil { + return diag.FromErr(err) + } + d.SetId(id) + + tflog.Info(ctx, fmt.Sprintf("Created enterprise code security configuration: %s/%s (ID: %d)", enterprise, name, configuration.GetID()), map[string]any{ + "enterprise": enterprise, + "name": name, + "id": configuration.GetID(), + }) + + return resourceGithubEnterpriseSecurityConfigurationRead(ctx, d, meta) +} + +func resourceGithubEnterpriseSecurityConfigurationRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + client := meta.(*Owner).v3client + + enterprise, idStr, err := parseID2(d.Id()) + if err != nil { + return diag.FromErr(err) + } + + id, err := strconv.ParseInt(idStr, 10, 64) + if err != nil { + return diag.FromErr(err) + } + + tflog.Trace(ctx, fmt.Sprintf("Reading enterprise code security configuration: %s/%d", enterprise, id), map[string]any{ + "enterprise": enterprise, + "id": id, + }) + + configuration, _, err := client.Enterprise.GetCodeSecurityConfiguration(ctx, enterprise, id) + if err != nil { + var ghErr *github.ErrorResponse + if errors.As(err, &ghErr) { + if ghErr.Response.StatusCode == http.StatusNotFound { + tflog.Info(ctx, fmt.Sprintf("Removing enterprise code security configuration %s/%d from state because it no longer exists in GitHub", enterprise, id), map[string]any{ + "enterprise": enterprise, + "id": id, + }) + d.SetId("") + return nil + } + } + tflog.Error(ctx, fmt.Sprintf("Failed to read enterprise code security configuration: %s/%d", enterprise, id), map[string]any{ + "enterprise": enterprise, + "id": id, + "error": err.Error(), + }) + return diag.FromErr(err) + } + + if err = d.Set("enterprise_slug", enterprise); err != nil { + return diag.FromErr(err) + } + + if err = d.Set("name", configuration.Name); err != nil { + return diag.FromErr(err) + } + if err = d.Set("description", configuration.Description); err != nil { + return diag.FromErr(err) + } + if err = d.Set("advanced_security", configuration.GetAdvancedSecurity()); err != nil { + return diag.FromErr(err) + } + if err = d.Set("dependency_graph", configuration.GetDependencyGraph()); err != nil { + return diag.FromErr(err) + } + if err = d.Set("dependency_graph_autosubmit_action", configuration.GetDependencyGraphAutosubmitAction()); err != nil { + return diag.FromErr(err) + } + if err := d.Set("dependency_graph_autosubmit_action_options", flattenDependencyGraphAutosubmitActionOptions(configuration.DependencyGraphAutosubmitActionOptions)); err != nil { + return diag.FromErr(err) + } + if err = d.Set("dependabot_alerts", configuration.GetDependabotAlerts()); err != nil { + return diag.FromErr(err) + } + if err = d.Set("dependabot_security_updates", configuration.GetDependabotSecurityUpdates()); err != nil { + return diag.FromErr(err) + } + if err = d.Set("code_scanning_default_setup", configuration.GetCodeScanningDefaultSetup()); err != nil { + return diag.FromErr(err) + } + if err := d.Set("code_scanning_default_setup_options", flattenCodeScanningDefaultSetupOptions(configuration.CodeScanningDefaultSetupOptions)); err != nil { + return diag.FromErr(err) + } + if err := d.Set("code_scanning_options", flattenCodeScanningOptions(configuration.CodeScanningOptions)); err != nil { + return diag.FromErr(err) + } + codeSec := configuration.GetCodeSecurity() + if codeSec == "" { + codeSec = "disabled" + } + if err = d.Set("code_security", codeSec); err != nil { + return diag.FromErr(err) + } + if err = d.Set("secret_scanning", configuration.GetSecretScanning()); err != nil { + return diag.FromErr(err) + } + if err = d.Set("secret_scanning_push_protection", configuration.GetSecretScanningPushProtection()); err != nil { + return diag.FromErr(err) + } + if err = d.Set("secret_scanning_validity_checks", configuration.GetSecretScanningValidityChecks()); err != nil { + return diag.FromErr(err) + } + if err = d.Set("secret_scanning_non_provider_patterns", configuration.GetSecretScanningNonProviderPatterns()); err != nil { + return diag.FromErr(err) + } + if err = d.Set("secret_scanning_generic_secrets", configuration.GetSecretScanningGenericSecrets()); err != nil { + return diag.FromErr(err) + } + secretProt := configuration.GetSecretProtection() + if secretProt == "" { + secretProt = "disabled" + } + if err = d.Set("secret_protection", secretProt); err != nil { + return diag.FromErr(err) + } + if err = d.Set("private_vulnerability_reporting", configuration.GetPrivateVulnerabilityReporting()); err != nil { + return diag.FromErr(err) + } + if err = d.Set("enforcement", configuration.GetEnforcement()); err != nil { + return diag.FromErr(err) + } + if err = d.Set("target_type", configuration.GetTargetType()); err != nil { + return diag.FromErr(err) + } + + tflog.Trace(ctx, fmt.Sprintf("Successfully read enterprise code security configuration: %s/%d", enterprise, id), map[string]any{ + "enterprise": enterprise, + "id": id, + }) + + return nil +} + +func resourceGithubEnterpriseSecurityConfigurationUpdate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + client := meta.(*Owner).v3client + + enterprise, idStr, err := parseID2(d.Id()) + if err != nil { + return diag.FromErr(err) + } + + id, err := strconv.ParseInt(idStr, 10, 64) + if err != nil { + return diag.FromErr(err) + } + + tflog.Debug(ctx, fmt.Sprintf("Updating enterprise code security configuration: %s/%d", enterprise, id), map[string]any{ + "enterprise": enterprise, + "id": id, + }) + + config := expandEnterpriseCodeSecurityConfiguration(d) + + _, _, err = client.Enterprise.UpdateCodeSecurityConfiguration(ctx, enterprise, id, config) + if err != nil { + tflog.Error(ctx, fmt.Sprintf("Failed to update enterprise code security configuration: %s/%d", enterprise, id), map[string]any{ + "enterprise": enterprise, + "id": id, + "error": err.Error(), + }) + return diag.FromErr(err) + } + + tflog.Info(ctx, fmt.Sprintf("Updated enterprise code security configuration: %s/%d", enterprise, id), map[string]any{ + "enterprise": enterprise, + "id": id, + }) + + return resourceGithubEnterpriseSecurityConfigurationRead(ctx, d, meta) +} + +func resourceGithubEnterpriseSecurityConfigurationDelete(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + client := meta.(*Owner).v3client + + enterprise, idStr, err := parseID2(d.Id()) + if err != nil { + return diag.FromErr(err) + } + + id, err := strconv.ParseInt(idStr, 10, 64) + if err != nil { + return diag.FromErr(err) + } + + tflog.Debug(ctx, fmt.Sprintf("Deleting enterprise code security configuration: %s/%d", enterprise, id), map[string]any{ + "enterprise": enterprise, + "id": id, + }) + + _, err = client.Enterprise.DeleteCodeSecurityConfiguration(ctx, enterprise, id) + if err != nil { + var ghErr *github.ErrorResponse + if errors.As(err, &ghErr) && ghErr.Response.StatusCode == http.StatusNotFound { + tflog.Info(ctx, fmt.Sprintf("Enterprise code security configuration %s/%d already deleted", enterprise, id), map[string]any{ + "enterprise": enterprise, + "id": id, + }) + return nil + } + tflog.Error(ctx, fmt.Sprintf("Failed to delete enterprise code security configuration: %s/%d", enterprise, id), map[string]any{ + "enterprise": enterprise, + "id": id, + "error": err.Error(), + }) + return diag.FromErr(err) + } + + tflog.Info(ctx, fmt.Sprintf("Deleted enterprise code security configuration: %s/%d", enterprise, id), map[string]any{ + "enterprise": enterprise, + "id": id, + }) + + return nil +} + +func resourceGithubEnterpriseSecurityConfigurationImport(ctx context.Context, d *schema.ResourceData, meta any) ([]*schema.ResourceData, error) { + enterpriseSlug, configID, err := parseID2(d.Id()) + if err != nil { + return nil, fmt.Errorf("invalid import specified: supplied import must be written as :. Parse error: %w", err) + } + + id, err := buildID(enterpriseSlug, configID) + if err != nil { + return nil, err + } + d.SetId(id) + + if err = d.Set("enterprise_slug", enterpriseSlug); err != nil { + return nil, err + } + + return []*schema.ResourceData{d}, nil +} + +func expandEnterpriseCodeSecurityConfiguration(d *schema.ResourceData) github.CodeSecurityConfiguration { + config := github.CodeSecurityConfiguration{ + Name: d.Get("name").(string), + Description: d.Get("description").(string), + } + + // Only set optional fields if they are explicitly configured + if val, ok := d.GetOk("advanced_security"); ok { + config.AdvancedSecurity = github.Ptr(val.(string)) + } + + if val, ok := d.GetOk("dependency_graph"); ok { + config.DependencyGraph = github.Ptr(val.(string)) + } + + if val, ok := d.GetOk("dependency_graph_autosubmit_action"); ok { + config.DependencyGraphAutosubmitAction = github.Ptr(val.(string)) + } + + if val, ok := d.GetOk("dependabot_alerts"); ok { + config.DependabotAlerts = github.Ptr(val.(string)) + } + + if val, ok := d.GetOk("dependabot_security_updates"); ok { + config.DependabotSecurityUpdates = github.Ptr(val.(string)) + } + + if val, ok := d.GetOk("code_scanning_default_setup"); ok { + config.CodeScanningDefaultSetup = github.Ptr(val.(string)) + } + + if val, ok := d.GetOk("code_security"); ok { + config.CodeSecurity = github.Ptr(val.(string)) + } + + if val, ok := d.GetOk("secret_scanning"); ok { + config.SecretScanning = github.Ptr(val.(string)) + } + + if val, ok := d.GetOk("secret_scanning_push_protection"); ok { + config.SecretScanningPushProtection = github.Ptr(val.(string)) + } + + if val, ok := d.GetOk("secret_scanning_validity_checks"); ok { + config.SecretScanningValidityChecks = github.Ptr(val.(string)) + } + + if val, ok := d.GetOk("secret_scanning_non_provider_patterns"); ok { + config.SecretScanningNonProviderPatterns = github.Ptr(val.(string)) + } + + if val, ok := d.GetOk("secret_scanning_generic_secrets"); ok { + config.SecretScanningGenericSecrets = github.Ptr(val.(string)) + } + + if val, ok := d.GetOk("secret_protection"); ok { + config.SecretProtection = github.Ptr(val.(string)) + } + + if val, ok := d.GetOk("private_vulnerability_reporting"); ok { + config.PrivateVulnerabilityReporting = github.Ptr(val.(string)) + } + + if val, ok := d.GetOk("enforcement"); ok { + config.Enforcement = github.Ptr(val.(string)) + } + + if val, ok := d.GetOk("dependency_graph_autosubmit_action_options"); ok { + optionsList := val.([]any) + if len(optionsList) > 0 { + autosubmitOpts := optionsList[0].(map[string]any) + config.DependencyGraphAutosubmitActionOptions = &github.DependencyGraphAutosubmitActionOptions{ + LabeledRunners: github.Ptr(autosubmitOpts["labeled_runners"].(bool)), + } + } + } + + if val, ok := d.GetOk("code_scanning_default_setup_options"); ok { + optionsList := val.([]any) + if len(optionsList) > 0 { + setupOpts := optionsList[0].(map[string]any) + config.CodeScanningDefaultSetupOptions = &github.CodeScanningDefaultSetupOptions{ + RunnerType: setupOpts["runner_type"].(string), + } + if runnerLabel, ok := setupOpts["runner_label"].(string); ok && runnerLabel != "" { + config.CodeScanningDefaultSetupOptions.RunnerLabel = github.Ptr(runnerLabel) + } + } + } + + if val, ok := d.GetOk("code_scanning_options"); ok { + optionsList := val.([]any) + if len(optionsList) > 0 { + scanOpts := optionsList[0].(map[string]any) + config.CodeScanningOptions = &github.CodeScanningOptions{ + AllowAdvanced: github.Ptr(scanOpts["allow_advanced"].(bool)), + } + } + } + + return config +} diff --git a/github/resource_github_enterprise_security_configuration_test.go b/github/resource_github_enterprise_security_configuration_test.go new file mode 100644 index 0000000000..d414d82438 --- /dev/null +++ b/github/resource_github_enterprise_security_configuration_test.go @@ -0,0 +1,178 @@ +package github + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +func TestAccGithubEnterpriseSecurityConfiguration(t *testing.T) { + t.Run("creates enterprise security configuration without error", func(t *testing.T) { + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) + configName := fmt.Sprintf("test-config-%s", randomID) + + config := fmt.Sprintf(` + resource "github_enterprise_security_configuration" "test" { + enterprise_slug = "%s" + name = "%s" + description = "Test configuration" + advanced_security = "enabled" + dependency_graph = "enabled" + dependabot_alerts = "enabled" + dependabot_security_updates = "enabled" + code_scanning_default_setup = "enabled" + secret_scanning = "enabled" + secret_scanning_push_protection = "enabled" + private_vulnerability_reporting = "enabled" + enforcement = "enforced" + }`, testAccConf.enterpriseSlug, configName) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnlessEnterprise(t) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{ + { + Config: config, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_enterprise_security_configuration.test", + tfjsonpath.New("name"), knownvalue.StringExact(configName)), + statecheck.ExpectKnownValue("github_enterprise_security_configuration.test", + tfjsonpath.New("description"), knownvalue.StringExact("Test configuration")), + statecheck.ExpectKnownValue("github_enterprise_security_configuration.test", + tfjsonpath.New("advanced_security"), knownvalue.StringExact("enabled")), + statecheck.ExpectKnownValue("github_enterprise_security_configuration.test", + tfjsonpath.New("enforcement"), knownvalue.StringExact("enforced")), + }, + }, + { + ResourceName: "github_enterprise_security_configuration.test", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) + }) + + t.Run("updates enterprise security configuration without error", func(t *testing.T) { + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) + configName := fmt.Sprintf("test-config-%s", randomID) + configNameUpdated := fmt.Sprintf("test-config-updated-%s", randomID) + + tmpl := ` + resource "github_enterprise_security_configuration" "test" { + enterprise_slug = "%s" + name = "%s" + description = "%s" + advanced_security = "%s" + }` + configBefore := fmt.Sprintf(tmpl, testAccConf.enterpriseSlug, configName, "Test configuration", "disabled") + configAfter := fmt.Sprintf(tmpl, testAccConf.enterpriseSlug, configNameUpdated, "Test configuration updated", "enabled") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnlessEnterprise(t) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{ + { + Config: configBefore, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_enterprise_security_configuration.test", + tfjsonpath.New("name"), knownvalue.StringExact(configName)), + statecheck.ExpectKnownValue("github_enterprise_security_configuration.test", + tfjsonpath.New("advanced_security"), knownvalue.StringExact("disabled")), + }, + }, + { + Config: configAfter, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_enterprise_security_configuration.test", + tfjsonpath.New("name"), knownvalue.StringExact(configNameUpdated)), + statecheck.ExpectKnownValue("github_enterprise_security_configuration.test", + tfjsonpath.New("advanced_security"), knownvalue.StringExact("enabled")), + }, + }, + }, + }) + }) + + t.Run("creates enterprise security configuration with options", func(t *testing.T) { + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) + configName := fmt.Sprintf("test-config-options-%s", randomID) + + config := fmt.Sprintf(` + resource "github_enterprise_security_configuration" "test" { + enterprise_slug = "%s" + name = "%s" + description = "Test configuration with options" + advanced_security = "enabled" + dependency_graph = "enabled" + dependency_graph_autosubmit_action = "enabled" + dependency_graph_autosubmit_action_options { + labeled_runners = true + } + code_scanning_default_setup = "enabled" + code_scanning_default_setup_options { + runner_type = "labeled" + runner_label = "code-scanning" + } + secret_scanning = "enabled" + }`, testAccConf.enterpriseSlug, configName) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnlessEnterprise(t) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{ + { + Config: config, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_enterprise_security_configuration.test", + tfjsonpath.New("name"), knownvalue.StringExact(configName)), + statecheck.ExpectKnownValue("github_enterprise_security_configuration.test", + tfjsonpath.New("dependency_graph_autosubmit_action_options").AtSliceIndex(0).AtMapKey("labeled_runners"), knownvalue.Bool(true)), + statecheck.ExpectKnownValue("github_enterprise_security_configuration.test", + tfjsonpath.New("code_scanning_default_setup_options").AtSliceIndex(0).AtMapKey("runner_type"), knownvalue.StringExact("labeled")), + statecheck.ExpectKnownValue("github_enterprise_security_configuration.test", + tfjsonpath.New("code_scanning_default_setup_options").AtSliceIndex(0).AtMapKey("runner_label"), knownvalue.StringExact("code-scanning")), + }, + }, + { + ResourceName: "github_enterprise_security_configuration.test", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) + }) + + t.Run("creates enterprise security configuration with minimal config", func(t *testing.T) { + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) + configName := fmt.Sprintf("test-config-minimal-%s", randomID) + + config := fmt.Sprintf(` + resource "github_enterprise_security_configuration" "test" { + enterprise_slug = "%s" + name = "%s" + description = "Minimal test configuration" + }`, testAccConf.enterpriseSlug, configName) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnlessEnterprise(t) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{ + { + Config: config, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_enterprise_security_configuration.test", + tfjsonpath.New("name"), knownvalue.StringExact(configName)), + statecheck.ExpectKnownValue("github_enterprise_security_configuration.test", + tfjsonpath.New("target_type"), knownvalue.NotNull()), + }, + }, + }, + }) + }) +} diff --git a/github/resource_github_organization_security_configuration.go b/github/resource_github_organization_security_configuration.go new file mode 100644 index 0000000000..573e19b3b8 --- /dev/null +++ b/github/resource_github_organization_security_configuration.go @@ -0,0 +1,694 @@ +package github + +import ( + "context" + "errors" + "fmt" + "net/http" + "strconv" + + "github.com/google/go-github/v82/github" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +func resourceGithubOrganizationSecurityConfiguration() *schema.Resource { + return &schema.Resource{ + Description: "Manages a code security configuration for a GitHub Organization.", + CreateContext: resourceGithubOrganizationSecurityConfigurationCreate, + ReadContext: resourceGithubOrganizationSecurityConfigurationRead, + UpdateContext: resourceGithubOrganizationSecurityConfigurationUpdate, + DeleteContext: resourceGithubOrganizationSecurityConfigurationDelete, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + Description: "The name of the code security configuration.", + }, + "description": { + Type: schema.TypeString, + Required: true, + Description: "A description of the code security configuration.", + }, + "advanced_security": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "The advanced security configuration for the code security configuration. Can be one of 'enabled', 'disabled'.", + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{ + "enabled", "disabled", + }, false)), + }, + "dependency_graph": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "The dependency graph configuration for the code security configuration. Can be one of 'enabled', 'disabled', 'not_set'.", + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{ + "enabled", "disabled", "not_set", + }, false)), + }, + "dependency_graph_autosubmit_action": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "The dependency graph autosubmit action configuration for the code security configuration. Can be one of 'enabled', 'disabled', 'not_set'.", + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{ + "enabled", "disabled", "not_set", + }, false)), + }, + "dependency_graph_autosubmit_action_options": { + Type: schema.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Description: "The dependency graph autosubmit action options for the code security configuration.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "labeled_runners": { + Type: schema.TypeBool, + Optional: true, + Description: "Whether to use labeled runners for the dependency graph autosubmit action.", + }, + }, + }, + }, + "dependabot_alerts": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "The dependabot alerts configuration for the code security configuration. Can be one of 'enabled', 'disabled', 'not_set'.", + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{ + "enabled", "disabled", "not_set", + }, false)), + }, + "dependabot_security_updates": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "The dependabot security updates configuration for the code security configuration. Can be one of 'enabled', 'disabled', 'not_set'.", + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{ + "enabled", "disabled", "not_set", + }, false)), + }, + "code_scanning_default_setup": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "The code scanning default setup configuration for the code security configuration. Can be one of 'enabled', 'disabled', 'not_set'.", + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{ + "enabled", "disabled", "not_set", + }, false)), + }, + "code_scanning_default_setup_options": { + Type: schema.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Description: "The code scanning default setup options for the code security configuration.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "runner_type": { + Type: schema.TypeString, + Optional: true, + Description: "The type of runner to use for code scanning default setup. Can be one of 'standard', 'labeled'.", + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"standard", "labeled"}, false)), + }, + "runner_label": { + Type: schema.TypeString, + Optional: true, + Description: "The label of the runner to use for code scanning default setup.", + }, + }, + }, + }, + "code_scanning_delegated_alert_dismissal": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "The code scanning delegated alert dismissal configuration for the code security configuration. Can be one of 'enabled', 'disabled', 'not_set'.", + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{ + "enabled", "disabled", "not_set", + }, false)), + }, + "code_scanning_options": { + Type: schema.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Description: "The code scanning options for the code security configuration.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "allow_advanced": { + Type: schema.TypeBool, + Optional: true, + Description: "Whether to allow advanced security for code scanning.", + }, + }, + }, + }, + "code_security": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "The code security configuration for the code security configuration. Can be one of 'enabled', 'disabled', 'not_set'.", + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{ + "enabled", "disabled", "not_set", + }, false)), + }, + "secret_scanning": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "The secret scanning configuration for the code security configuration. Can be one of 'enabled', 'disabled', 'not_set'.", + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{ + "enabled", "disabled", "not_set", + }, false)), + }, + "secret_scanning_push_protection": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "The secret scanning push protection configuration for the code security configuration. Can be one of 'enabled', 'disabled', 'not_set'.", + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{ + "enabled", "disabled", "not_set", + }, false)), + }, + "secret_scanning_delegated_bypass": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "The secret scanning delegated bypass configuration for the code security configuration. Can be one of 'enabled', 'disabled', 'not_set'.", + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{ + "enabled", "disabled", "not_set", + }, false)), + }, + "secret_scanning_delegated_bypass_options": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Description: "The secret scanning delegated bypass options for the code security configuration.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "reviewers": { + Type: schema.TypeList, + Optional: true, + Description: "The bypass reviewers for the secret scanning delegated bypass.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "reviewer_id": { + Type: schema.TypeInt, + Required: true, + Description: "The ID of the bypass reviewer.", + }, + "reviewer_type": { + Type: schema.TypeString, + Required: true, + Description: "The type of the bypass reviewer. Can be one of 'Team', 'Role'.", + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"Team", "Role"}, false)), + }, + }, + }, + }, + }, + }, + }, + "secret_scanning_validity_checks": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "The secret scanning validity checks configuration for the code security configuration. Can be one of 'enabled', 'disabled', 'not_set'.", + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{ + "enabled", "disabled", "not_set", + }, false)), + }, + "secret_scanning_non_provider_patterns": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "The secret scanning non provider patterns configuration for the code security configuration. Can be one of 'enabled', 'disabled', 'not_set'.", + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{ + "enabled", "disabled", "not_set", + }, false)), + }, + "secret_scanning_generic_secrets": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "The secret scanning generic secrets configuration for the code security configuration. Can be one of 'enabled', 'disabled', 'not_set'.", + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{ + "enabled", "disabled", "not_set", + }, false)), + }, + "secret_scanning_delegated_alert_dismissal": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "The secret scanning delegated alert dismissal configuration for the code security configuration. Can be one of 'enabled', 'disabled', 'not_set'.", + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{ + "enabled", "disabled", "not_set", + }, false)), + }, + "secret_protection": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "The secret protection configuration for the code security configuration. Can be one of 'enabled', 'disabled', 'not_set'.", + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{ + "enabled", "disabled", "not_set", + }, false)), + }, + "private_vulnerability_reporting": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "The private vulnerability reporting configuration for the code security configuration. Can be one of 'enabled', 'disabled', 'not_set'.", + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{ + "enabled", "disabled", "not_set", + }, false)), + }, + "enforcement": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "The enforcement configuration for the code security configuration. Can be one of 'enforced', 'unenforced'.", + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{ + "enforced", "unenforced", + }, false)), + }, + "target_type": { + Type: schema.TypeString, + Computed: true, + Description: "The target type of the code security configuration.", + }, + }, + } +} + +func resourceGithubOrganizationSecurityConfigurationCreate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + err := checkOrganization(meta) + if err != nil { + return diag.FromErr(err) + } + client := meta.(*Owner).v3client + org := meta.(*Owner).name + name := d.Get("name").(string) + + tflog.Debug(ctx, fmt.Sprintf("Creating organization code security configuration: %s/%s", org, name), map[string]any{ + "organization": org, + "name": name, + }) + + config := expandCodeSecurityConfiguration(d) + + configuration, _, err := client.Organizations.CreateCodeSecurityConfiguration(ctx, org, config) + if err != nil { + tflog.Error(ctx, fmt.Sprintf("Failed to create organization code security configuration: %s/%s", org, name), map[string]any{ + "organization": org, + "name": name, + "error": err.Error(), + }) + return diag.FromErr(err) + } + + id, err := buildID(org, strconv.FormatInt(configuration.GetID(), 10)) + if err != nil { + return diag.FromErr(err) + } + d.SetId(id) + + tflog.Info(ctx, fmt.Sprintf("Created organization code security configuration: %s/%s (ID: %d)", org, name, configuration.GetID()), map[string]any{ + "organization": org, + "name": name, + "id": configuration.GetID(), + }) + + return resourceGithubOrganizationSecurityConfigurationRead(ctx, d, meta) +} + +func resourceGithubOrganizationSecurityConfigurationRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + err := checkOrganization(meta) + if err != nil { + return diag.FromErr(err) + } + client := meta.(*Owner).v3client + + org, idStr, err := parseID2(d.Id()) + if err != nil { + return diag.FromErr(err) + } + + id, err := strconv.ParseInt(idStr, 10, 64) + if err != nil { + return diag.FromErr(err) + } + + tflog.Trace(ctx, fmt.Sprintf("Reading organization code security configuration: %s/%d", org, id), map[string]any{ + "organization": org, + "id": id, + }) + + configuration, _, err := client.Organizations.GetCodeSecurityConfiguration(ctx, org, id) + if err != nil { + var ghErr *github.ErrorResponse + if errors.As(err, &ghErr) { + if ghErr.Response.StatusCode == http.StatusNotFound { + tflog.Info(ctx, fmt.Sprintf("Removing organization code security configuration %s/%d from state because it no longer exists in GitHub", org, id), map[string]any{ + "organization": org, + "id": id, + }) + d.SetId("") + return nil + } + } + tflog.Error(ctx, fmt.Sprintf("Failed to read organization code security configuration: %s/%d", org, id), map[string]any{ + "organization": org, + "id": id, + "error": err.Error(), + }) + return diag.FromErr(err) + } + + if err = d.Set("name", configuration.Name); err != nil { + return diag.FromErr(err) + } + if err = d.Set("description", configuration.Description); err != nil { + return diag.FromErr(err) + } + if err = d.Set("advanced_security", configuration.GetAdvancedSecurity()); err != nil { + return diag.FromErr(err) + } + if err = d.Set("dependency_graph", configuration.GetDependencyGraph()); err != nil { + return diag.FromErr(err) + } + if err = d.Set("dependency_graph_autosubmit_action", configuration.GetDependencyGraphAutosubmitAction()); err != nil { + return diag.FromErr(err) + } + if err := d.Set("dependency_graph_autosubmit_action_options", flattenDependencyGraphAutosubmitActionOptions(configuration.DependencyGraphAutosubmitActionOptions)); err != nil { + return diag.FromErr(err) + } + if err = d.Set("dependabot_alerts", configuration.GetDependabotAlerts()); err != nil { + return diag.FromErr(err) + } + if err = d.Set("dependabot_security_updates", configuration.GetDependabotSecurityUpdates()); err != nil { + return diag.FromErr(err) + } + if err = d.Set("code_scanning_default_setup", configuration.GetCodeScanningDefaultSetup()); err != nil { + return diag.FromErr(err) + } + if err := d.Set("code_scanning_default_setup_options", flattenCodeScanningDefaultSetupOptions(configuration.CodeScanningDefaultSetupOptions)); err != nil { + return diag.FromErr(err) + } + if err = d.Set("code_scanning_delegated_alert_dismissal", configuration.GetCodeScanningDelegatedAlertDismissal()); err != nil { + return diag.FromErr(err) + } + if err := d.Set("code_scanning_options", flattenCodeScanningOptions(configuration.CodeScanningOptions)); err != nil { + return diag.FromErr(err) + } + codeSec := configuration.GetCodeSecurity() + if codeSec == "" { + codeSec = "disabled" + } + if err = d.Set("code_security", codeSec); err != nil { + return diag.FromErr(err) + } + if err = d.Set("secret_scanning", configuration.GetSecretScanning()); err != nil { + return diag.FromErr(err) + } + if err = d.Set("secret_scanning_push_protection", configuration.GetSecretScanningPushProtection()); err != nil { + return diag.FromErr(err) + } + if err = d.Set("secret_scanning_delegated_bypass", configuration.GetSecretScanningDelegatedBypass()); err != nil { + return diag.FromErr(err) + } + if err := d.Set("secret_scanning_delegated_bypass_options", flattenSecretScanningDelegatedBypassOptions(configuration.SecretScanningDelegatedBypassOptions)); err != nil { + return diag.FromErr(err) + } + if err = d.Set("secret_scanning_validity_checks", configuration.GetSecretScanningValidityChecks()); err != nil { + return diag.FromErr(err) + } + if err = d.Set("secret_scanning_non_provider_patterns", configuration.GetSecretScanningNonProviderPatterns()); err != nil { + return diag.FromErr(err) + } + if err = d.Set("secret_scanning_generic_secrets", configuration.GetSecretScanningGenericSecrets()); err != nil { + return diag.FromErr(err) + } + if err = d.Set("secret_scanning_delegated_alert_dismissal", configuration.GetSecretScanningDelegatedAlertDismissal()); err != nil { + return diag.FromErr(err) + } + secretProt := configuration.GetSecretProtection() + if secretProt == "" { + secretProt = "disabled" + } + if err = d.Set("secret_protection", secretProt); err != nil { + return diag.FromErr(err) + } + if err = d.Set("private_vulnerability_reporting", configuration.GetPrivateVulnerabilityReporting()); err != nil { + return diag.FromErr(err) + } + if err = d.Set("enforcement", configuration.GetEnforcement()); err != nil { + return diag.FromErr(err) + } + if err = d.Set("target_type", configuration.GetTargetType()); err != nil { + return diag.FromErr(err) + } + + tflog.Trace(ctx, fmt.Sprintf("Successfully read organization code security configuration: %s/%d", org, id), map[string]any{ + "organization": org, + "id": id, + }) + + return nil +} + +func resourceGithubOrganizationSecurityConfigurationUpdate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + err := checkOrganization(meta) + if err != nil { + return diag.FromErr(err) + } + client := meta.(*Owner).v3client + + org, idStr, err := parseID2(d.Id()) + if err != nil { + return diag.FromErr(err) + } + + id, err := strconv.ParseInt(idStr, 10, 64) + if err != nil { + return diag.FromErr(err) + } + + tflog.Debug(ctx, fmt.Sprintf("Updating organization code security configuration: %s/%d", org, id), map[string]any{ + "organization": org, + "id": id, + }) + + config := expandCodeSecurityConfiguration(d) + + _, _, err = client.Organizations.UpdateCodeSecurityConfiguration(ctx, org, id, config) + if err != nil { + tflog.Error(ctx, fmt.Sprintf("Failed to update organization code security configuration: %s/%d", org, id), map[string]any{ + "organization": org, + "id": id, + "error": err.Error(), + }) + return diag.FromErr(err) + } + + tflog.Info(ctx, fmt.Sprintf("Updated organization code security configuration: %s/%d", org, id), map[string]any{ + "organization": org, + "id": id, + }) + + return resourceGithubOrganizationSecurityConfigurationRead(ctx, d, meta) +} + +func resourceGithubOrganizationSecurityConfigurationDelete(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + err := checkOrganization(meta) + if err != nil { + return diag.FromErr(err) + } + client := meta.(*Owner).v3client + + org, idStr, err := parseID2(d.Id()) + if err != nil { + return diag.FromErr(err) + } + + id, err := strconv.ParseInt(idStr, 10, 64) + if err != nil { + return diag.FromErr(err) + } + + tflog.Debug(ctx, fmt.Sprintf("Deleting organization code security configuration: %s/%d", org, id), map[string]any{ + "organization": org, + "id": id, + }) + + _, err = client.Organizations.DeleteCodeSecurityConfiguration(ctx, org, id) + if err != nil { + var ghErr *github.ErrorResponse + if errors.As(err, &ghErr) && ghErr.Response.StatusCode == http.StatusNotFound { + tflog.Info(ctx, fmt.Sprintf("Organization code security configuration %s/%d already deleted", org, id), map[string]any{ + "organization": org, + "id": id, + }) + return nil + } + tflog.Error(ctx, fmt.Sprintf("Failed to delete organization code security configuration: %s/%d", org, id), map[string]any{ + "organization": org, + "id": id, + "error": err.Error(), + }) + return diag.FromErr(err) + } + + tflog.Info(ctx, fmt.Sprintf("Deleted organization code security configuration: %s/%d", org, id), map[string]any{ + "organization": org, + "id": id, + }) + + return nil +} + +func expandCodeSecurityConfiguration(d *schema.ResourceData) github.CodeSecurityConfiguration { + config := github.CodeSecurityConfiguration{ + Name: d.Get("name").(string), + Description: d.Get("description").(string), + } + + // Only set optional fields if they are explicitly configured + if val, ok := d.GetOk("advanced_security"); ok { + config.AdvancedSecurity = github.Ptr(val.(string)) + } + + if val, ok := d.GetOk("dependency_graph"); ok { + config.DependencyGraph = github.Ptr(val.(string)) + } + + if val, ok := d.GetOk("dependency_graph_autosubmit_action"); ok { + config.DependencyGraphAutosubmitAction = github.Ptr(val.(string)) + } + + if val, ok := d.GetOk("dependabot_alerts"); ok { + config.DependabotAlerts = github.Ptr(val.(string)) + } + + if val, ok := d.GetOk("dependabot_security_updates"); ok { + config.DependabotSecurityUpdates = github.Ptr(val.(string)) + } + + if val, ok := d.GetOk("code_scanning_default_setup"); ok { + config.CodeScanningDefaultSetup = github.Ptr(val.(string)) + } + + if val, ok := d.GetOk("code_scanning_delegated_alert_dismissal"); ok { + config.CodeScanningDelegatedAlertDismissal = github.Ptr(val.(string)) + } + + if val, ok := d.GetOk("code_security"); ok { + config.CodeSecurity = github.Ptr(val.(string)) + } + + if val, ok := d.GetOk("secret_scanning"); ok { + config.SecretScanning = github.Ptr(val.(string)) + } + + if val, ok := d.GetOk("secret_scanning_push_protection"); ok { + config.SecretScanningPushProtection = github.Ptr(val.(string)) + } + + if val, ok := d.GetOk("secret_scanning_delegated_bypass"); ok { + config.SecretScanningDelegatedBypass = github.Ptr(val.(string)) + } + + if val, ok := d.GetOk("secret_scanning_validity_checks"); ok { + config.SecretScanningValidityChecks = github.Ptr(val.(string)) + } + + if val, ok := d.GetOk("secret_scanning_non_provider_patterns"); ok { + config.SecretScanningNonProviderPatterns = github.Ptr(val.(string)) + } + + if val, ok := d.GetOk("secret_scanning_generic_secrets"); ok { + config.SecretScanningGenericSecrets = github.Ptr(val.(string)) + } + + if val, ok := d.GetOk("secret_scanning_delegated_alert_dismissal"); ok { + config.SecretScanningDelegatedAlertDismissal = github.Ptr(val.(string)) + } + + if val, ok := d.GetOk("secret_protection"); ok { + config.SecretProtection = github.Ptr(val.(string)) + } + + if val, ok := d.GetOk("private_vulnerability_reporting"); ok { + config.PrivateVulnerabilityReporting = github.Ptr(val.(string)) + } + + if val, ok := d.GetOk("enforcement"); ok { + config.Enforcement = github.Ptr(val.(string)) + } + + if val, ok := d.GetOk("dependency_graph_autosubmit_action_options"); ok { + optionsList := val.([]any) + if len(optionsList) > 0 { + autosubmitOpts := optionsList[0].(map[string]any) + config.DependencyGraphAutosubmitActionOptions = &github.DependencyGraphAutosubmitActionOptions{ + LabeledRunners: github.Ptr(autosubmitOpts["labeled_runners"].(bool)), + } + } + } + + if val, ok := d.GetOk("code_scanning_default_setup_options"); ok { + optionsList := val.([]any) + if len(optionsList) > 0 { + setupOpts := optionsList[0].(map[string]any) + config.CodeScanningDefaultSetupOptions = &github.CodeScanningDefaultSetupOptions{ + RunnerType: setupOpts["runner_type"].(string), + } + if runnerLabel, ok := setupOpts["runner_label"].(string); ok && runnerLabel != "" { + config.CodeScanningDefaultSetupOptions.RunnerLabel = github.Ptr(runnerLabel) + } + } + } + + if val, ok := d.GetOk("code_scanning_options"); ok { + optionsList := val.([]any) + if len(optionsList) > 0 { + scanOpts := optionsList[0].(map[string]any) + config.CodeScanningOptions = &github.CodeScanningOptions{ + AllowAdvanced: github.Ptr(scanOpts["allow_advanced"].(bool)), + } + } + } + + if val, ok := d.GetOk("secret_scanning_delegated_bypass_options"); ok { + optionsList := val.([]any) + if len(optionsList) > 0 { + bypassOpts := optionsList[0].(map[string]any) + options := &github.SecretScanningDelegatedBypassOptions{} + if reviewersVal, ok := bypassOpts["reviewers"]; ok { + reviewersList := reviewersVal.([]any) + reviewers := make([]*github.BypassReviewer, 0, len(reviewersList)) + for _, reviewerRaw := range reviewersList { + reviewerMap := reviewerRaw.(map[string]any) + reviewers = append(reviewers, &github.BypassReviewer{ + ReviewerID: int64(reviewerMap["reviewer_id"].(int)), + ReviewerType: reviewerMap["reviewer_type"].(string), + }) + } + options.Reviewers = reviewers + } + config.SecretScanningDelegatedBypassOptions = options + } + } + + return config +} diff --git a/github/resource_github_organization_security_configuration_test.go b/github/resource_github_organization_security_configuration_test.go new file mode 100644 index 0000000000..cfa3cbe754 --- /dev/null +++ b/github/resource_github_organization_security_configuration_test.go @@ -0,0 +1,175 @@ +package github + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +func TestAccGithubOrganizationSecurityConfiguration(t *testing.T) { + t.Run("creates organization security configuration without error", func(t *testing.T) { + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) + configName := fmt.Sprintf("test-config-%s", randomID) + + config := fmt.Sprintf(` + resource "github_organization_security_configuration" "test" { + name = "%s" + description = "Test configuration" + advanced_security = "enabled" + dependency_graph = "enabled" + dependabot_alerts = "enabled" + dependabot_security_updates = "enabled" + code_scanning_default_setup = "enabled" + secret_scanning = "enabled" + secret_scanning_push_protection = "enabled" + private_vulnerability_reporting = "enabled" + enforcement = "enforced" + }`, configName) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnlessHasOrgs(t) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{ + { + Config: config, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_organization_security_configuration.test", + tfjsonpath.New("name"), knownvalue.StringExact(configName)), + statecheck.ExpectKnownValue("github_organization_security_configuration.test", + tfjsonpath.New("description"), knownvalue.StringExact("Test configuration")), + statecheck.ExpectKnownValue("github_organization_security_configuration.test", + tfjsonpath.New("advanced_security"), knownvalue.StringExact("enabled")), + statecheck.ExpectKnownValue("github_organization_security_configuration.test", + tfjsonpath.New("enforcement"), knownvalue.StringExact("enforced")), + }, + }, + { + ResourceName: "github_organization_security_configuration.test", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) + }) + + t.Run("updates organization security configuration without error", func(t *testing.T) { + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) + configName := fmt.Sprintf("test-config-%s", randomID) + configNameUpdated := fmt.Sprintf("test-config-updated-%s", randomID) + + tmpl := ` + resource "github_organization_security_configuration" "test" { + name = "%s" + description = "%s" + advanced_security = "%s" + }` + configBefore := fmt.Sprintf(tmpl, configName, "Test configuration", "disabled") + configAfter := fmt.Sprintf(tmpl, configNameUpdated, "Test configuration updated", "enabled") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnlessHasOrgs(t) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{ + { + Config: configBefore, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_organization_security_configuration.test", + tfjsonpath.New("name"), knownvalue.StringExact(configName)), + statecheck.ExpectKnownValue("github_organization_security_configuration.test", + tfjsonpath.New("advanced_security"), knownvalue.StringExact("disabled")), + }, + }, + { + Config: configAfter, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_organization_security_configuration.test", + tfjsonpath.New("name"), knownvalue.StringExact(configNameUpdated)), + statecheck.ExpectKnownValue("github_organization_security_configuration.test", + tfjsonpath.New("advanced_security"), knownvalue.StringExact("enabled")), + }, + }, + }, + }) + }) + + t.Run("creates organization security configuration with options", func(t *testing.T) { + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) + configName := fmt.Sprintf("test-config-options-%s", randomID) + + config := fmt.Sprintf(` + resource "github_organization_security_configuration" "test" { + name = "%s" + description = "Test configuration with options" + advanced_security = "enabled" + dependency_graph = "enabled" + dependency_graph_autosubmit_action = "enabled" + dependency_graph_autosubmit_action_options { + labeled_runners = true + } + code_scanning_default_setup = "enabled" + code_scanning_default_setup_options { + runner_type = "labeled" + runner_label = "code-scanning" + } + secret_scanning = "enabled" + secret_scanning_push_protection = "enabled" + }`, configName) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnlessHasOrgs(t) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{ + { + Config: config, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_organization_security_configuration.test", + tfjsonpath.New("name"), knownvalue.StringExact(configName)), + statecheck.ExpectKnownValue("github_organization_security_configuration.test", + tfjsonpath.New("dependency_graph_autosubmit_action_options").AtSliceIndex(0).AtMapKey("labeled_runners"), knownvalue.Bool(true)), + statecheck.ExpectKnownValue("github_organization_security_configuration.test", + tfjsonpath.New("code_scanning_default_setup_options").AtSliceIndex(0).AtMapKey("runner_type"), knownvalue.StringExact("labeled")), + statecheck.ExpectKnownValue("github_organization_security_configuration.test", + tfjsonpath.New("code_scanning_default_setup_options").AtSliceIndex(0).AtMapKey("runner_label"), knownvalue.StringExact("code-scanning")), + }, + }, + { + ResourceName: "github_organization_security_configuration.test", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) + }) + + t.Run("creates organization security configuration with minimal config", func(t *testing.T) { + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) + configName := fmt.Sprintf("test-config-minimal-%s", randomID) + + config := fmt.Sprintf(` + resource "github_organization_security_configuration" "test" { + name = "%s" + description = "Minimal test configuration" + }`, configName) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnlessHasOrgs(t) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{ + { + Config: config, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_organization_security_configuration.test", + tfjsonpath.New("name"), knownvalue.StringExact(configName)), + statecheck.ExpectKnownValue("github_organization_security_configuration.test", + tfjsonpath.New("target_type"), knownvalue.NotNull()), + }, + }, + }, + }) + }) +} diff --git a/github/util_security_configuration.go b/github/util_security_configuration.go new file mode 100644 index 0000000000..f8d5bf4db3 --- /dev/null +++ b/github/util_security_configuration.go @@ -0,0 +1,61 @@ +package github + +import ( + "github.com/google/go-github/v82/github" +) + +// flattenDependencyGraphAutosubmitActionOptions converts DependencyGraphAutosubmitActionOptions to a Terraform-compatible format +func flattenDependencyGraphAutosubmitActionOptions(options *github.DependencyGraphAutosubmitActionOptions) []any { + if options == nil { + return []any{} + } + autosubmitOpts := make(map[string]any) + if options.LabeledRunners != nil { + autosubmitOpts["labeled_runners"] = options.GetLabeledRunners() + } + return []any{autosubmitOpts} +} + +// flattenCodeScanningDefaultSetupOptions converts CodeScanningDefaultSetupOptions to a Terraform-compatible format +func flattenCodeScanningDefaultSetupOptions(options *github.CodeScanningDefaultSetupOptions) []any { + if options == nil { + return []any{} + } + setupOpts := make(map[string]any) + setupOpts["runner_type"] = options.RunnerType + if options.RunnerLabel != nil { + setupOpts["runner_label"] = options.GetRunnerLabel() + } + return []any{setupOpts} +} + +// flattenCodeScanningOptions converts CodeScanningOptions to a Terraform-compatible format +func flattenCodeScanningOptions(options *github.CodeScanningOptions) []any { + if options == nil { + return []any{} + } + scanOpts := make(map[string]any) + if options.AllowAdvanced != nil { + scanOpts["allow_advanced"] = options.GetAllowAdvanced() + } + return []any{scanOpts} +} + +// flattenSecretScanningDelegatedBypassOptions converts SecretScanningDelegatedBypassOptions to a Terraform-compatible format +func flattenSecretScanningDelegatedBypassOptions(options *github.SecretScanningDelegatedBypassOptions) []any { + if options == nil { + return []any{} + } + bypassOpts := make(map[string]any) + if options.Reviewers != nil { + reviewers := make([]any, 0, len(options.Reviewers)) + for _, reviewer := range options.Reviewers { + reviewerMap := make(map[string]any) + reviewerMap["reviewer_id"] = reviewer.ReviewerID + reviewerMap["reviewer_type"] = reviewer.ReviewerType + reviewers = append(reviewers, reviewerMap) + } + bypassOpts["reviewers"] = reviewers + } + return []any{bypassOpts} +} diff --git a/website/docs/r/enterprise_security_configuration.html.markdown b/website/docs/r/enterprise_security_configuration.html.markdown new file mode 100644 index 0000000000..e86326b1a8 --- /dev/null +++ b/website/docs/r/enterprise_security_configuration.html.markdown @@ -0,0 +1,86 @@ +--- +layout: "github" +page_title: "GitHub: github_enterprise_security_configuration" +description: |- + Manages a code security configuration for a GitHub Enterprise. +--- + +# github_enterprise_security_configuration + +This resource allows you to create and manage code security configurations for a GitHub Enterprise. + +## Example Usage + +```hcl +resource "github_enterprise_security_configuration" "default" { + enterprise_slug = "my-enterprise" + name = "default-config" + description = "Default security configuration" + advanced_security = "enabled" + dependency_graph = "enabled" + dependabot_alerts = "enabled" + dependabot_security_updates = "enabled" + code_scanning_default_setup = "enabled" + secret_scanning = "enabled" + secret_scanning_push_protection = "enabled" + private_vulnerability_reporting = "enabled" + enforcement = "enforced" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `enterprise_slug` - (Required) The slug of the enterprise. Changing this forces a new resource to be created. +* `name` - (Required) The name of the code security configuration. +* `description` - (Required) A description of the code security configuration. +* `advanced_security` - (Optional) The advanced security configuration. Can be one of `enabled`, `disabled`. +* `dependency_graph` - (Optional) The dependency graph configuration. Can be one of `enabled`, `disabled`, `not_set`. +* `dependency_graph_autosubmit_action` - (Optional) The dependency graph autosubmit action configuration. Can be one of `enabled`, `disabled`, `not_set`. +* `dependency_graph_autosubmit_action_options` - (Optional) The dependency graph autosubmit action options. See [Dependency Graph Autosubmit Action Options](#dependency-graph-autosubmit-action-options) below for details. +* `dependabot_alerts` - (Optional) The dependabot alerts configuration. Can be one of `enabled`, `disabled`, `not_set`. +* `dependabot_security_updates` - (Optional) The dependabot security updates configuration. Can be one of `enabled`, `disabled`, `not_set`. +* `code_scanning_default_setup` - (Optional) The code scanning default setup configuration. Can be one of `enabled`, `disabled`, `not_set`. +* `code_scanning_default_setup_options` - (Optional) The code scanning default setup options. See [Code Scanning Default Setup Options](#code-scanning-default-setup-options) below for details. +* `code_scanning_options` - (Optional) The code scanning options. See [Code Scanning Options](#code-scanning-options) below for details. +* `code_security` - (Optional) The code security configuration. Can be one of `enabled`, `disabled`, `not_set`. +* `secret_scanning` - (Optional) The secret scanning configuration. Can be one of `enabled`, `disabled`, `not_set`. +* `secret_scanning_push_protection` - (Optional) The secret scanning push protection configuration. Can be one of `enabled`, `disabled`, `not_set`. +* `secret_scanning_validity_checks` - (Optional) The secret scanning validity checks configuration. Can be one of `enabled`, `disabled`, `not_set`. +* `secret_scanning_non_provider_patterns` - (Optional) The secret scanning non provider patterns configuration. Can be one of `enabled`, `disabled`, `not_set`. +* `secret_scanning_generic_secrets` - (Optional) The secret scanning generic secrets configuration. Can be one of `enabled`, `disabled`, `not_set`. +* `secret_protection` - (Optional) The secret protection configuration. Can be one of `enabled`, `disabled`, `not_set`. +* `private_vulnerability_reporting` - (Optional) The private vulnerability reporting configuration. Can be one of `enabled`, `disabled`, `not_set`. +* `enforcement` - (Optional) The enforcement configuration. Can be one of `enforced`, `unenforced`. + +## Attributes Reference + +* `target_type` - The target type of the code security configuration. + +### Dependency Graph Autosubmit Action Options + +The `dependency_graph_autosubmit_action_options` block supports: + +* `labeled_runners` - (Optional) Whether to use labeled runners for the dependency graph autosubmit action. + +### Code Scanning Default Setup Options + +The `code_scanning_default_setup_options` block supports: + +* `runner_type` - (Optional) The type of runner to use for code scanning default setup. Can be one of `standard`, `labeled`. +* `runner_label` - (Optional) The label of the runner to use for code scanning default setup. + +### Code Scanning Options + +The `code_scanning_options` block supports: + +* `allow_advanced` - (Optional) Whether to allow advanced security for code scanning. + +## Import + +GitHub Enterprise Code Security Configurations can be imported using the enterprise slug and the configuration ID separated by a colon, e.g. + +```text +$ terraform import github_enterprise_security_configuration.example my-enterprise:123 +``` diff --git a/website/docs/r/organization_security_configuration.html.markdown b/website/docs/r/organization_security_configuration.html.markdown new file mode 100644 index 0000000000..17283bfb3a --- /dev/null +++ b/website/docs/r/organization_security_configuration.html.markdown @@ -0,0 +1,101 @@ +--- +layout: "github" +page_title: "GitHub: github_organization_security_configuration" +description: |- + Manages a code security configuration for a GitHub Organization. +--- + +# github_organization_security_configuration + +This resource allows you to create and manage code security configurations for a GitHub Organization. + +## Example Usage + +```hcl +resource "github_organization_security_configuration" "default" { + name = "default-config" + description = "Default security configuration" + advanced_security = "enabled" + dependency_graph = "enabled" + dependabot_alerts = "enabled" + dependabot_security_updates = "enabled" + code_scanning_default_setup = "enabled" + secret_scanning = "enabled" + secret_scanning_push_protection = "enabled" + private_vulnerability_reporting = "enabled" + enforcement = "enforced" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of the code security configuration. +* `description` - (Required) A description of the code security configuration. +* `advanced_security` - (Optional) The advanced security configuration. Can be one of `enabled`, `disabled`. +* `dependency_graph` - (Optional) The dependency graph configuration. Can be one of `enabled`, `disabled`, `not_set`. +* `dependency_graph_autosubmit_action` - (Optional) The dependency graph autosubmit action configuration. Can be one of `enabled`, `disabled`, `not_set`. +* `dependency_graph_autosubmit_action_options` - (Optional) The dependency graph autosubmit action options. See [Dependency Graph Autosubmit Action Options](#dependency-graph-autosubmit-action-options) below for details. +* `dependabot_alerts` - (Optional) The dependabot alerts configuration. Can be one of `enabled`, `disabled`, `not_set`. +* `dependabot_security_updates` - (Optional) The dependabot security updates configuration. Can be one of `enabled`, `disabled`, `not_set`. +* `code_scanning_default_setup` - (Optional) The code scanning default setup configuration. Can be one of `enabled`, `disabled`, `not_set`. +* `code_scanning_default_setup_options` - (Optional) The code scanning default setup options. See [Code Scanning Default Setup Options](#code-scanning-default-setup-options) below for details. +* `code_scanning_delegated_alert_dismissal` - (Optional) The code scanning delegated alert dismissal configuration. Can be one of `enabled`, `disabled`, `not_set`. +* `code_scanning_options` - (Optional) The code scanning options. See [Code Scanning Options](#code-scanning-options) below for details. +* `code_security` - (Optional) The code security configuration. Can be one of `enabled`, `disabled`, `not_set`. +* `secret_scanning` - (Optional) The secret scanning configuration. Can be one of `enabled`, `disabled`, `not_set`. +* `secret_scanning_push_protection` - (Optional) The secret scanning push protection configuration. Can be one of `enabled`, `disabled`, `not_set`. +* `secret_scanning_delegated_bypass` - (Optional) The secret scanning delegated bypass configuration. Can be one of `enabled`, `disabled`, `not_set`. +* `secret_scanning_delegated_bypass_options` - (Optional) The secret scanning delegated bypass options. See [Secret Scanning Delegated Bypass Options](#secret-scanning-delegated-bypass-options) below for details. +* `secret_scanning_validity_checks` - (Optional) The secret scanning validity checks configuration. Can be one of `enabled`, `disabled`, `not_set`. +* `secret_scanning_non_provider_patterns` - (Optional) The secret scanning non provider patterns configuration. Can be one of `enabled`, `disabled`, `not_set`. +* `secret_scanning_generic_secrets` - (Optional) The secret scanning generic secrets configuration. Can be one of `enabled`, `disabled`, `not_set`. +* `secret_scanning_delegated_alert_dismissal` - (Optional) The secret scanning delegated alert dismissal configuration. Can be one of `enabled`, `disabled`, `not_set`. +* `secret_protection` - (Optional) The secret protection configuration. Can be one of `enabled`, `disabled`, `not_set`. +* `private_vulnerability_reporting` - (Optional) The private vulnerability reporting configuration. Can be one of `enabled`, `disabled`, `not_set`. +* `enforcement` - (Optional) The enforcement configuration. Can be one of `enforced`, `unenforced`. + +## Attributes Reference + +* `target_type` - The target type of the code security configuration. + +### Dependency Graph Autosubmit Action Options + +The `dependency_graph_autosubmit_action_options` block supports: + +* `labeled_runners` - (Optional) Whether to use labeled runners for the dependency graph autosubmit action. + +### Code Scanning Default Setup Options + +The `code_scanning_default_setup_options` block supports: + +* `runner_type` - (Optional) The type of runner to use for code scanning default setup. Can be one of `standard`, `labeled`. +* `runner_label` - (Optional) The label of the runner to use for code scanning default setup. + +### Code Scanning Options + +The `code_scanning_options` block supports: + +* `allow_advanced` - (Optional) Whether to allow advanced security for code scanning. + +### Secret Scanning Delegated Bypass Options + +The `secret_scanning_delegated_bypass_options` block supports: + +* `reviewers` - (Optional) The bypass reviewers for the secret scanning delegated bypass. See [Reviewers](#reviewers) below for details. + +#### Reviewers + +The `reviewers` block supports: + +* `reviewer_id` - (Required) The ID of the bypass reviewer. +* `reviewer_type` - (Required) The type of the bypass reviewer. Can be one of `Team`, `Role`. + +## Import + +GitHub Organization Code Security Configurations can be imported using the organization name and the configuration ID separated by a colon, e.g. + +```text +$ terraform import github_organization_security_configuration.example my-org:123 +``` \ No newline at end of file