187e29d068
Rancher requires global role binding names to be RFC 1123 labels. The per-user binding name lowercased the key and replaced only "/", so usernames containing ".", "_" or "@" still produced an invalid name and failed at apply, and hyphenated names could collide (foo-bar + baz vs foo + bar-baz) into one object. Add validation blocks on var.users requiring the username and every referenced role name to be RFC 1123 labels, so non-compliant input fails the plan with an actionable message. The binding name is then built from the two parts directly. A precondition rejects the remaining hyphen ambiguity at plan time rather than as a mid-apply conflict. Document the constraint in the users schema section of the README. The group binding path is unchanged.
116 lines
4.6 KiB
Terraform
116 lines
4.6 KiB
Terraform
variable "keycloakoidc" {
|
|
description = "Rancher Keycloak(OIDC) auth provider config, or null to leave unmanaged."
|
|
type = object({
|
|
rancher_url = string # Rancher redirect URL, must end with /verify-auth
|
|
client_id = string
|
|
issuer = string # OIDC issuer (Authentik application URL)
|
|
auth_endpoint = string # OIDC authorization endpoint
|
|
scopes = optional(string, "openid profile email")
|
|
# 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({
|
|
mount = string
|
|
path = string
|
|
}), null)
|
|
})
|
|
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 = {}
|
|
}
|