diff --git a/modules/gh-repo-secrets-section/.terraform-docs.yml b/modules/gh-repo-secrets-section/.terraform-docs.yml new file mode 100644 index 000000000..3a69365ff --- /dev/null +++ b/modules/gh-repo-secrets-section/.terraform-docs.yml @@ -0,0 +1,48 @@ +formatter: "markdown" + +version: "" + +header-from: docs/header.md +footer-from: docs/footer.md + +recursive: + enabled: false + path: modules + include-main: true + +sections: + hide: [] + show: [] + +content: "" + +output: + file: "README.md" + mode: inject + template: |- + + {{ .Content }} + + +output-values: + enabled: false + from: "" + +sort: + enabled: true + by: name + +settings: + anchor: true + color: true + default: true + description: false + escape: true + hide-empty: false + html: true + indent: 2 + lockfile: true + read-comments: true + required: true + sensitive: true + type: true diff --git a/modules/gh-repo-secrets-section/README.md b/modules/gh-repo-secrets-section/README.md new file mode 100644 index 000000000..9bd6bfec6 --- /dev/null +++ b/modules/gh-repo-secrets-section/README.md @@ -0,0 +1,117 @@ + +# **GitHub Repository Secrets Terraform Module** + +## Overview + +This module manages GitHub repository secrets for **Actions**, **Codespaces**, and **Dependabot** using a single strongly-typed `config` object. + +**Important**: +The `encryptedValue` values passed in the `config` **must already be encrypted** using **libsodium** against the target repository’s public key (GitHub’s recommended method). +**Terraform does not perform any encryption**. It simply forwards the pre-encrypted value to the GitHub API. + +This approach is the most secure for automated pipelines (Prefapp IDP, GitHub Actions, etc.). + +## Key Features + +- **Single complex object**: All secrets are defined in one `config` variable. +- **Pre-encrypted values**: `encryptedValue` must be provided already encrypted with libsodium. +- **Three secret types**: Actions, Codespaces, and Dependabot supported in the same module. +- **Lifecycle protection**: `ignore_changes` on `encrypted_value` to prevent unnecessary drift. +- **Full validation**: Enforces required fields and non-empty values. +- **JSON-native**: Works seamlessly with `terraform.tfvars.json` generated by external programs. + +## Basic Usage + +### Using `terraform.tfvars.json` (recommended) + +```hcl +module "repo_secrets" { + source = "git::https://github.com/prefapp/tfm.git//modules/github-repository-secrets" + + config = var.config # Terraform automatically loads terraform.tfvars.json +} + +### Inline example + +Manages GitHub secrets for **one repository only**. + +```hcl +module "secrets" { + source = "github.com/prefapp/tfm//modules/gh-repo-secrets-section" + + config = { + repository = "prefapp/tfm" + + actions = { + "MY\_API\_KEY" = "r+RFBGIn8U7z2Opm5RN7PXKdgFzefXiV91IpG3O2DrClZl9dkTJBfhRZbi2uV2nu..." + } + codespaces = {} + dependabot = {} + } +} +``` +``` + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.5.0 | +| [github](#requirement\_github) | ~> 6.0 | + +## Providers + +| Name | Version | +|------|---------| +| [github](#provider\_github) | ~> 6.0 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [github_actions_secret.this](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/actions_secret) | resource | +| [github_codespaces_secret.this](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/codespaces_secret) | resource | +| [github_dependabot_secret.this](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/dependabot_secret) | resource | +| [github_repository.this](https://registry.terraform.io/providers/integrations/github/latest/docs/data-sources/repository) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [config](#input\_config) | Complete configuration object for this module (single repository only).

• repository = GitHub repo in 'owner/repo' format (required)
• actions / codespaces / dependabot = map of secret\_name => encrypted\_value
• encrypted\_value must be pre-encrypted with libsodium using the repo's public key. |
object({
repository = string

actions = optional(map(string), {})
codespaces = optional(map(string), {})
dependabot = optional(map(string), {})
})
| n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [actions\_secret\_names](#output\_actions\_secret\_names) | List of created Actions secret names | +| [all\_secret\_names](#output\_all\_secret\_names) | Combined list of all secret names | +| [codespaces\_secret\_names](#output\_codespaces\_secret\_names) | List of created Codespaces secret names | +| [dependabot\_secret\_names](#output\_dependabot\_secret\_names) | List of created Dependabot secret names | +| [repository](#output\_repository) | The repository these secrets belong to | + +### 3. `docs/footer.md` +```markdown +## Examples + +For detailed examples, refer to the [module examples](https://github.com/prefapp/tfm/tree/main/modules/github-repository-secrets/_examples): + +- [basic](https://github.com/prefapp/tfm/tree/main/modules/github-repository-secrets/_examples/basic) - Full example with Actions, Codespaces, and Dependabot secrets + +## Resources + +- **github_actions_secret**: [Official Documentation](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/actions_secret) +- **github_codespaces_secret**: [Official Documentation](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/codespaces_secret) +- **github_dependabot_secret**: [Official Documentation](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/dependabot_secret) +- **GitHub Terraform Provider**: [Official Documentation](https://registry.terraform.io/providers/integrations/github/latest/docs) + +## Support + +For issues, questions, or contributions related to this module, please visit the [repository's issue tracker](https://github.com/prefapp/tfm/issues). + +``` + \ No newline at end of file diff --git a/modules/gh-repo-secrets-section/_examples/basic/main.tf b/modules/gh-repo-secrets-section/_examples/basic/main.tf new file mode 100644 index 000000000..98b21c0ee --- /dev/null +++ b/modules/gh-repo-secrets-section/_examples/basic/main.tf @@ -0,0 +1,5 @@ +module "repo_secrets" { + source = "git::https://github.com/prefapp/tfm.git//modules/gh-repo-secrets-section" + + config = var.config # Terraform automatically loads terraform.tfvars.json +} diff --git a/modules/gh-repo-secrets-section/_examples/basic/secrets-config.json b/modules/gh-repo-secrets-section/_examples/basic/secrets-config.json new file mode 100644 index 000000000..a5529b683 --- /dev/null +++ b/modules/gh-repo-secrets-section/_examples/basic/secrets-config.json @@ -0,0 +1,13 @@ +{ + "config": { + "actions": { + "SECRET_A": "ENCRYPTED_SECRET_A_PLACEHOLDER", + + "SECRET_B": "ENCRYPTED_SECRET_B_PLACEHOLDER" + }, + "codespaces": { + "SECRET_C": "ENCRYPTED_SECRET_C_PLACEHOLDER" + }, + "dependabot": {} + } +} diff --git a/modules/gh-repo-secrets-section/docs/footer.md b/modules/gh-repo-secrets-section/docs/footer.md new file mode 100644 index 000000000..a220a64f4 --- /dev/null +++ b/modules/gh-repo-secrets-section/docs/footer.md @@ -0,0 +1,16 @@ +## Examples + +For detailed examples, refer to the [module examples](https://github.com/prefapp/tfm/tree/main/modules/gh-repo-secrets-section/_examples): + +- [basic](https://github.com/prefapp/tfm/tree/main/modules/gh-repo-secrets-section/_examples/basic) - Full example with Actions, Codespaces, and Dependabot secrets + +## Resources + +- **github_actions_secret**: [Official Documentation](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/actions_secret) +- **github_codespaces_secret**: [Official Documentation](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/codespaces_secret) +- **github_dependabot_secret**: [Official Documentation](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/dependabot_secret) +- **GitHub Terraform Provider**: [Official Documentation](https://registry.terraform.io/providers/integrations/github/latest/docs) + +## Support + +For issues, questions, or contributions related to this module, please visit the [repository's issue tracker](https://github.com/prefapp/tfm/issues). diff --git a/modules/gh-repo-secrets-section/docs/header.md b/modules/gh-repo-secrets-section/docs/header.md new file mode 100644 index 000000000..eb7cb5cda --- /dev/null +++ b/modules/gh-repo-secrets-section/docs/header.md @@ -0,0 +1,53 @@ +# **GitHub Repository Secrets Terraform Module** + +## Overview + +This module manages GitHub repository secrets for **Actions**, **Codespaces**, and **Dependabot** using a single strongly-typed `config` object. + +**Important**: +The `encryptedValue` values passed in the `config` **must already be encrypted** using **libsodium** against the target repository’s public key (GitHub’s recommended method). +**Terraform does not perform any encryption**. It simply forwards the pre-encrypted value to the GitHub API. + +This approach is the most secure for automated pipelines (Prefapp IDP, GitHub Actions, etc.). + +## Key Features + +- **Single complex object**: All secrets are defined in one `config` variable. +- **Pre-encrypted values**: `encryptedValue` must be provided already encrypted with libsodium. +- **Three secret types**: Actions, Codespaces, and Dependabot supported in the same module. +- **Lifecycle protection**: `ignore_changes` on `encrypted_value` to prevent unnecessary drift. +- **Full validation**: Enforces required fields and non-empty values. +- **JSON-native**: Works seamlessly with `terraform.tfvars.json` generated by external programs. + +## Basic Usage + +### Using `terraform.tfvars.json` (recommended) + +```hcl +module "repo_secrets" { + source = "git::https://github.com/prefapp/tfm.git//modules/gh-repo-secrets-section" + + config = var.config # Terraform automatically loads terraform.tfvars.json +} +``` + +### Inline example + +Manages GitHub secrets for **one repository only**. + +```hcl +<<<<<<< HEAD +module "repo_secrets" { + source = "git::https://github.com/prefapp/tfm.git//modules/gh-repo-secrets-section" + + config = { + repository = "prefapp/tfm" + + actions = { + "MY_API_KEY" = "r+RFBGIn8U7z2Opm5RN7PXKdgFzefXiV91IpG3O2DrClZl9dkTJBfhRZbi2uV2nu..." + } + codespaces = {} + dependabot = {} + } +} +``` diff --git a/modules/gh-repo-secrets-section/main.tf b/modules/gh-repo-secrets-section/main.tf new file mode 100644 index 000000000..6db1e0a34 --- /dev/null +++ b/modules/gh-repo-secrets-section/main.tf @@ -0,0 +1,51 @@ +# ───────────────────────────────────────────────────────────── +# Fetch repository info (validates existence + gives canonical name) +# ───────────────────────────────────────────────────────────── +data "github_repository" "this" { + full_name = var.config.repository +} + +# ───────────────────────────────────────────────────────────── +# GitHub Actions Secrets +# ───────────────────────────────────────────────────────────── +resource "github_actions_secret" "this" { + for_each = var.config.actions + + repository = data.github_repository.this.name + secret_name = each.key + encrypted_value = each.value + + lifecycle { + ignore_changes = [encrypted_value] + } +} + +# ───────────────────────────────────────────────────────────── +# GitHub Codespaces Secrets +# ───────────────────────────────────────────────────────────── +resource "github_codespaces_secret" "this" { + for_each = var.config.codespaces + + repository = data.github_repository.this.name + secret_name = each.key + encrypted_value = each.value + + lifecycle { + ignore_changes = [encrypted_value] + } +} + +# ───────────────────────────────────────────────────────────── +# GitHub Dependabot Secrets +# ───────────────────────────────────────────────────────────── +resource "github_dependabot_secret" "this" { + for_each = var.config.dependabot + + repository = data.github_repository.this.name + secret_name = each.key + encrypted_value = each.value + + lifecycle { + ignore_changes = [encrypted_value] + } +} diff --git a/modules/gh-repo-secrets-section/outputs.tf b/modules/gh-repo-secrets-section/outputs.tf new file mode 100644 index 000000000..ad9338624 --- /dev/null +++ b/modules/gh-repo-secrets-section/outputs.tf @@ -0,0 +1,28 @@ +output "repository" { + description = "The repository these secrets belong to" + value = var.config.repository +} + +output "actions_secret_names" { + description = "List of created Actions secret names" + value = keys(var.config.actions) +} + +output "codespaces_secret_names" { + description = "List of created Codespaces secret names" + value = keys(var.config.codespaces) +} + +output "dependabot_secret_names" { + description = "List of created Dependabot secret names" + value = keys(var.config.dependabot) +} + +output "all_secret_names" { + description = "Combined list of all secret names" + value = concat( + keys(var.config.actions), + keys(var.config.codespaces), + keys(var.config.dependabot) + ) +} diff --git a/modules/gh-repo-secrets-section/variables.tf b/modules/gh-repo-secrets-section/variables.tf new file mode 100644 index 000000000..e778998a0 --- /dev/null +++ b/modules/gh-repo-secrets-section/variables.tf @@ -0,0 +1,24 @@ +variable "config" { + description = <<-EOT + Complete configuration object for this module (single repository only). + + • repository = GitHub repo in 'owner/repo' format (required) + • actions / codespaces / dependabot = map of secret_name => encrypted_value + • encrypted_value must be pre-encrypted with libsodium using the repo's public key. + EOT + + type = object({ + repository = string + + actions = optional(map(string), {}) + codespaces = optional(map(string), {}) + dependabot = optional(map(string), {}) + }) + + validation { + condition = can(regex("^[a-zA-Z0-9_-]+/[a-zA-Z0-9_.-]+$", var.config.repository)) + error_message = "config.repository must be in 'owner/repo' format." + } + + nullable = false +} diff --git a/modules/gh-repo-secrets-section/versions.tf b/modules/gh-repo-secrets-section/versions.tf new file mode 100644 index 000000000..952a90543 --- /dev/null +++ b/modules/gh-repo-secrets-section/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.5.0" + + required_providers { + github = { + source = "integrations/github" + version = "~> 6.0" + } + } +} diff --git a/release-please-config.json b/release-please-config.json index 1791e861c..8b5a97438 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -151,6 +151,9 @@ "modules/aws-secretsmanager-replication": { "package-name": "aws-secretsmanager-replication" }, + "modules/gh-repo-secrets-section": { + "package-name": "gh-repo-secrets-section" + }, "modules/azure-vnet-gateway": { "package-name": "azure-vnet-gateway" },