Compare commits
19 Commits
5fd2fb3b8f
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 2aa5cea027 | |||
| 2d5fff80dd | |||
| bb64931de7 | |||
| 6db8100aba | |||
| 187e29d068 | |||
| c7ef079c88 | |||
| b315698634 | |||
| 8322c5480e | |||
| 11260a81a4 | |||
| 420ccd4cfd | |||
| 0ff9f6bf7d | |||
| 7be6a3e6e4 | |||
| dbd1f6db8a | |||
| 08fbc9b8ab | |||
| f2f41fc1c0 | |||
| 1dc43580f6 | |||
| 80fa1b2844 | |||
| 5b6a0527ee | |||
| 90a01563dc |
@@ -7,8 +7,9 @@ steps:
|
||||
image: git.unkin.net/unkin/almalinux9-opentofu:20260606
|
||||
environment:
|
||||
VAULT_AUTH_METHOD: kubernetes
|
||||
VAULT_VERSION: "1.20.0"
|
||||
commands:
|
||||
- dnf install vault -y
|
||||
- curl -fsSL -o /tmp/vault.zip "https://artifactapi.k8s.syd1.au.unkin.net/api/v1/remote/hashicorp-releases/vault/$${VAULT_VERSION}/vault_$${VAULT_VERSION}_linux_amd64.zip" && python3 -m zipfile -e /tmp/vault.zip /tmp/ && install -m0755 /tmp/vault /usr/local/bin/vault && rm -f /tmp/vault.zip /tmp/vault /tmp/LICENSE.txt
|
||||
- make plan
|
||||
- make apply
|
||||
backend_options:
|
||||
|
||||
@@ -6,8 +6,9 @@ steps:
|
||||
image: git.unkin.net/unkin/almalinux9-opentofu:20260606
|
||||
environment:
|
||||
VAULT_AUTH_METHOD: kubernetes
|
||||
VAULT_VERSION: "1.20.0"
|
||||
commands:
|
||||
- dnf install vault -y
|
||||
- curl -fsSL -o /tmp/vault.zip "https://artifactapi.k8s.syd1.au.unkin.net/api/v1/remote/hashicorp-releases/vault/$${VAULT_VERSION}/vault_$${VAULT_VERSION}_linux_amd64.zip" && python3 -m zipfile -e /tmp/vault.zip /tmp/ && install -m0755 /tmp/vault /usr/local/bin/vault && rm -f /tmp/vault.zip /tmp/vault /tmp/LICENSE.txt
|
||||
- make plan
|
||||
backend_options:
|
||||
kubernetes:
|
||||
|
||||
@@ -4,13 +4,9 @@ VAULT_AUTH_METHOD ?= approle
|
||||
VAULT_K8S_ROLE ?= woodpecker_terraform_rancher
|
||||
VAULT_K8S_MOUNT ?= auth/k8s/au/syd1
|
||||
VAULT_K8S_JWT_PATH ?= /var/run/secrets/kubernetes.io/serviceaccount/token
|
||||
# kv-v2 location of the Rancher admin API token used by the rancher2 provider.
|
||||
# TODO: migrate to the dedicated Vault Rancher secrets engine once it exists
|
||||
# (swap the `vault kv get` below for `vault read -field=token rancher/creds/<role>`).
|
||||
# Until then note Rancher API tokens have a 90-day max lifetime and must be rotated.
|
||||
RANCHER_TOKEN_KV_MOUNT ?= kv
|
||||
RANCHER_TOKEN_KV_PATH ?= service/terraform/rancher
|
||||
RANCHER_TOKEN_KV_FIELD ?= token
|
||||
# Vault Rancher engine role minting the token the rancher2 provider uses; the
|
||||
# token is ephemeral (1h lease) and Vault revokes it in Rancher on expiry.
|
||||
RANCHER_CREDS_PATH ?= rancher/creds/ci
|
||||
|
||||
define vault_env
|
||||
@export VAULT_ADDR="https://vault.service.consul:8200" && \
|
||||
@@ -20,7 +16,7 @@ define vault_env
|
||||
export VAULT_TOKEN=$$(vault write -field=token auth/approle/login role_id=$$VAULT_ROLEID); \
|
||||
fi && \
|
||||
export CONSUL_HTTP_TOKEN=$$(vault read -field=token consul_root/au/syd1/creds/terraform-rancher) && \
|
||||
export TF_VAR_rancher_token=$$(vault kv get -mount=$(RANCHER_TOKEN_KV_MOUNT) -field=$(RANCHER_TOKEN_KV_FIELD) $(RANCHER_TOKEN_KV_PATH))
|
||||
export TF_VAR_rancher_token=$$(vault read -field=token $(RANCHER_CREDS_PATH))
|
||||
endef
|
||||
|
||||
init:
|
||||
@@ -29,7 +25,7 @@ init:
|
||||
|
||||
plan: init
|
||||
@$(call vault_env) && \
|
||||
terragrunt run --all --parallelism 4 --non-interactive plan
|
||||
terragrunt run --all --parallelism 4 --non-interactive plan -- -lock=false
|
||||
|
||||
apply: init
|
||||
@$(call vault_env) && \
|
||||
|
||||
@@ -7,9 +7,18 @@ provider. Mirrors the `terraform-authentik` pattern.
|
||||
## Managed Resources
|
||||
|
||||
- **Keycloak(OIDC) auth config** — Authentik OIDC login for Rancher.
|
||||
- **Global role bindings** — Authentik group → Rancher global role.
|
||||
- **Users** — Rancher-local users and their global roles.
|
||||
- **Global roles** — custom Rancher global roles.
|
||||
- **Tokens** — API tokens for the identity Terraform authenticates as.
|
||||
- **Settings** — Rancher settings such as `server-url`.
|
||||
|
||||
## Configuration
|
||||
|
||||
Every kind is discovered from YAML by `config/config.hcl`: the file name is the
|
||||
object's key/name and the file body holds its attributes. Adding a file is the
|
||||
only step needed to manage a new object; an empty directory means an empty map.
|
||||
|
||||
`config/keycloakoidc.yaml` defines the auth provider. The OAuth client secret is
|
||||
read from Vault (kv-v2) — the same secret Authentik sets on its `rancher`
|
||||
provider — and is never committed.
|
||||
@@ -18,6 +27,76 @@ provider — and is never committed.
|
||||
roles are granted to users/groups separately. This avoids locking the admin out
|
||||
when the provider is first enabled.
|
||||
|
||||
### `config/global_role_bindings/<group>.yaml`
|
||||
|
||||
```yaml
|
||||
global_role_id: admin
|
||||
```
|
||||
|
||||
### `config/users/<username>.yaml`
|
||||
|
||||
```yaml
|
||||
name: Some Person # optional display name, defaults to the file name
|
||||
enabled: true # optional, default true
|
||||
must_change_password: false # optional, default false
|
||||
password_vault: # required — rancher2_user requires a password
|
||||
mount: kv
|
||||
path: service/rancher/users/someperson
|
||||
key: password # optional, default "password"
|
||||
global_role_bindings: # optional
|
||||
- user
|
||||
```
|
||||
|
||||
The password is read from Vault **at plan time**, so seed the kv-v2 secret
|
||||
before adding the file, or the plan fails.
|
||||
|
||||
The file name (the username) and every entry in `global_role_bindings` must be
|
||||
an RFC 1123 label — `^[a-z0-9]([a-z0-9-]*[a-z0-9])?$`, i.e. lowercase
|
||||
alphanumerics and `-`, starting and ending alphanumeric. No `.`, `_`, `@` or
|
||||
uppercase. Rancher names each binding `akuser-<username>-<role>` and rejects
|
||||
anything else, so non-compliant values fail the plan with an explicit error
|
||||
instead of the apply. Names that are individually valid but collapse to the same
|
||||
binding name (e.g. `foo-bar` + `baz` and `foo` + `bar-baz`) are rejected at plan
|
||||
time too.
|
||||
|
||||
### `config/roles/<name>.yaml`
|
||||
|
||||
```yaml
|
||||
description: Read-only across all clusters
|
||||
new_user_default: false # optional, default false
|
||||
inherited_cluster_roles: # optional
|
||||
- view
|
||||
rules: # optional
|
||||
- api_groups: ["management.cattle.io"]
|
||||
resources: ["clusters"]
|
||||
verbs: ["get", "list", "watch"]
|
||||
# non_resource_urls and resource_names are also supported
|
||||
```
|
||||
|
||||
### `config/tokens/<name>.yaml`
|
||||
|
||||
```yaml
|
||||
description: CI token # optional, defaults to the file name
|
||||
ttl: 7776000 # optional, seconds
|
||||
renew: true # optional
|
||||
cluster_id: c-m-abcdefgh # optional, scopes the token to one cluster
|
||||
```
|
||||
|
||||
> **Caveat:** `rancher2_token` has no user selector — `user_id` is computed by
|
||||
> the provider (14.1.1), not settable. Tokens declared here are minted for the
|
||||
> identity the rancher2 provider authenticates as (the admin token Terraform
|
||||
> runs with), **not** for users in `config/users/`. A token for another user has
|
||||
> to be created by that user. Token values land in Terraform state.
|
||||
|
||||
### `config/settings/<name>.yaml`
|
||||
|
||||
```yaml
|
||||
value: https://rancher.k8s.syd1.au.unkin.net
|
||||
```
|
||||
|
||||
Rancher ships defaults for its settings, so an entry takes over an existing
|
||||
setting rather than creating a new one.
|
||||
|
||||
## Usage
|
||||
|
||||
```sh
|
||||
@@ -28,13 +107,10 @@ make format # fmt tofu + terragrunt hcl
|
||||
|
||||
### Authentication
|
||||
|
||||
The rancher2 provider needs a Rancher admin API token, read from Vault at
|
||||
`kv/service/terraform/rancher` (field `token`).
|
||||
|
||||
> **Note:** Rancher API tokens have a **90-day maximum** lifetime, so the static
|
||||
> token must be rotated. This is intended to move to a dedicated Vault Rancher
|
||||
> secrets engine that mints short-lived tokens on demand; when that lands, update
|
||||
> the Makefile `vault_env` helper to `vault read` from that engine.
|
||||
The rancher2 provider needs a Rancher API token, minted on demand by the Vault
|
||||
Rancher secrets engine at `rancher/creds/ci`. The token is ephemeral (1h lease)
|
||||
and Vault revokes it in Rancher when the lease expires, so nothing needs
|
||||
rotating.
|
||||
|
||||
Set `VAULT_ROLEID` for local AppRole auth, or `VAULT_AUTH_METHOD=kubernetes`
|
||||
for CI (Woodpecker).
|
||||
|
||||
@@ -8,5 +8,30 @@ locals {
|
||||
|
||||
config = {
|
||||
keycloakoidc = try(local.all_configs["keycloakoidc.yaml"], null)
|
||||
global_role_bindings = {
|
||||
for file_path, content in local.all_configs :
|
||||
trimsuffix(basename(file_path), ".yaml") => content
|
||||
if startswith(file_path, "global_role_bindings/")
|
||||
}
|
||||
users = {
|
||||
for file_path, content in local.all_configs :
|
||||
trimsuffix(basename(file_path), ".yaml") => content
|
||||
if startswith(file_path, "users/")
|
||||
}
|
||||
roles = {
|
||||
for file_path, content in local.all_configs :
|
||||
trimsuffix(basename(file_path), ".yaml") => content
|
||||
if startswith(file_path, "roles/")
|
||||
}
|
||||
tokens = {
|
||||
for file_path, content in local.all_configs :
|
||||
trimsuffix(basename(file_path), ".yaml") => content
|
||||
if startswith(file_path, "tokens/")
|
||||
}
|
||||
settings = {
|
||||
for file_path, content in local.all_configs :
|
||||
trimsuffix(basename(file_path), ".yaml") => content
|
||||
if startswith(file_path, "settings/")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
# akP-rancher-admin -> Rancher global admin. Granted to akR-global-admin members
|
||||
# (inherited) and direct members.
|
||||
global_role_id: admin
|
||||
@@ -0,0 +1,3 @@
|
||||
# akP-rancher-user -> standard Rancher user global role. Granted to
|
||||
# akR-standard-user members (inherited) and direct members.
|
||||
global_role_id: user
|
||||
@@ -6,7 +6,10 @@ rancher_url: https://rancher.k8s.syd1.au.unkin.net/verify-auth
|
||||
client_id: rancher
|
||||
issuer: https://identity.unkin.net/application/o/rancher/
|
||||
auth_endpoint: https://identity.unkin.net/application/o/authorize/
|
||||
scopes: openid profile email
|
||||
# ak_groups = hierarchical group claim from terraform-authentik (permission
|
||||
# groups inherited via role groups). groups_field points Rancher at that claim.
|
||||
scopes: openid profile email ak_groups
|
||||
groups_field: ak_groups
|
||||
access_mode: unrestricted
|
||||
enabled: true
|
||||
client_secret_vault:
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
# Rancher's server-url setting. Must match the URL Rancher is reached on,
|
||||
# otherwise newly registered cluster agents dial the wrong endpoint.
|
||||
value: https://rancher.k8s.syd1.au.unkin.net
|
||||
@@ -17,5 +17,10 @@ terraform {
|
||||
}
|
||||
|
||||
inputs = {
|
||||
keycloakoidc = local.config.keycloakoidc
|
||||
keycloakoidc = local.config.keycloakoidc
|
||||
global_role_bindings = local.config.global_role_bindings
|
||||
users = local.config.users
|
||||
global_roles = local.config.roles
|
||||
tokens = local.config.tokens
|
||||
settings = local.config.settings
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ generate "backend" {
|
||||
if_exists = "overwrite"
|
||||
contents = <<EOF
|
||||
provider "rancher2" {
|
||||
api_url = "https://${path_relative_to_include()}"
|
||||
api_url = var.rancher_url
|
||||
token_key = var.rancher_token
|
||||
}
|
||||
|
||||
@@ -20,6 +20,13 @@ variable "rancher_token" {
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
# Defaults to the environment's public URL; an exported TF_VAR_rancher_url
|
||||
# takes precedence over terragrunt inputs.
|
||||
variable "rancher_url" {
|
||||
type = string
|
||||
default = "https://${path_relative_to_include()}"
|
||||
}
|
||||
|
||||
terraform {
|
||||
backend "consul" {
|
||||
address = "https://consul.service.consul"
|
||||
|
||||
@@ -5,6 +5,16 @@ data "vault_kv_secret_v2" "keycloakoidc" {
|
||||
name = var.keycloakoidc.client_secret_vault.path
|
||||
}
|
||||
|
||||
# Read the internal PKI CA chain (intermediate + root) from Vault. Rancher makes
|
||||
# its OIDC discovery call to https://identity.unkin.net server-side; without the
|
||||
# internal CA it fails with x509 "certificate signed by unknown authority". The
|
||||
# ca_chain field is public/non-sensitive. Sourced from Vault (not hardcoded) so
|
||||
# the trust never goes stale on rotation.
|
||||
data "vault_generic_secret" "internal_ca" {
|
||||
count = var.keycloakoidc != null ? 1 : 0
|
||||
path = "pki_int/cert/ca_chain"
|
||||
}
|
||||
|
||||
# Rancher's Keycloak(OIDC) auth provider, pointed at Authentik. access_mode
|
||||
# "unrestricted" means any authenticated Authentik user can log in; Rancher
|
||||
# roles are granted to users/groups separately (avoids admin lockout on enable).
|
||||
@@ -17,6 +27,131 @@ resource "rancher2_auth_config_keycloak_oidc" "this" {
|
||||
issuer = var.keycloakoidc.issuer
|
||||
auth_endpoint = var.keycloakoidc.auth_endpoint
|
||||
scopes = var.keycloakoidc.scopes
|
||||
groups_field = var.keycloakoidc.groups_field
|
||||
access_mode = var.keycloakoidc.access_mode
|
||||
enabled = var.keycloakoidc.enabled
|
||||
|
||||
# CA cert Rancher uses to trust the IdP's TLS during OIDC discovery. Defaults
|
||||
# to the internal PKI chain from Vault; an explicit config value overrides it.
|
||||
certificate = coalesce(var.keycloakoidc.certificate, data.vault_generic_secret.internal_ca[0].data["ca_chain"])
|
||||
}
|
||||
|
||||
# Grant Rancher global roles to Authentik permission groups. The keycloak_oidc
|
||||
# group principal id is keycloakoidc_group://<group name>. Members of a role
|
||||
# group (e.g. akR-global-admin) inherit the permission group, so they receive
|
||||
# the bound global role.
|
||||
resource "rancher2_global_role_binding" "group" {
|
||||
for_each = var.global_role_bindings
|
||||
|
||||
# Binding name must be an RFC 1123 label (lowercase); the group name may be
|
||||
# mixed-case (akP-*), so lowercase it here. The principal id keeps the original
|
||||
# case to match the group.
|
||||
name = "akgroup-${lower(each.key)}"
|
||||
global_role_id = each.value.global_role_id
|
||||
group_principal_id = "keycloakoidc_group://${each.key}"
|
||||
|
||||
depends_on = [rancher2_auth_config_keycloak_oidc.this]
|
||||
}
|
||||
|
||||
# Rancher-local users. rancher2_user makes `password` required, so each user's
|
||||
# password comes from a Vault kv-v2 secret rather than the repo. Vault data
|
||||
# sources resolve at plan time: seed the secret before adding the yaml.
|
||||
data "vault_kv_secret_v2" "user_password" {
|
||||
for_each = var.users
|
||||
|
||||
mount = each.value.password_vault.mount
|
||||
name = each.value.password_vault.path
|
||||
}
|
||||
|
||||
resource "rancher2_user" "this" {
|
||||
for_each = var.users
|
||||
|
||||
username = each.key
|
||||
name = coalesce(each.value.name, each.key)
|
||||
enabled = each.value.enabled
|
||||
must_change_password = each.value.must_change_password
|
||||
password = data.vault_kv_secret_v2.user_password[each.key].data[each.value.password_vault.key]
|
||||
}
|
||||
|
||||
# Flatten users -> their global roles into "<username>/<role>" keys so adding or
|
||||
# removing one role never re-indexes the others.
|
||||
locals {
|
||||
user_global_role_bindings = merge([
|
||||
for username, user in var.users : {
|
||||
for role in user.global_role_bindings :
|
||||
"${username}/${role}" => {
|
||||
username = username
|
||||
global_role_id = role
|
||||
}
|
||||
}
|
||||
]...)
|
||||
}
|
||||
|
||||
resource "rancher2_global_role_binding" "user" {
|
||||
for_each = local.user_global_role_bindings
|
||||
|
||||
# Both parts are RFC 1123 labels already (var.users validates them), so the
|
||||
# binding name needs no transformation.
|
||||
name = "akuser-${each.value.username}-${each.value.global_role_id}"
|
||||
global_role_id = each.value.global_role_id
|
||||
user_id = rancher2_user.this[each.value.username].id
|
||||
|
||||
lifecycle {
|
||||
# Hyphens are legal inside both parts, so distinct bindings can still flatten
|
||||
# to the same name (foo-bar + baz vs foo + bar-baz). Catch that at plan time
|
||||
# rather than as a duplicate-object conflict mid-apply.
|
||||
precondition {
|
||||
condition = length(distinct([
|
||||
for binding in local.user_global_role_bindings :
|
||||
"akuser-${binding.username}-${binding.global_role_id}"
|
||||
])) == length(local.user_global_role_bindings)
|
||||
error_message = "Two user global role bindings collapse to the same Rancher binding name (akuser-<username>-<role>). Rename one of the users or roles involved."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Custom global roles. Rules are optional: a role with none grants nothing on its
|
||||
# own and is useful purely as a container for inherited_cluster_roles.
|
||||
resource "rancher2_global_role" "this" {
|
||||
for_each = var.global_roles
|
||||
|
||||
name = each.key
|
||||
description = each.value.description
|
||||
new_user_default = each.value.new_user_default
|
||||
inherited_cluster_roles = each.value.inherited_cluster_roles
|
||||
|
||||
dynamic "rules" {
|
||||
for_each = each.value.rules
|
||||
content {
|
||||
api_groups = rules.value.api_groups
|
||||
resources = rules.value.resources
|
||||
verbs = rules.value.verbs
|
||||
non_resource_urls = rules.value.non_resource_urls
|
||||
resource_names = rules.value.resource_names
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# CAVEAT: rancher2_token has no user selector — `user_id` is computed by the
|
||||
# provider at 14.1.1, not settable. Every token declared here is minted for the
|
||||
# identity the rancher2 provider authenticates as (the CI admin service account),
|
||||
# NOT for any user in config/users/. There is no way to mint a token on another
|
||||
# user's behalf through this provider; that has to be done by that user.
|
||||
# The token/secret_key values land in Terraform state, so treat state as secret.
|
||||
resource "rancher2_token" "this" {
|
||||
for_each = var.tokens
|
||||
|
||||
description = coalesce(each.value.description, each.key)
|
||||
ttl = each.value.ttl
|
||||
renew = each.value.renew
|
||||
cluster_id = each.value.cluster_id
|
||||
}
|
||||
|
||||
# Rancher settings, e.g. server-url. Rancher ships defaults for these, so an
|
||||
# entry here takes over an existing setting rather than creating a new one.
|
||||
resource "rancher2_setting" "this" {
|
||||
for_each = var.settings
|
||||
|
||||
name = each.key
|
||||
value = each.value.value
|
||||
}
|
||||
|
||||
@@ -6,8 +6,13 @@ variable "keycloakoidc" {
|
||||
issuer = string # OIDC issuer (Authentik application URL)
|
||||
auth_endpoint = string # OIDC authorization endpoint
|
||||
scopes = optional(string, "openid profile email")
|
||||
access_mode = optional(string, "unrestricted")
|
||||
enabled = optional(bool, true)
|
||||
# OIDC claim to read group names from (default Rancher uses "groups").
|
||||
groups_field = optional(string, "groups")
|
||||
access_mode = optional(string, "unrestricted")
|
||||
enabled = optional(bool, true)
|
||||
# PEM CA cert Rancher trusts for the IdP's TLS on OIDC discovery. Leave null
|
||||
# to default to the internal PKI ca_chain read from Vault.
|
||||
certificate = optional(string, null)
|
||||
# client_secret is never committed. Point at a Vault kv-v2 secret whose
|
||||
# `client_secret` key holds the value (seeded out of band); TF reads it.
|
||||
client_secret_vault = optional(object({
|
||||
@@ -17,3 +22,94 @@ variable "keycloakoidc" {
|
||||
})
|
||||
default = null
|
||||
}
|
||||
|
||||
# Global role bindings mapping an Authentik group (permission group) to a Rancher
|
||||
# global role. The group name is the map key; the OIDC group principal id is
|
||||
# derived as keycloakoidc_group://<name>.
|
||||
variable "global_role_bindings" {
|
||||
type = map(object({
|
||||
global_role_id = string # e.g. "admin", "user"
|
||||
}))
|
||||
default = {}
|
||||
}
|
||||
|
||||
# Rancher-local users (config/users/<username>.yaml). Map key is the username.
|
||||
# rancher2_user requires a password, so every entry must point at a Vault kv-v2
|
||||
# secret holding one; the value is read at plan time, so the secret must exist
|
||||
# before a user yaml is added.
|
||||
variable "users" {
|
||||
type = map(object({
|
||||
name = optional(string, null) # display name, defaults to the username
|
||||
enabled = optional(bool, true)
|
||||
must_change_password = optional(bool, false)
|
||||
password_vault = object({
|
||||
mount = string
|
||||
path = string
|
||||
key = optional(string, "password")
|
||||
})
|
||||
# Global roles granted to this user, e.g. ["user", "admin"].
|
||||
global_role_bindings = optional(list(string), [])
|
||||
}))
|
||||
default = {}
|
||||
|
||||
# Rancher names each global role binding as an RFC 1123 label and rejects
|
||||
# anything else at apply time. The binding name is built from the username and
|
||||
# the role name verbatim, so both must already be compliant — fail the plan
|
||||
# with a clear message instead of munging the input and hoping.
|
||||
validation {
|
||||
condition = alltrue([
|
||||
for username in keys(var.users) :
|
||||
can(regex("^[a-z0-9]([a-z0-9-]*[a-z0-9])?$", username))
|
||||
])
|
||||
error_message = "Usernames must be RFC 1123 labels: lowercase alphanumerics and '-', starting and ending alphanumeric (no '.', '_', '@' or uppercase). Rename config/users/<username>.yaml to a compliant username."
|
||||
}
|
||||
|
||||
validation {
|
||||
condition = alltrue(flatten([
|
||||
for user in values(var.users) : [
|
||||
for role in user.global_role_bindings :
|
||||
can(regex("^[a-z0-9]([a-z0-9-]*[a-z0-9])?$", role))
|
||||
]
|
||||
]))
|
||||
error_message = "Values in global_role_bindings must be RFC 1123 labels: lowercase alphanumerics and '-', starting and ending alphanumeric."
|
||||
}
|
||||
}
|
||||
|
||||
# Custom Rancher global roles (config/roles/<name>.yaml). Map key is the role
|
||||
# name, which is also the id other configs bind to via global_role_id.
|
||||
variable "global_roles" {
|
||||
type = map(object({
|
||||
description = optional(string, null)
|
||||
new_user_default = optional(bool, false)
|
||||
inherited_cluster_roles = optional(list(string), [])
|
||||
rules = optional(list(object({
|
||||
api_groups = optional(list(string), [])
|
||||
resources = optional(list(string), [])
|
||||
verbs = optional(list(string), [])
|
||||
non_resource_urls = optional(list(string), [])
|
||||
resource_names = optional(list(string), [])
|
||||
})), [])
|
||||
}))
|
||||
default = {}
|
||||
}
|
||||
|
||||
# API tokens (config/tokens/<name>.yaml). Map key names the Terraform resource
|
||||
# only. rancher2_token has no user selector (user_id is computed at 14.1.1), so
|
||||
# every token here is minted for the identity the provider authenticates as.
|
||||
variable "tokens" {
|
||||
type = map(object({
|
||||
description = optional(string, null) # defaults to the map key
|
||||
ttl = optional(number, null) # seconds; 0/null = provider default
|
||||
renew = optional(bool, null)
|
||||
cluster_id = optional(string, null) # scope to one cluster, null = global
|
||||
}))
|
||||
default = {}
|
||||
}
|
||||
|
||||
# Rancher settings (config/settings/<name>.yaml). Map key is the setting name.
|
||||
variable "settings" {
|
||||
type = map(object({
|
||||
value = string
|
||||
}))
|
||||
default = {}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ terraform {
|
||||
required_providers {
|
||||
rancher2 = {
|
||||
source = "rancher/rancher2"
|
||||
version = ">= 5.0.0"
|
||||
version = "~> 14.0"
|
||||
}
|
||||
vault = {
|
||||
source = "hashicorp/vault"
|
||||
|
||||
Reference in New Issue
Block a user