diff --git a/modules/azure-kv/.terraform-docs.yml b/modules/azure-kv/.terraform-docs.yml new file mode 100644 index 000000000..199016010 --- /dev/null +++ b/modules/azure-kv/.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: true + escape: true + hide-empty: false + html: true + indent: 2 + lockfile: false + read-comments: true + required: true + sensitive: true + type: true diff --git a/modules/azure-kv/README.md b/modules/azure-kv/README.md index 2df9594a8..3ddb53955 100644 --- a/modules/azure-kv/README.md +++ b/modules/azure-kv/README.md @@ -1,17 +1,122 @@ + +# **Azure Key Vault Terraform Module** + +## Overview + +This module creates an **Azure Key Vault** (`azurerm_key_vault`) in an **existing resource group**. It supports **Azure RBAC** on the vault or **classic access policies**, optional resolution of principals via **Azure AD** data sources (user, group, or service principal by display name or UPN), tag merge from the resource group, and lifecycle **preconditions** so access policies are not mixed with RBAC when that combination is invalid. + +The module does **not** create the resource group, private endpoints, or key/secret/certificate resources; consumers add those separately if needed. Use it when you want a small, opinionated wrapper around `azurerm_key_vault` with consistent tagging and access-policy wiring for teams that still use vault access policies or are migrating toward vault-level RBAC. + +## Key Features + +- **Key Vault**: SKU, soft delete retention, purge protection, disk encryption integration, tenant binding. +- **Authorization model**: `enable_rbac_authorization` toggles between RBAC and access policies; when RBAC is enabled, `access_policies` must be empty. +- **Access policies**: Optional list; object fields remain optional in the type for compatibility. For real policies, use a **non-empty, unique `name`** per row (`main.tf` uses it as a `for_each` key and for Azure AD lookups). Use `type` (`user`, `group`, `service_principal`) plus `name`, or `object_id` with empty `type` for a direct principal. +- **Outputs**: Exposes the Key Vault **`id`** for downstream resources, role assignments, or references that need the Azure resource identifier. + +## Prerequisites + +- Existing **resource group** (`resource_group`); the module reads it for location and optional tag inheritance. +- **Key Vault name** must be **globally unique**, 3–24 characters, and use only letters, numbers, and hyphens. +- The module depends on **hashicorp/azuread** and **hashicorp/azurerm** (`versions.tf`); Terraform installs the required provider versions. Configure **`provider "azuread"`** with working credentials **only when** access policies use `type` `user`, `group`, or `service_principal` so the lookup data sources can run. For RBAC-only setups or policies that only set `object_id` (empty `type`), those data sources are not instantiated and you do not need Azure AD authentication for this module alone. +- For **access policies** with principal lookup, principals must exist and be resolvable (correct UPN, group display name, or service principal display name). + +## Basic Usage + +Point `source` at this module and set required inputs. Use **RBAC** (`enable_rbac_authorization = true`) with an empty `access_policies` list for the smallest surface, or **access policies** with `enable_rbac_authorization = false`. + +### Example (RBAC on the vault) + +```hcl +module "key_vault" { + source = "git::https://github.com/prefapp/tfm.git//modules/azure-kv?ref=" + + name = "kv-myapp-dev01" + resource_group = "my-resource-group" + enabled_for_disk_encryption = false + soft_delete_retention_days = 7 + purge_protection_enabled = false + sku_name = "standard" + enable_rbac_authorization = true + access_policies = [] + + tags_from_rg = false + tags = { + environment = "dev" + } +} +``` + +Assign **Key Vault data plane** or **management** roles at vault scope or above when using RBAC; this module does not create role assignments. + +### Example (access policies, RBAC disabled) + +```hcl +module "key_vault_access_policies" { + source = "git::https://github.com/prefapp/tfm.git//modules/azure-kv?ref=" + + name = "kv-myapp-legacy01" + resource_group = "my-resource-group" + enabled_for_disk_encryption = false + soft_delete_retention_days = 7 + purge_protection_enabled = false + sku_name = "standard" + enable_rbac_authorization = false + + tags_from_rg = false + tags = { + environment = "dev" + } + + access_policies = [ + { + name = "workload-spn" + type = "" + object_id = "11111111-1111-1111-1111-111111111111" + key_permissions = ["Get", "List"] + secret_permissions = ["Get", "List"] + certificate_permissions = ["Get", "List"] + storage_permissions = ["Get", "List"] + }, + ] +} +``` + +Replace `object_id` with a real principal in your tenant. To resolve principals by **UPN / display name** instead, set `type` to `user`, `group`, or `service_principal` and set `name` accordingly; that path uses the Azure AD provider data sources. + +## File structure + +``` +. +├── CHANGELOG.md +├── main.tf +├── outputs.tf +├── variables.tf +├── versions.tf +├── docs +│ ├── footer.md +│ └── header.md +├── _examples +│ ├── basic +│ └── comprehensive +├── README.md +└── .terraform-docs.yml +``` + ## Requirements | Name | Version | |------|---------| | [terraform](#requirement\_terraform) | >= 1.7.0 | -| [azurerm](#requirement\_azurerm) | ~> 4.21.0 | -| [azurerm](#requirement\_azurerm) | ~> 2.53.0 | +| [azuread](#requirement\_azuread) | ~> 2.53.0 | +| [azurerm](#requirement\_azurerm) | >= 4.21.0 | ## Providers | Name | Version | |------|---------| -| [azurerm](#provider\_azurerm) | ~> 4.21.0 | -| [azuread](#provider\_azuread) | ~> 2.53.0 | +| [azuread](#provider\_azuread) | ~> 2.53.0 | +| [azurerm](#provider\_azurerm) | >= 4.21.0 | ## Modules @@ -22,83 +127,55 @@ No modules. | Name | Type | |------|------| | [azurerm_key_vault.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/key_vault) | resource | +| [azuread_group.this](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/data-sources/group) | data source | +| [azuread_service_principal.this](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/data-sources/service_principal) | data source | +| [azuread_user.this](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/data-sources/user) | data source | | [azurerm_client_config.current](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/client_config) | data source | | [azurerm_resource_group.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/resource_group) | data source | -| [azuread_user.this](https://registry.terraform.io/providers/hashicorp/azuread/2.53.0/docs/data-sources/user) | data source | -| [azuread_group.this](https://registry.terraform.io/providers/hashicorp/azuread/2.53.0/docs/data-sources/group) | data source | -| [azuread_application.this](https://registry.terraform.io/providers/hashicorp/azuread/2.53.0/docs/data-sources/application) | data source | ## Inputs | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| -| [enabled\_for\_disk\_encryption](#input\_enabled\_for\_disk\_encryption) | Specifies whether the Key Vault is enabled for Azure Disk Encryption | `bool` | n/a | yes | -| [name](#input\_name) | The name of the Key Vault | `string` | n/a | yes | -| [purge\_protection\_enabled](#input\_purge\_protection\_enabled) | Specifies whether purge protection is enabled for the Key Vault | `bool` | n/a | yes | -| [resource\_group](#input\_resource\_group) | The name of the resource group in which the Key Vault is created | `string` | n/a | yes | -| [sku\_name](#input\_sku\_name) | The SKU name of the Key Vault (e.g., standard or premium) | `string` | n/a | yes | -| [soft\_delete\_retention\_days](#input\_soft\_delete\_retention\_days) | The number of days that soft-deleted items are retained in the Key Vault | `number` | n/a | yes | -| [enable_rbac_authorization](#input\_enable\_rbac\_authorization) | Set RBAC authorization for the Key Vault. Disable access policies authorization | `bool` | n/a | yes | -| [access_policies](#input\_access\_policies) | Block for access policies definition. Will fail if `enable_rbac_authorization: true` | `list(object)` | n/a | optional | -| [access_policies.name](#input\_access\_policies.name) | Name for the access policy. Display name in groups and SPN, user principal name in users and custom for `object_id` | `string` | n/a | optional | -| [access_policies.name.type](#input\_access\_policies.name.type) | Entity type \[ group \| service_principal \| user \]. If we provide the `object_id` type value must be `""` | `string` | n/a | optional | -| [access_policies.name.object_id](#input\_access\_policies.name.object_id) | Object ID of the entity. If we provide an entity type value must be `""` | `string` | n/a | optional | -| [access_policies.name.key_permissions](#input\_access\_policies.name.key_permissions) | [List of key permissions](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/key_vault#key_permissions) | `list(string)` | n/a | optional | -| [access_policies.name.secret_permissions](#input\_access\_policies.name.secret_permissions) | [List of secret permissions](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/key_vault#secret_permissions) | `list(string)` | n/a | optional | -| [access_policies.name.certificate_permissions](#input\_access\_policies.name.certificate_permissions) | [List of certificate permissions](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/key_vault#certificate_permissions) | `list(string)` | n/a | optional | -| [access_policies.name.storage_permissions](#input\_access\_policies.name.storage_permissions) | [List of storage permissions](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/key_vault#storage_permissions) | `list(string)` | n/a | optional | -| [tags\_from\_rg](#input\_tags\_from\_rg) | Enable inherit tags from resource group | `bool` | `false` | no | -| [tags](#input\_tags) | Tags | `map(string)` | `{}` | no | +| [access\_policies](#input\_access\_policies) | Legacy access policies when `enable_rbac_authorization` is false.

Attributes stay **optional** in the object type for backward compatibility with existing callers. When you pass entries, each one should still set a **non-empty, unique `name`**: `main.tf` uses `name` as the `for_each` key and to index Azure AD data sources, so missing or duplicate names typically surface as errors at plan time.

Provide `object_id` (with `type` unset/empty) or set `type` to `user`, `group`, or `service_principal` and use `name` as UPN, group display name, or service principal display name for lookup.

If `type` is empty and `object_id` is empty, the module cannot resolve an object ID and that row is skipped for the vault access policy block (no explicit error). |
list(object({
type = optional(string)
name = optional(string)
object_id = optional(string, "")
key_permissions = optional(list(string))
secret_permissions = optional(list(string))
certificate_permissions = optional(list(string))
storage_permissions = optional(list(string))
}))
| `[]` | no | +| [enable\_rbac\_authorization](#input\_enable\_rbac\_authorization) | When true, use Azure RBAC for data plane access; access policies must be empty (see precondition in main.tf). | `bool` | n/a | yes | +| [enabled\_for\_disk\_encryption](#input\_enabled\_for\_disk\_encryption) | Whether the vault can be used for Azure Disk Encryption. | `bool` | n/a | yes | +| [name](#input\_name) | Globally unique name of the Key Vault (3–24 characters; letters, numbers, and hyphens). | `string` | n/a | yes | +| [purge\_protection\_enabled](#input\_purge\_protection\_enabled) | Whether purge protection is enabled (prevents permanent purge of soft-deleted vaults and objects when true). | `bool` | n/a | yes | +| [resource\_group](#input\_resource\_group) | Name of the existing resource group where the Key Vault will be created. | `string` | n/a | yes | +| [sku\_name](#input\_sku\_name) | SKU for the vault (`standard` or `premium`). | `string` | n/a | yes | +| [soft\_delete\_retention\_days](#input\_soft\_delete\_retention\_days) | Number of days soft-deleted items are retained before permanent deletion. | `number` | n/a | yes | +| [tags](#input\_tags) | Tags applied to the Key Vault (merged with resource group tags when `tags_from_rg` is true). | `map(string)` | `{}` | no | +| [tags\_from\_rg](#input\_tags\_from\_rg) | When true, merge tags from the resource group with `tags`. | `bool` | `false` | no | ## Outputs | Name | Description | |------|-------------| -| [id](#output\_id) | n/a | - -## Example - -```yaml -# kv.yaml - values: - name: "keyvault_name" - tags_from_rg: true - tags: - extra_tags: "example" - enabled_for_disk_encryption: true - resource_group: "resource_group_name" - soft_delete_retention_days: 7 - purge_protection_enabled: true - sku_name: "standard" - enable_rbac_authorization: false # If RBAC is set to true access policies will fail if there are any defined. - access_policies: - - name: "Name for the Object ID" - type: "" # Leave empty value if you provide directly the object ID - object_id: "1a9590f4-27d3-4abf-9e30-5be7f46959bb" - key_permissions: ["Get", "List"] - secret_permissions: ["Get", "List"] - certificate_permissions: ["Get", "List"] - storage_permissions: ["Get", "List"] - - name: "Group display name" - type: "group" - object_id: "" # Leave empty value if you want to look up the group ID - key_permissions: ["Get", "List"] - secret_permissions: ["Get", "List"] - certificate_permissions: ["Get", "List"] - storage_permissions: ["Get", "List"] - - name: "Service Principal display name" - type: "service_principal" - object_id: "" # Leave empty value if you want to look up the service principal ID - key_permissions: ["Get", "List"] - secret_permissions: ["Get", "List"] - certificate_permissions: ["Get", "List"] - storage_permissions: ["Get", "List"] - - name: "User principal name" - type: "user" - object_id: "" # Leave empty value if you want to look up the user ID - key_permissions: ["Get", "List"] - secret_permissions: ["Get", "List"] - certificate_permissions: ["Get", "List"] - storage_permissions: ["Get", "List"] +| [id](#output\_id) | The Azure resource ID of the Key Vault. | -``` +## Examples + +For detailed examples, refer to the [module examples](https://github.com/prefapp/tfm/tree/main/modules/azure-kv/_examples): + +- [basic](https://github.com/prefapp/tfm/tree/main/modules/azure-kv/_examples/basic) — Key Vault with **RBAC** enabled and no access policies; set an existing resource group and a globally unique vault name (see folder README). +- [comprehensive](https://github.com/prefapp/tfm/tree/main/modules/azure-kv/_examples/comprehensive) — Reference HCL and YAML for **access policies** (object ID and Azure AD lookups) and tag options (`values.reference.yaml`; see folder README). + +## Remote resources + +Terraform **azurerm** links below use **4.21.0** as a baseline aligned with the minimum `azurerm` version in `versions.tf` (`>= 4.21.0`). **azuread** links use **2.53.0**, aligned with `versions.tf` (`~> 2.53.0`). Provider version constraints for your workspace appear in the **Providers** table above after regenerating this README with `terraform-docs .`, as described in [README.md generation](https://github.com/prefapp/tfm/blob/main/CONTRIBUTING.md#5-readmemd-generation). + +- **Azure Key Vault**: [https://learn.microsoft.com/azure/key-vault/](https://learn.microsoft.com/azure/key-vault/) +- **azurerm\_key\_vault**: [https://registry.terraform.io/providers/hashicorp/azurerm/4.21.0/docs/resources/key_vault](https://registry.terraform.io/providers/hashicorp/azurerm/4.21.0/docs/resources/key_vault) +- **azurerm\_client\_config** (data source): [https://registry.terraform.io/providers/hashicorp/azurerm/4.21.0/docs/data-sources/client_config](https://registry.terraform.io/providers/hashicorp/azurerm/4.21.0/docs/data-sources/client_config) +- **azurerm\_resource\_group** (data source): [https://registry.terraform.io/providers/hashicorp/azurerm/4.21.0/docs/data-sources/resource_group](https://registry.terraform.io/providers/hashicorp/azurerm/4.21.0/docs/data-sources/resource_group) +- **azuread\_user** (data source): [https://registry.terraform.io/providers/hashicorp/azuread/2.53.0/docs/data-sources/user](https://registry.terraform.io/providers/hashicorp/azuread/2.53.0/docs/data-sources/user) +- **azuread\_group** (data source): [https://registry.terraform.io/providers/hashicorp/azuread/2.53.0/docs/data-sources/group](https://registry.terraform.io/providers/hashicorp/azuread/2.53.0/docs/data-sources/group) +- **azuread\_service\_principal** (data source): [https://registry.terraform.io/providers/hashicorp/azuread/2.53.0/docs/data-sources/service_principal](https://registry.terraform.io/providers/hashicorp/azuread/2.53.0/docs/data-sources/service_principal) +- **Terraform AzureRM provider**: [https://registry.terraform.io/providers/hashicorp/azurerm/4.21.0](https://registry.terraform.io/providers/hashicorp/azurerm/4.21.0) +- **Terraform AzureAD provider**: [https://registry.terraform.io/providers/hashicorp/azuread/2.53.0](https://registry.terraform.io/providers/hashicorp/azuread/2.53.0) + +## 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/azure-kv/_examples/basic/README.md b/modules/azure-kv/_examples/basic/README.md new file mode 100644 index 000000000..03a91625b --- /dev/null +++ b/modules/azure-kv/_examples/basic/README.md @@ -0,0 +1,5 @@ +# Basic example — `azure-kv` + +Creates one Key Vault with **Azure RBAC** enabled and **no access policies**. + +Set `resource_group` to an existing resource group and `name` to a **globally unique** vault name (3–24 characters: letters, numbers, and hyphens). This root only configures **`azurerm`** for your subscription; the child module still lists **`azuread`** in its `required_providers` (Terraform installs it), but this RBAC-only path does not evaluate Azure AD data sources. If you extend the stack with access policies using `type` `user`, `group`, or `service_principal`, add **`provider "azuread"`** (and credentials) so those lookups can run. diff --git a/modules/azure-kv/_examples/basic/main.tf b/modules/azure-kv/_examples/basic/main.tf new file mode 100644 index 000000000..2c00ea780 --- /dev/null +++ b/modules/azure-kv/_examples/basic/main.tf @@ -0,0 +1,39 @@ +# Replace resource_group with an existing RG name and name with a globally unique vault name (3–24 characters: letters, numbers, hyphens). + +terraform { + required_version = ">= 1.7.0" + + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = ">= 4.21.0" + } + } +} + +provider "azurerm" { + features {} +} + +module "key_vault" { + source = "../.." + + name = "kv-basicex0001" + resource_group = "example-rg" + enabled_for_disk_encryption = false + soft_delete_retention_days = 7 + purge_protection_enabled = false + sku_name = "standard" + enable_rbac_authorization = true + access_policies = [] + + tags_from_rg = false + tags = { + example = "basic" + } +} + +output "key_vault_id" { + description = "Azure resource ID of the Key Vault (only output exposed by this module)." + value = module.key_vault.id +} diff --git a/modules/azure-kv/_examples/comprehensive/README.md b/modules/azure-kv/_examples/comprehensive/README.md new file mode 100644 index 000000000..3da8db3f7 --- /dev/null +++ b/modules/azure-kv/_examples/comprehensive/README.md @@ -0,0 +1,6 @@ +# Comprehensive reference — `azure-kv` + +- [`module.reference.hcl`](./module.reference.hcl) — Illustrative module block with **access policies** (direct `object_id` and Azure AD lookups by type/name). +- [`values.reference.yaml`](./values.reference.yaml) — Same inputs as YAML for `yamldecode` or external tooling. + +These samples are documentation-oriented: replace names, IDs, and resource group with values valid in your tenant before apply. The variable type keeps `name` optional for compatibility, but each sample row uses a **distinct, non-empty `name`** because `main.tf` keys lookups by `name`. Keep large samples here instead of embedding them in the terraform-docs `README.md` body. diff --git a/modules/azure-kv/_examples/comprehensive/module.reference.hcl b/modules/azure-kv/_examples/comprehensive/module.reference.hcl new file mode 100644 index 000000000..3f2880cbc --- /dev/null +++ b/modules/azure-kv/_examples/comprehensive/module.reference.hcl @@ -0,0 +1,58 @@ +# Same intent as values.reference.yaml, expressed as a module block. +# enable_rbac_authorization must be false when access_policies is non-empty. + +module "key_vault" { + source = "../.." + + name = "kv-myapp-ref01" + resource_group = "my-resource-group" + enabled_for_disk_encryption = true + soft_delete_retention_days = 7 + purge_protection_enabled = true + sku_name = "standard" + enable_rbac_authorization = false + + tags_from_rg = true + tags = { + extra = "from-module" + } + + access_policies = [ + { + name = "Name for the Object ID" + type = "" + object_id = "1a9590f4-27d3-4abf-9e30-5be7f46959bb" + key_permissions = ["Get", "List"] + secret_permissions = ["Get", "List"] + certificate_permissions = ["Get", "List"] + storage_permissions = ["Get", "List"] + }, + { + name = "Group display name" + type = "group" + object_id = "" + key_permissions = ["Get", "List"] + secret_permissions = ["Get", "List"] + certificate_permissions = ["Get", "List"] + storage_permissions = ["Get", "List"] + }, + { + name = "Service Principal display name" + type = "service_principal" + object_id = "" + key_permissions = ["Get", "List"] + secret_permissions = ["Get", "List"] + certificate_permissions = ["Get", "List"] + storage_permissions = ["Get", "List"] + }, + { + name = "user@contoso.com" + type = "user" + object_id = "" + key_permissions = ["Get", "List"] + secret_permissions = ["Get", "List"] + certificate_permissions = ["Get", "List"] + storage_permissions = ["Get", "List"] + }, + ] +} diff --git a/modules/azure-kv/_examples/comprehensive/values.reference.yaml b/modules/azure-kv/_examples/comprehensive/values.reference.yaml new file mode 100644 index 000000000..f5b6eb3da --- /dev/null +++ b/modules/azure-kv/_examples/comprehensive/values.reference.yaml @@ -0,0 +1,43 @@ +# Reference values for azure-kv (e.g. Helm / wrapper YAML). +# If enable_rbac_authorization is true, access_policies must be []. + +values: + name: "kv-myapp-ref01" + tags_from_rg: true + tags: + extra: "from-yaml" + enabled_for_disk_encryption: true + resource_group: "my-resource-group" + soft_delete_retention_days: 7 + purge_protection_enabled: true + sku_name: "standard" + enable_rbac_authorization: false + access_policies: + - name: "Name for the Object ID" + type: "" + object_id: "1a9590f4-27d3-4abf-9e30-5be7f46959bb" + key_permissions: ["Get", "List"] + secret_permissions: ["Get", "List"] + certificate_permissions: ["Get", "List"] + storage_permissions: ["Get", "List"] + - name: "Group display name" + type: "group" + object_id: "" + key_permissions: ["Get", "List"] + secret_permissions: ["Get", "List"] + certificate_permissions: ["Get", "List"] + storage_permissions: ["Get", "List"] + - name: "Service Principal display name" + type: "service_principal" + object_id: "" + key_permissions: ["Get", "List"] + secret_permissions: ["Get", "List"] + certificate_permissions: ["Get", "List"] + storage_permissions: ["Get", "List"] + - name: "user@contoso.com" + type: "user" + object_id: "" + key_permissions: ["Get", "List"] + secret_permissions: ["Get", "List"] + certificate_permissions: ["Get", "List"] + storage_permissions: ["Get", "List"] diff --git a/modules/azure-kv/docs/footer.md b/modules/azure-kv/docs/footer.md new file mode 100644 index 000000000..615f200b6 --- /dev/null +++ b/modules/azure-kv/docs/footer.md @@ -0,0 +1,24 @@ +## Examples + +For detailed examples, refer to the [module examples](https://github.com/prefapp/tfm/tree/main/modules/azure-kv/_examples): + +- [basic](https://github.com/prefapp/tfm/tree/main/modules/azure-kv/_examples/basic) — Key Vault with **RBAC** enabled and no access policies; set an existing resource group and a globally unique vault name (see folder README). +- [comprehensive](https://github.com/prefapp/tfm/tree/main/modules/azure-kv/_examples/comprehensive) — Reference HCL and YAML for **access policies** (object ID and Azure AD lookups) and tag options (`values.reference.yaml`; see folder README). + +## Remote resources + +Terraform **azurerm** links below use **4.21.0** as a baseline aligned with the minimum `azurerm` version in `versions.tf` (`>= 4.21.0`). **azuread** links use **2.53.0**, aligned with `versions.tf` (`~> 2.53.0`). Provider version constraints for your workspace appear in the **Providers** table above after regenerating this README with `terraform-docs .`, as described in [README.md generation](https://github.com/prefapp/tfm/blob/main/CONTRIBUTING.md#5-readmemd-generation). + +- **Azure Key Vault**: [https://learn.microsoft.com/azure/key-vault/](https://learn.microsoft.com/azure/key-vault/) +- **azurerm_key_vault**: [https://registry.terraform.io/providers/hashicorp/azurerm/4.21.0/docs/resources/key_vault](https://registry.terraform.io/providers/hashicorp/azurerm/4.21.0/docs/resources/key_vault) +- **azurerm_client_config** (data source): [https://registry.terraform.io/providers/hashicorp/azurerm/4.21.0/docs/data-sources/client_config](https://registry.terraform.io/providers/hashicorp/azurerm/4.21.0/docs/data-sources/client_config) +- **azurerm_resource_group** (data source): [https://registry.terraform.io/providers/hashicorp/azurerm/4.21.0/docs/data-sources/resource_group](https://registry.terraform.io/providers/hashicorp/azurerm/4.21.0/docs/data-sources/resource_group) +- **azuread_user** (data source): [https://registry.terraform.io/providers/hashicorp/azuread/2.53.0/docs/data-sources/user](https://registry.terraform.io/providers/hashicorp/azuread/2.53.0/docs/data-sources/user) +- **azuread_group** (data source): [https://registry.terraform.io/providers/hashicorp/azuread/2.53.0/docs/data-sources/group](https://registry.terraform.io/providers/hashicorp/azuread/2.53.0/docs/data-sources/group) +- **azuread_service_principal** (data source): [https://registry.terraform.io/providers/hashicorp/azuread/2.53.0/docs/data-sources/service_principal](https://registry.terraform.io/providers/hashicorp/azuread/2.53.0/docs/data-sources/service_principal) +- **Terraform AzureRM provider**: [https://registry.terraform.io/providers/hashicorp/azurerm/4.21.0](https://registry.terraform.io/providers/hashicorp/azurerm/4.21.0) +- **Terraform AzureAD provider**: [https://registry.terraform.io/providers/hashicorp/azuread/2.53.0](https://registry.terraform.io/providers/hashicorp/azuread/2.53.0) + +## 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/azure-kv/docs/header.md b/modules/azure-kv/docs/header.md new file mode 100644 index 000000000..51fafe104 --- /dev/null +++ b/modules/azure-kv/docs/header.md @@ -0,0 +1,103 @@ +# **Azure Key Vault Terraform Module** + +## Overview + +This module creates an **Azure Key Vault** (`azurerm_key_vault`) in an **existing resource group**. It supports **Azure RBAC** on the vault or **classic access policies**, optional resolution of principals via **Azure AD** data sources (user, group, or service principal by display name or UPN), tag merge from the resource group, and lifecycle **preconditions** so access policies are not mixed with RBAC when that combination is invalid. + +The module does **not** create the resource group, private endpoints, or key/secret/certificate resources; consumers add those separately if needed. Use it when you want a small, opinionated wrapper around `azurerm_key_vault` with consistent tagging and access-policy wiring for teams that still use vault access policies or are migrating toward vault-level RBAC. + +## Key Features + +- **Key Vault**: SKU, soft delete retention, purge protection, disk encryption integration, tenant binding. +- **Authorization model**: `enable_rbac_authorization` toggles between RBAC and access policies; when RBAC is enabled, `access_policies` must be empty. +- **Access policies**: Optional list; object fields remain optional in the type for compatibility. For real policies, use a **non-empty, unique `name`** per row (`main.tf` uses it as a `for_each` key and for Azure AD lookups). Use `type` (`user`, `group`, `service_principal`) plus `name`, or `object_id` with empty `type` for a direct principal. +- **Outputs**: Exposes the Key Vault **`id`** for downstream resources, role assignments, or references that need the Azure resource identifier. + +## Prerequisites + +- Existing **resource group** (`resource_group`); the module reads it for location and optional tag inheritance. +- **Key Vault name** must be **globally unique**, 3–24 characters, and use only letters, numbers, and hyphens. +- The module depends on **hashicorp/azuread** and **hashicorp/azurerm** (`versions.tf`); Terraform installs the required provider versions. Configure **`provider "azuread"`** with working credentials **only when** access policies use `type` `user`, `group`, or `service_principal` so the lookup data sources can run. For RBAC-only setups or policies that only set `object_id` (empty `type`), those data sources are not instantiated and you do not need Azure AD authentication for this module alone. +- For **access policies** with principal lookup, principals must exist and be resolvable (correct UPN, group display name, or service principal display name). + +## Basic Usage + +Point `source` at this module and set required inputs. Use **RBAC** (`enable_rbac_authorization = true`) with an empty `access_policies` list for the smallest surface, or **access policies** with `enable_rbac_authorization = false`. + +### Example (RBAC on the vault) + +```hcl +module "key_vault" { + source = "git::https://github.com/prefapp/tfm.git//modules/azure-kv?ref=" + + name = "kv-myapp-dev01" + resource_group = "my-resource-group" + enabled_for_disk_encryption = false + soft_delete_retention_days = 7 + purge_protection_enabled = false + sku_name = "standard" + enable_rbac_authorization = true + access_policies = [] + + tags_from_rg = false + tags = { + environment = "dev" + } +} +``` + +Assign **Key Vault data plane** or **management** roles at vault scope or above when using RBAC; this module does not create role assignments. + +### Example (access policies, RBAC disabled) + +```hcl +module "key_vault_access_policies" { + source = "git::https://github.com/prefapp/tfm.git//modules/azure-kv?ref=" + + name = "kv-myapp-legacy01" + resource_group = "my-resource-group" + enabled_for_disk_encryption = false + soft_delete_retention_days = 7 + purge_protection_enabled = false + sku_name = "standard" + enable_rbac_authorization = false + + tags_from_rg = false + tags = { + environment = "dev" + } + + access_policies = [ + { + name = "workload-spn" + type = "" + object_id = "11111111-1111-1111-1111-111111111111" + key_permissions = ["Get", "List"] + secret_permissions = ["Get", "List"] + certificate_permissions = ["Get", "List"] + storage_permissions = ["Get", "List"] + }, + ] +} +``` + +Replace `object_id` with a real principal in your tenant. To resolve principals by **UPN / display name** instead, set `type` to `user`, `group`, or `service_principal` and set `name` accordingly; that path uses the Azure AD provider data sources. + +## File structure + +``` +. +├── CHANGELOG.md +├── main.tf +├── outputs.tf +├── variables.tf +├── versions.tf +├── docs +│ ├── footer.md +│ └── header.md +├── _examples +│ ├── basic +│ └── comprehensive +├── README.md +└── .terraform-docs.yml +``` diff --git a/modules/azure-kv/main.tf b/modules/azure-kv/main.tf index 2bb2aa317..b240707f6 100644 --- a/modules/azure-kv/main.tf +++ b/modules/azure-kv/main.tf @@ -1,8 +1,8 @@ # DATA SECTION -## https://registry.terraform.io/providers/hashicorp/azurerm/3.113.0/docs/data-sources/client_config +## https://registry.terraform.io/providers/hashicorp/azurerm/4.21.0/docs/data-sources/client_config data "azurerm_client_config" "current" {} -## https://registry.terraform.io/providers/hashicorp/azurerm/3.113.0/docs/data-sources/resource_group +## https://registry.terraform.io/providers/hashicorp/azurerm/4.21.0/docs/data-sources/resource_group data "azurerm_resource_group" "this" { name = var.resource_group } @@ -57,7 +57,7 @@ locals { # RESOURCES SECTION -## https://registry.terraform.io/providers/hashicorp/azurerm/3.113.0/docs/resources/key_vault +## https://registry.terraform.io/providers/hashicorp/azurerm/4.21.0/docs/resources/key_vault resource "azurerm_key_vault" "this" { name = var.name location = data.azurerm_resource_group.this.location diff --git a/modules/azure-kv/outputs.tf b/modules/azure-kv/outputs.tf index c06b777b0..3dc3367dc 100644 --- a/modules/azure-kv/outputs.tf +++ b/modules/azure-kv/outputs.tf @@ -1,3 +1,4 @@ output "id" { - value = azurerm_key_vault.this.id + description = "The Azure resource ID of the Key Vault." + value = azurerm_key_vault.this.id } diff --git a/modules/azure-kv/variables.tf b/modules/azure-kv/variables.tf index ae4bcbc8d..7c2825d2b 100644 --- a/modules/azure-kv/variables.tf +++ b/modules/azure-kv/variables.tf @@ -1,39 +1,48 @@ variable "name" { - type = string + type = string + description = "Globally unique name of the Key Vault (3–24 characters; letters, numbers, and hyphens)." } variable "enabled_for_disk_encryption" { - type = bool + type = bool + description = "Whether the vault can be used for Azure Disk Encryption." } variable "soft_delete_retention_days" { - type = number + type = number + description = "Number of days soft-deleted items are retained before permanent deletion." } variable "purge_protection_enabled" { - type = bool + type = bool + description = "Whether purge protection is enabled (prevents permanent purge of soft-deleted vaults and objects when true)." } variable "sku_name" { - type = string + type = string + description = "SKU for the vault (`standard` or `premium`)." } variable "resource_group" { - type = string + type = string + description = "Name of the existing resource group where the Key Vault will be created." } variable "enable_rbac_authorization" { - type = bool + type = bool + description = "When true, use Azure RBAC for data plane access; access policies must be empty (see precondition in main.tf)." } variable "tags_from_rg" { - type = bool - default = false + type = bool + description = "When true, merge tags from the resource group with `tags`." + default = false } variable "tags" { - type = map(string) - default = {} + type = map(string) + description = "Tags applied to the Key Vault (merged with resource group tags when `tags_from_rg` is true)." + default = {} } variable "access_policies" { @@ -46,5 +55,14 @@ variable "access_policies" { certificate_permissions = optional(list(string)) storage_permissions = optional(list(string)) })) - default = [] + description = <<-EOT + Legacy access policies when `enable_rbac_authorization` is false. + + Attributes stay **optional** in the object type for backward compatibility with existing callers. When you pass entries, each one should still set a **non-empty, unique `name`**: `main.tf` uses `name` as the `for_each` key and to index Azure AD data sources, so missing or duplicate names typically surface as errors at plan time. + + Provide `object_id` (with `type` unset/empty) or set `type` to `user`, `group`, or `service_principal` and use `name` as UPN, group display name, or service principal display name for lookup. + + If `type` is empty and `object_id` is empty, the module cannot resolve an object ID and that row is skipped for the vault access policy block (no explicit error). + EOT + default = [] }