diff --git a/.cz.toml b/.cz.toml new file mode 100644 index 0000000..a05167a --- /dev/null +++ b/.cz.toml @@ -0,0 +1,5 @@ +[tool.commitizen] +name = "cz_conventional_commits" +version_provider = "scm" +update_changelog_on_bump = true +major_version_zero = true diff --git a/.gitea/workflows/development.yml b/.gitea/workflows/development.yml new file mode 100644 index 0000000..bfba126 --- /dev/null +++ b/.gitea/workflows/development.yml @@ -0,0 +1,26 @@ +--- +name: development +on: + push: + branches-ignore: + - main + +jobs: + commit-check: + name: Check commit compliance + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install commitizen + run: pip3 install commitizen + shell: bash + working-directory: ./ + + - name: Verify commit message compliance + run: | + echo "cz check --message '${{ github.event.head_commit.message }}'" + cz check --message "${{ github.event.head_commit.message }}" + shell: bash + working-directory: ./ diff --git a/.gitea/workflows/pull-request-open.yml b/.gitea/workflows/pull-request-open.yml new file mode 100644 index 0000000..ea2a600 --- /dev/null +++ b/.gitea/workflows/pull-request-open.yml @@ -0,0 +1,35 @@ +--- +name: pull-requests-open +on: + pull_request: + types: + - opened + - edited + - synchronize + branches: + - main + +jobs: + commit-history-check: + name: Check commit compliance + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha }} + fetch-depth: 0 + + - name: Install commitizen + run: pip3 install commitizen + shell: bash + working-directory: ./ + + - run: git log origin/${{ github.event.pull_request.base.ref }}.. + + - name: Verify commit message compliance + run: | + echo "cz check --rev-range origin/${{ gitea.event.pull_request.base.ref }}.." + cz check --rev-range origin/${{ gitea.event.pull_request.base.ref }}.. + shell: bash + working-directory: ./ diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml new file mode 100644 index 0000000..e617278 --- /dev/null +++ b/.gitea/workflows/release.yml @@ -0,0 +1,54 @@ +--- +name: release +on: + push: + branches: + - main + +jobs: + do-release: + if: "!startsWith(github.event.head_commit.message, 'bump:')" + runs-on: ubuntu-latest + name: Bump version and create changelog with commitizen + steps: + - name: Get secrets from vault + id: import-secrets + uses: hashicorp/vault-action@v3 + with: + url: "https://vault.ednz.fr" + method: approle + roleId: ${{ secrets.VAULT_APPROLE_ID }} + secretId: ${{ secrets.VAULT_APPROLE_SECRET_ID }} + secrets: | + kv/data/applications/gitea/users/actions username | GITEA_ACTIONS_USERNAME ; + kv/data/applications/gitea/users/actions token_write | GITEA_ACTIONS_TOKEN ; + + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ steps.import-secrets.outputs.GITEA_ACTIONS_TOKEN }} + + - name: Install commitizen + run: pip3 install commitizen + shell: bash + working-directory: ./ + + - name: Configure git credentials + uses: oleksiyrudenko/gha-git-credentials@v2 + with: + global: true + name: "Gitea-Actions Bot" + email: "gitea-actions@ednz.fr" + actor: ${{ steps.import-secrets.outputs.GITEA_ACTIONS_USERNAME }} + token: ${{ steps.import-secrets.outputs.GITEA_ACTIONS_TOKEN }} + + - name: Do release + run: cz -nr 21 bump --yes + shell: bash + working-directory: ./ + + - name: Push release + run: git push && git push --tags + shell: bash + working-directory: ./ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..b7141fe --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,27 @@ +repos: + - repo: https://github.com/antonbabenko/pre-commit-terraform + rev: v1.86.0 + hooks: + - id: terraform_fmt + - id: terraform_docs + args: + - "--hook-config=--path-to-file=README.md" + - "--hook-config=--add-to-existing-file=true" + - "--hook-config=--create-file-if-not-exist=true" + - "--args=--escape=false" + - "--args=--lockfile=false" + - "--args=--indent 3" + - "--args=--show all" + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.5.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - repo: https://github.com/commitizen-tools/commitizen + rev: v3.24.0 + hooks: + - id: commitizen + - id: commitizen-branch + stages: + - post-commit + - push diff --git a/README.md b/README.md index 3345f7a..fb6d51c 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,66 @@ # terraform-vault-tenant -Terraform module to deploy tenant in Hashicorp Vault community version. \ No newline at end of file +This module aims to provide a way for companies and individuals running the community version of vault, to segregate accesses between teams. + +This "tenant" module requires that you have at least one approle auth method mounted prior to deploying it. It will create a tenant admin approle role on these mount, and apply the policy you define. It can also create an additional tenant-scoped approle auth mount, and create roles based on policies that you define. + +The idea behind this is that outside of access control, a tenant is free to create whatever secret engine, secrets, etc... As long as those are prefixed with their tenant prefix. + + +### Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement_terraform) | >= 1.0.0 | +| [random](#requirement_random) | ~> 3.6.2 | +| [vault](#requirement_vault) | ~> 4.2.0 | + +### Providers + +| Name | Version | +|------|---------| +| [random](#provider_random) | ~> 3.6.2 | +| [vault](#provider_vault) | ~> 4.2.0 | + +### Modules + +No modules. + +### Resources + +| Name | Type | +|------|------| +| [random_uuid.extra_secret_id](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/uuid) | resource | +| [random_uuid.root_secret_id](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/uuid) | resource | +| [vault_approle_auth_backend_role.extra](https://registry.terraform.io/providers/hashicorp/vault/latest/docs/resources/approle_auth_backend_role) | resource | +| [vault_approle_auth_backend_role.root](https://registry.terraform.io/providers/hashicorp/vault/latest/docs/resources/approle_auth_backend_role) | resource | +| [vault_approle_auth_backend_role_secret_id.extra](https://registry.terraform.io/providers/hashicorp/vault/latest/docs/resources/approle_auth_backend_role_secret_id) | resource | +| [vault_approle_auth_backend_role_secret_id.root](https://registry.terraform.io/providers/hashicorp/vault/latest/docs/resources/approle_auth_backend_role_secret_id) | resource | +| [vault_auth_backend.approle](https://registry.terraform.io/providers/hashicorp/vault/latest/docs/resources/auth_backend) | resource | +| [vault_identity_entity.extra](https://registry.terraform.io/providers/hashicorp/vault/latest/docs/resources/identity_entity) | resource | +| [vault_identity_entity.root](https://registry.terraform.io/providers/hashicorp/vault/latest/docs/resources/identity_entity) | resource | +| [vault_identity_entity_alias.extra](https://registry.terraform.io/providers/hashicorp/vault/latest/docs/resources/identity_entity_alias) | resource | +| [vault_identity_entity_alias.root](https://registry.terraform.io/providers/hashicorp/vault/latest/docs/resources/identity_entity_alias) | resource | +| [vault_identity_group.this](https://registry.terraform.io/providers/hashicorp/vault/latest/docs/resources/identity_group) | resource | +| [vault_policy.extra](https://registry.terraform.io/providers/hashicorp/vault/latest/docs/resources/policy) | resource | +| [vault_policy.root](https://registry.terraform.io/providers/hashicorp/vault/latest/docs/resources/policy) | resource | + +### Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [additional_roles](#input_additional_roles) | A map of additional role names, with the path to the associated policy file to add for this tenant.
A separate approle auth method is created for this tenant (mounted at auth/-approle) including all the roles declared in this variable.
The variable should look like:
additional_roles = {
devs = {
policy_file = "/some/path/to/policy.hcl"
}
admins = {...}
} |
map(object({
policy_file = string
}))
| `{}` | no | +| [name](#input_name) | The name of the tenant you want to create | `string` | n/a | yes | +| [prefix](#input_prefix) | The prefix to use for the tenant in vault (this will prefix mount points, policies, etc..) | `string` | n/a | yes | +| [root_policy_file](#input_root_policy_file) | The path to the admin policy file for this tenant | `string` | `null` | no | + +### Outputs + +| Name | Description | +|------|-------------| +| [approle_mount](#output_approle_mount) | The approle mount for the tenant | +| [extra_role_policies](#output_extra_role_policies) | The tenant extra role policy names | +| [extra_roles](#output_extra_roles) | The tenant extra approle roles | +| [root_policy](#output_root_policy) | The tenant root policy name | +| [root_role](#output_root_role) | The tenant root approle role | + diff --git a/auth.tf b/auth.tf new file mode 100644 index 0000000..f554194 --- /dev/null +++ b/auth.tf @@ -0,0 +1,17 @@ +resource "vault_auth_backend" "approle" { + type = "approle" + path = "${var.prefix}/approle" + tune { + default_lease_ttl = "3600s" + max_lease_ttl = "14400s" + } +} + +resource "vault_identity_group" "this" { + name = var.name + type = "internal" + metadata = { + tenant = var.name + prefix = var.prefix + } +} diff --git a/examples/.gitkeep b/examples/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/extra_policies.tf b/extra_policies.tf new file mode 100644 index 0000000..4d9535c --- /dev/null +++ b/extra_policies.tf @@ -0,0 +1,38 @@ +resource "vault_approle_auth_backend_role" "extra" { + for_each = var.additional_roles + + backend = vault_auth_backend.approle.path + role_name = each.key + token_policies = ["default", "${vault_policy.extra[each.key].name}"] +} + +resource "random_uuid" "extra_secret_id" { for_each = var.additional_roles } + +resource "vault_approle_auth_backend_role_secret_id" "extra" { + for_each = var.additional_roles + + backend = vault_auth_backend.approle.path + role_name = vault_approle_auth_backend_role.extra[each.key].role_name + secret_id = random_uuid.extra_secret_id[each.key].result +} + +resource "vault_policy" "extra" { + for_each = var.additional_roles + + name = "${var.prefix}-${each.key}" + policy = file(each.value.policy_file) +} + +resource "vault_identity_entity" "extra" { + for_each = var.additional_roles + + name = "${var.prefix}-${each.key}" +} + +resource "vault_identity_entity_alias" "extra" { + for_each = var.additional_roles + + name = vault_approle_auth_backend_role.extra[each.key].role_id + mount_accessor = vault_auth_backend.approle.accessor + canonical_id = vault_identity_entity.extra[each.key].id +} diff --git a/main.tf b/main.tf new file mode 100644 index 0000000..cfa3b67 --- /dev/null +++ b/main.tf @@ -0,0 +1,13 @@ +terraform { + required_version = ">= 1.0.0" + required_providers { + vault = { + source = "hashicorp/vault" + version = "~> 4.2.0" + } + random = { + source = "hashicorp/random" + version = "~> 3.6.2" + } + } +} diff --git a/modules/.gitkeep b/modules/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/outputs.tf b/outputs.tf new file mode 100644 index 0000000..7d2c537 --- /dev/null +++ b/outputs.tf @@ -0,0 +1,52 @@ +output "approle_mount" { + value = vault_auth_backend.approle + sensitive = true + description = "The approle mount for the tenant" +} + + +output "root_role" { + value = { + role_id = vault_approle_auth_backend_role.root.role_name + secret_id = vault_approle_auth_backend_role_secret_id.root.secret_id + } + sensitive = true + description = "The tenant root approle role" + depends_on = [ + vault_approle_auth_backend_role.root, + vault_approle_auth_backend_role_secret_id.root + ] +} + +output "root_policy" { + value = vault_policy.root.name + sensitive = false + description = "The tenant root policy name" + depends_on = [vault_policy.root] +} + +output "extra_roles" { + value = { + for key, role in vault_approle_auth_backend_role.extra : + key => { + role_id = role.role_name + secret_id = vault_approle_auth_backend_role_secret_id.extra[key].secret_id + } + } + sensitive = true + description = "The tenant extra approle roles" + depends_on = [ + vault_approle_auth_backend_role.extra, + vault_approle_auth_backend_role_secret_id.extra + ] +} + +output "extra_role_policies" { + value = { + for key, policy in vault_policy.extra : + key => policy.name + } + sensitive = false + description = "The tenant extra role policy names" + depends_on = [vault_policy.extra] +} diff --git a/policies/root.policy.hcl b/policies/root.policy.hcl new file mode 100644 index 0000000..9aeeaa9 --- /dev/null +++ b/policies/root.policy.hcl @@ -0,0 +1,19 @@ +path "${tenant_prefix}/*" { + capabilities = ["create", "update", "read", "delete", "list"] +} + +path "sys/mounts/${tenant_prefix}/*" { + capabilities = ["create", "update", "read", "delete", "list"] +} + +path "sys/remount" { + capabilities = ["update", "sudo"] + allowed_parameters = { + "from" = ["${tenant_prefix}/*"] + "to" = ["${tenant_prefix}/*"] + } +} + +path "sys/remount/status/*" { + capabilities = ["read"] +} diff --git a/root.tf b/root.tf new file mode 100644 index 0000000..6a756cb --- /dev/null +++ b/root.tf @@ -0,0 +1,28 @@ +resource "vault_approle_auth_backend_role" "root" { + backend = vault_auth_backend.approle.path + role_name = "${var.name}-root" + token_policies = ["default", vault_policy.root.name] +} + +resource "random_uuid" "root_secret_id" {} + +resource "vault_approle_auth_backend_role_secret_id" "root" { + backend = vault_auth_backend.approle.path + role_name = vault_approle_auth_backend_role.root.role_name + secret_id = random_uuid.root_secret_id.result +} + +resource "vault_policy" "root" { + name = "${var.name}-root" + policy = var.root_policy_file == null ? templatefile("${path.module}/policies/root.policy.hcl", { tenant_prefix = var.prefix }) : file(var.root_policy_file) +} + +resource "vault_identity_entity" "root" { + name = "${var.prefix}-root" +} + +resource "vault_identity_entity_alias" "root" { + name = vault_approle_auth_backend_role.root.role_id + mount_accessor = vault_auth_backend.approle.accessor + canonical_id = vault_identity_entity.root.id +} diff --git a/variables.tf b/variables.tf new file mode 100644 index 0000000..89cc6a1 --- /dev/null +++ b/variables.tf @@ -0,0 +1,37 @@ +variable "name" { + type = string + description = "The name of the tenant you want to create" + validation { + condition = can(regex("^[-a-zA-Z0-9_]*$", var.name)) + error_message = "The tenant name must only contain alphanumeric characters, dashes, and underscores." + } +} + +variable "prefix" { + type = string + description = "The prefix to use for the tenant in vault (this will prefix mount points, policies, etc..)" +} + +variable "root_policy_file" { + type = string + default = null + description = "The path to the admin policy file for this tenant" +} + +variable "additional_roles" { + type = map(object({ + policy_file = string + })) + default = {} + description = <-approle) including all the roles declared in this variable. + The variable should look like: + additional_roles = { + devs = { + policy_file = "/some/path/to/policy.hcl" + } + admins = {...} + } + EOT +}