diff --git a/README.md b/README.md
index 0f2af72..58fe43b 100644
--- a/README.md
+++ b/README.md
@@ -18,6 +18,7 @@ A [Terraform] module for creating a public or private repository on [Github].
- [Resources](#resources)
- [Inputs](#inputs)
- [Outputs](#outputs)
+ - [Security And Analysis Configuration](#security-and-analysis-configuration)
- [External Documentation](#external-documentation)
- [Terraform Github Provider Documentation](#terraform-github-provider-documentation)
- [Module Versioning](#module-versioning)
@@ -87,13 +88,13 @@ See [variables.tf] and [examples/] for details and use-cases.
| Name | Version |
|------|---------|
| [terraform](#requirement\_terraform) | >= 1.3 |
-| [github](#requirement\_github) | >= 6.2, < 7.0 |
+| [github](#requirement\_github) | >= 6.2, < 6.9 |
### Providers
| Name | Version |
|------|---------|
-| [github](#provider\_github) | >= 6.2, < 7.0 |
+| [github](#provider\_github) | >= 6.2, < 6.9 |
### Resources
@@ -178,6 +179,7 @@ See [variables.tf] and [examples/] for details and use-cases.
| [push\_team\_ids](#input\_push\_team\_ids) | (Optional) A list of teams (by id) to grant push (read-write) permission to. | `list(string)` | `[]` | no |
| [push\_teams](#input\_push\_teams) | (Optional) A list of teams (by name/slug) to grant push (read-write) permission to. | `list(string)` | `[]` | no |
| [rulesets](#input\_rulesets) | (Optional) A list of branch rulesets to apply to the repository. Default is [].
It is very likely removal of any section will require setting it to an empty list/map.
This is due to limitations in the API whereby components are not destroyed upon removal. |
list(| `[]` | no | +| [security\_and\_analysis](#input\_security\_and\_analysis) | (Optional) Security and analysis configuration for the repository.
object({
enforcement = string
name = string
target = string
rules = list(
object({
creation = optional(bool)
deletion = optional(bool)
non_fast_forward = optional(bool)
required_signatures = optional(bool)
required_linear_history = optional(bool)
update = optional(bool)
update_allows_fetch_and_merge = optional(bool)
branch_name_pattern = optional(
object({
operator = string
pattern = string
name = optional(string)
negate = optional(bool)
})
)
commit_author_email_pattern = optional(
object({
operator = string
pattern = string
name = optional(string)
negate = optional(bool)
})
)
commit_message_pattern = optional(
object({
operator = string
pattern = string
name = optional(string)
negate = optional(bool)
})
)
committer_email_pattern = optional(
object({
operator = string
pattern = string
name = optional(string)
negate = optional(bool)
})
)
tag_name_pattern = optional(
object({
operator = string
pattern = string
name = optional(string)
negate = optional(bool)
})
)
required_status_checks = optional(
object({
strict_required_status_checks_policy = optional(bool)
do_not_enforce_on_create = optional(bool)
required_check = list(
object({
context = string
integration_id = optional(number)
})
)
})
)
pull_request = optional(
object({
dismiss_stale_reviews_on_push = optional(bool)
require_code_owner_review = optional(bool)
require_last_push_approval = optional(bool)
required_approving_review_count = optional(number)
required_review_thread_resolution = optional(bool)
})
)
required_workflows = optional(
object({
required_workflow = list(
object({
repository_id = number
ref = string
path = string
})
)
})
)
required_deployments = optional(
object({
required_deployment_environments = list(string)
})
)
required_code_scanning = optional(
object({
required_code_scanning_tool = list(
object({
tool = string
alerts_threshold = string
security_alerts_threshold = string
})
)
})
)
merge_queue = optional(
object({
check_response_timeout_minutes = optional(number)
grouping_strategy = optional(string)
max_entries_to_build = optional(number)
max_entries_to_merge = optional(number)
merge_method = optional(string)
min_entries_to_merge = optional(number)
min_entries_to_merge_wait_minutes = optional(number)
})
)
})
)
bypass_actors = optional(
list(
object({
actor_id = optional(number)
actor_type = string
bypass_mode = optional(string)
})
)
)
conditions = optional(
object({
ref_name = object({
include = list(string)
exclude = list(string)
})
})
)
})
)
object({
org_advanced_security = optional(bool, false)
advanced_security = optional(string, "disabled")
code_security = optional(string, "disabled")
secret_scanning = optional(string, "disabled")
secret_scanning_push_protection = optional(string, "disabled")
secret_scanning_ai_detection = optional(string, "disabled")
secret_scanning_non_provider_patterns = optional(string, "disabled")
}) | `null` | no |
| [squash\_merge\_commit\_message](#input\_squash\_merge\_commit\_message) | (Optional) Can be `PR_BODY`, `COMMIT_MESSAGES`, or `BLANK` for a default squash merge commit message. | `string` | `"COMMIT_MESSAGES"` | no |
| [squash\_merge\_commit\_title](#input\_squash\_merge\_commit\_title) | (Optional) Can be `PR_BODY`, `COMMIT_MESSAGES`, or `BLANK` for a default squash merge commit message. | `string` | `"COMMIT_OR_PR_TITLE"` | no |
| [template](#input\_template) | (Optional) Template repository to use. (Default: {}) | object({
owner = string
repository = string
}) | `null` | no |
@@ -210,6 +212,41 @@ See [variables.tf] and [examples/] for details and use-cases.
| [webhooks](#output\_webhooks) | All attributes and arguments as returned by the github\_repository\_webhook resource. |
+### Security And Analysis Configuration
+
+- [**`security_and_analysis`**](#var-security_and_analysis): *(Optional `object(security_and_analysis)`)*
+
+ (Optional) The repository's [security and analysis](https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/enabling-features-for-your-repository/managing-security-and-analysis-settings-for-your-repository) configuration.
+ See [Security and Analysis Configuration](#security-and-analysis-configuration) below for details.
+
+ Default is `null`.
+
+ The `security_and_analysis` object accepts the following attributes:
+
+ - [**`org_advanced_security`**](#attr-security_and_analysis-org_advanced_security: *(**Optional** `bool`)*
+
+ If your GitHub Organization has split licensing for Advanced Security you can have Security and Analysis on non-public repositories. Otherwise Security and Analysis operates on all public repositories. Currently there is no known way to automatically detect this.
+
+ Define `org_advanced_security` as true to give more visibility options.
+
+ - [**`advanced_security`**](#attr-security_and_analysis-advanced_security): *(**Optional** `string`)*
+
+ The advanced security configuration for the repository. See [Advanced Security Configuration](#advanced-security-configuration) below for details.
+
+ Default is `"disabled"`, except if `org_advanced_security` is not true when it is `"enabled"`.
+
+ - [**`secret_scanning`**](#attr-security_and_analysis-secret_scanning): *(**Optional** `string`)*
+
+ The secret scanning configuration for the repository. See [Secret Scanning Configuration](#secret-scanning-configuration) below for details.
+
+ Default is `"disabled"`.
+
+ - [**`secret_scanning_push_protection`**](#attr-security_and_analysis-secret_scanning_push_protection): *(**Optional** `string`)*
+
+ The secret scanning push protection configuration for the repository. See [Secret Scanning Push Protection Configuration](#secret-scanning-push-protection-configuration) below for details.
+
+ Default is `"disabled"`.
+
## External Documentation
### Terraform Github Provider Documentation
diff --git a/locals.tf b/locals.tf
deleted file mode 100644
index 2868abc..0000000
--- a/locals.tf
+++ /dev/null
@@ -1,5 +0,0 @@
-locals {
- rulesets_by_name = {
- for rs in var.rulesets : rs.name => rs
- }
-}
diff --git a/main.tf b/main.tf
index 022a731..ceb3dbf 100644
--- a/main.tf
+++ b/main.tf
@@ -153,6 +153,40 @@ resource "github_repository" "repository" {
}
}
+ dynamic "security_and_analysis" {
+ for_each = (
+ var.visibility == "public" || local.security_and_analysis.org_advanced_security
+ ) ? [local.security_and_analysis] : []
+
+ content {
+ dynamic "advanced_security" {
+ for_each = var.visibility == "public" ? [] : [local.advanced_security_status]
+ content {
+ status = advanced_security.value
+ }
+ }
+
+ secret_scanning {
+ status = local.security_and_analysis.secret_scanning
+ }
+
+ secret_scanning_push_protection {
+ status = local.security_and_analysis.secret_scanning_push_protection
+ }
+
+ # code_security, secret_scanning_ai_detection, and secret_scanning_non_provider_patterns require integrations/github >= 6.9
+ # code_security {
+ # status = local.security_and_analysis.code_security
+ # }
+ # secret_scanning_ai_detection {
+ # status = local.security_and_analysis.secret_scanning_ai_detection
+ # }
+ # secret_scanning_non_provider_patterns {
+ # status = local.security_and_analysis.secret_scanning_non_provider_patterns
+ # }
+ }
+ }
+
lifecycle {
ignore_changes = [
auto_init,
@@ -160,6 +194,22 @@ resource "github_repository" "repository" {
gitignore_template,
template,
]
+ precondition {
+ condition = (
+ (
+ var.visibility == "public"
+ || local.security_and_analysis.org_advanced_security
+ || !local.saa_child_enabled
+ )
+ &&
+ local.push_protection_valid
+ )
+ error_message = (
+ local.push_protection_valid
+ ? "security_and_analysis cannot be used for private/internal repositories unless org_advanced_security is true."
+ : "secret_scanning_push_protection requires secret_scanning to also be enabled."
+ )
+ }
}
}
diff --git a/rulesets_resources.tf b/rulesets_resources.tf
index cd5ab82..7a17d42 100644
--- a/rulesets_resources.tf
+++ b/rulesets_resources.tf
@@ -1,3 +1,9 @@
+locals {
+ rulesets_by_name = {
+ for rs in var.rulesets : rs.name => rs
+ }
+}
+
resource "github_repository_ruleset" "ruleset" {
for_each = local.rulesets_by_name
diff --git a/securityanalysis_resources.tf b/securityanalysis_resources.tf
new file mode 100644
index 0000000..c5fdc02
--- /dev/null
+++ b/securityanalysis_resources.tf
@@ -0,0 +1,29 @@
+locals {
+ security_and_analysis = var.security_and_analysis != null ? var.security_and_analysis : {
+ org_advanced_security = false
+ advanced_security = "disabled"
+ code_security = "disabled"
+ secret_scanning = "disabled"
+ secret_scanning_push_protection = "disabled"
+ secret_scanning_ai_detection = "disabled"
+ secret_scanning_non_provider_patterns = "disabled"
+ }
+
+ saa_child_enabled = (
+ local.security_and_analysis.code_security == "enabled" ||
+ local.security_and_analysis.secret_scanning == "enabled" ||
+ local.security_and_analysis.secret_scanning_push_protection == "enabled" ||
+ local.security_and_analysis.secret_scanning_ai_detection == "enabled" ||
+ local.security_and_analysis.secret_scanning_non_provider_patterns == "enabled"
+ )
+
+ advanced_security_status = (
+ local.saa_child_enabled || local.security_and_analysis.advanced_security == "enabled"
+ ) ? "enabled" : "disabled"
+
+ push_protection_valid = (
+ local.security_and_analysis.secret_scanning_push_protection == "enabled"
+ ? local.security_and_analysis.secret_scanning == "enabled"
+ : true
+ )
+}
diff --git a/securityanalysis_variables.tf b/securityanalysis_variables.tf
new file mode 100644
index 0000000..656fe85
--- /dev/null
+++ b/securityanalysis_variables.tf
@@ -0,0 +1,31 @@
+variable "security_and_analysis" {
+ description = <