Scaffold terraform-rancher: Authentik OIDC auth config
Manages Rancher's Keycloak(OIDC) auth provider via the rancher2 provider, pointed at Authentik. Mirrors the terraform-authentik layout (terragrunt + Vault-sourced secrets + Woodpecker plan/apply/pre-commit pipelines). - modules/rancher: rancher2_auth_config_keycloak_oidc, client_secret read from Vault (kv/kubernetes/namespace/cattle-system/default/oauth-credentials); access_mode unrestricted to avoid admin lockout on enable. - config/keycloakoidc.yaml: issuer/auth_endpoint at identity.unkin.net, client_id rancher, /verify-auth redirect, openid/profile/email scopes. - environments/rancher.k8s.syd1.au.unkin.net: consul state at infra/terraform/rancher/, rancher2 provider api_url from the env name. - rancher2 admin token read from kv/service/terraform/rancher (Makefile); to migrate to a dedicated Vault Rancher secrets engine (90-day token cap). Validated with `tofu validate` (config valid against the rancher2 provider). A live `plan` needs the Rancher admin API token seeded in Vault first.
This commit is contained in:
@@ -0,0 +1,6 @@
|
|||||||
|
.terraform/
|
||||||
|
*.tfstate
|
||||||
|
*.tfstate.backup
|
||||||
|
*.tfplan
|
||||||
|
backend.tf
|
||||||
|
.terragrunt-cache/
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
repos:
|
||||||
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
|
rev: v4.4.0
|
||||||
|
hooks:
|
||||||
|
- id: end-of-file-fixer
|
||||||
|
types: [yaml]
|
||||||
|
- id: trailing-whitespace
|
||||||
|
types: [yaml]
|
||||||
|
- repo: https://github.com/gruntwork-io/pre-commit
|
||||||
|
rev: v0.1.30
|
||||||
|
hooks:
|
||||||
|
- id: tofu-fmt
|
||||||
|
- id: tofu-validate
|
||||||
|
- id: tflint
|
||||||
|
- id: terragrunt-hcl-fmt
|
||||||
|
- repo: https://github.com/adrienverge/yamllint.git
|
||||||
|
rev: v1.37.1
|
||||||
|
hooks:
|
||||||
|
- id: yamllint
|
||||||
|
args:
|
||||||
|
[
|
||||||
|
"-d {extends: relaxed, rules: {line-length: disable}, ignore: chart}",
|
||||||
|
"-s",
|
||||||
|
]
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
when:
|
||||||
|
- event: push
|
||||||
|
branch: main
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: apply
|
||||||
|
image: git.unkin.net/unkin/almalinux9-opentofu:20260606
|
||||||
|
environment:
|
||||||
|
VAULT_AUTH_METHOD: kubernetes
|
||||||
|
commands:
|
||||||
|
- dnf install vault -y
|
||||||
|
- make plan
|
||||||
|
- make apply
|
||||||
|
backend_options:
|
||||||
|
kubernetes:
|
||||||
|
serviceAccountName: terraform-rancher
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
memory: 512Mi
|
||||||
|
cpu: 1
|
||||||
|
limits:
|
||||||
|
memory: 2Gi
|
||||||
|
cpu: 2
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
when:
|
||||||
|
- event: pull_request
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: plan
|
||||||
|
image: git.unkin.net/unkin/almalinux9-opentofu:20260606
|
||||||
|
environment:
|
||||||
|
VAULT_AUTH_METHOD: kubernetes
|
||||||
|
commands:
|
||||||
|
- dnf install vault -y
|
||||||
|
- make plan
|
||||||
|
backend_options:
|
||||||
|
kubernetes:
|
||||||
|
serviceAccountName: terraform-rancher
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
memory: 512Mi
|
||||||
|
cpu: 1
|
||||||
|
limits:
|
||||||
|
memory: 2Gi
|
||||||
|
cpu: 2
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
when:
|
||||||
|
- event: pull_request
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: pre-commit
|
||||||
|
image: git.unkin.net/unkin/almalinux9-opentofu:20260606
|
||||||
|
commands:
|
||||||
|
- uvx pre-commit run --all-files
|
||||||
|
backend_options:
|
||||||
|
kubernetes:
|
||||||
|
serviceAccountName: default
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
memory: 512Mi
|
||||||
|
cpu: 1
|
||||||
|
limits:
|
||||||
|
memory: 2Gi
|
||||||
|
cpu: 2
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
.PHONY: init plan apply format
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
define vault_env
|
||||||
|
@export VAULT_ADDR="https://vault.service.consul:8200" && \
|
||||||
|
if [ "$(VAULT_AUTH_METHOD)" = "kubernetes" ]; then \
|
||||||
|
export VAULT_TOKEN=$$(vault write -field=token $(VAULT_K8S_MOUNT)/login role=$(VAULT_K8S_ROLE) jwt=$$(cat $(VAULT_K8S_JWT_PATH))); \
|
||||||
|
else \
|
||||||
|
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))
|
||||||
|
endef
|
||||||
|
|
||||||
|
init:
|
||||||
|
@$(call vault_env) && \
|
||||||
|
terragrunt run --all --non-interactive init -- -upgrade
|
||||||
|
|
||||||
|
plan: init
|
||||||
|
@$(call vault_env) && \
|
||||||
|
terragrunt run --all --parallelism 4 --non-interactive plan
|
||||||
|
|
||||||
|
apply: init
|
||||||
|
@$(call vault_env) && \
|
||||||
|
terragrunt run --all --parallelism 2 --non-interactive apply
|
||||||
|
|
||||||
|
format:
|
||||||
|
@echo "Formatting OpenTofu files..."
|
||||||
|
@tofu fmt -recursive .
|
||||||
|
@echo "Formatting Terragrunt files..."
|
||||||
|
@terragrunt hcl fmt
|
||||||
@@ -1,3 +1,40 @@
|
|||||||
# terraform-rancher
|
# terraform-rancher
|
||||||
|
|
||||||
Terraform configuration for managing Rancher (auth, roles) via the rancher2 provider
|
Terraform configuration for managing Rancher (rancher.k8s.syd1.au.unkin.net)
|
||||||
|
authentication via the [rancher2](https://registry.terraform.io/providers/rancher/rancher2)
|
||||||
|
provider. Mirrors the `terraform-authentik` pattern.
|
||||||
|
|
||||||
|
## Managed Resources
|
||||||
|
|
||||||
|
- **Keycloak(OIDC) auth config** — Authentik OIDC login for Rancher.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
`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.
|
||||||
|
|
||||||
|
`access_mode: unrestricted` lets any authenticated Authentik user log in; Rancher
|
||||||
|
roles are granted to users/groups separately. This avoids locking the admin out
|
||||||
|
when the provider is first enabled.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```sh
|
||||||
|
make plan # init + plan
|
||||||
|
make apply # init + plan + apply
|
||||||
|
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.
|
||||||
|
|
||||||
|
Set `VAULT_ROLEID` for local AppRole auth, or `VAULT_AUTH_METHOD=kubernetes`
|
||||||
|
for CI (Woodpecker).
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
locals {
|
||||||
|
config_files = fileset(".", "**/*.yaml")
|
||||||
|
|
||||||
|
all_configs = {
|
||||||
|
for file_path in local.config_files :
|
||||||
|
file_path => yamldecode(file(file_path))
|
||||||
|
}
|
||||||
|
|
||||||
|
config = {
|
||||||
|
keycloakoidc = try(local.all_configs["keycloakoidc.yaml"], null)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# Rancher Keycloak(OIDC) auth provider backed by Authentik.
|
||||||
|
# client_secret is read from Vault (the same secret Authentik sets on its
|
||||||
|
# provider), not committed. access_mode "unrestricted" avoids admin lockout:
|
||||||
|
# any Authentik user can authenticate; Rancher roles are assigned separately.
|
||||||
|
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
|
||||||
|
access_mode: unrestricted
|
||||||
|
enabled: true
|
||||||
|
client_secret_vault:
|
||||||
|
mount: kv
|
||||||
|
path: kubernetes/namespace/cattle-system/default/oauth-credentials
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
include "root" {
|
||||||
|
path = find_in_parent_folders("root.hcl")
|
||||||
|
expose = true
|
||||||
|
}
|
||||||
|
|
||||||
|
include "config" {
|
||||||
|
path = "${get_repo_root()}/config/config.hcl"
|
||||||
|
expose = true
|
||||||
|
}
|
||||||
|
|
||||||
|
locals {
|
||||||
|
config = include.config.locals.config
|
||||||
|
}
|
||||||
|
|
||||||
|
terraform {
|
||||||
|
source = "../../modules/rancher"
|
||||||
|
}
|
||||||
|
|
||||||
|
inputs = {
|
||||||
|
keycloakoidc = local.config.keycloakoidc
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
generate "backend" {
|
||||||
|
path = "backend.tf"
|
||||||
|
if_exists = "overwrite"
|
||||||
|
contents = <<EOF
|
||||||
|
provider "rancher2" {
|
||||||
|
api_url = "https://${path_relative_to_include()}"
|
||||||
|
token_key = var.rancher_token
|
||||||
|
}
|
||||||
|
|
||||||
|
# Reads the OAuth client secret seeded in Vault (kv-v2). Auth via VAULT_ADDR +
|
||||||
|
# VAULT_TOKEN from the environment (set by the Makefile vault_env helper).
|
||||||
|
# skip_child_token is required because the short-lived CI token cannot create
|
||||||
|
# child tokens.
|
||||||
|
provider "vault" {
|
||||||
|
skip_child_token = true
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "rancher_token" {
|
||||||
|
type = string
|
||||||
|
sensitive = true
|
||||||
|
}
|
||||||
|
|
||||||
|
terraform {
|
||||||
|
backend "consul" {
|
||||||
|
address = "https://consul.service.consul"
|
||||||
|
path = "infra/terraform/rancher/${path_relative_to_include()}/state"
|
||||||
|
scheme = "https"
|
||||||
|
lock = true
|
||||||
|
ca_file = "/etc/pki/tls/certs/ca-bundle.crt"
|
||||||
|
}
|
||||||
|
required_version = ">= 1.10"
|
||||||
|
# required_providers are declared in the module's versions.tf.
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
}
|
||||||
Generated
+43
@@ -0,0 +1,43 @@
|
|||||||
|
# This file is maintained automatically by "tofu init".
|
||||||
|
# Manual edits may be lost in future updates.
|
||||||
|
|
||||||
|
provider "registry.opentofu.org/hashicorp/vault" {
|
||||||
|
version = "5.10.1"
|
||||||
|
constraints = ">= 4.0.0"
|
||||||
|
hashes = [
|
||||||
|
"h1:wo5cTkl/1nlxMfdn1yEDIHNoRLMczuK6COH2Id4/zeY=",
|
||||||
|
"zh:0abf976c01f0c0732d0ccc6481e52008be5ee9c8e3d9b5eba0573c640fcf7019",
|
||||||
|
"zh:2aff4d7ee7ba9eb3de2cd5cda16ba92b4ec7a2b43232aec180984241a323b216",
|
||||||
|
"zh:2cc186fd0bfc44e100a22b0b40ae8ddcd0ec210a53c1da65d310ee758b1d2b08",
|
||||||
|
"zh:3f8fb8594736b34af4b26437dd4df4dd4042ad4905223995cfebb8a1f10682ec",
|
||||||
|
"zh:47fb41b18b74073f557dbcd6aad2183e416293405ccd70c0691a279cfe97f8cd",
|
||||||
|
"zh:517e2f2764d671c22d22def0384fdfc521b456458189189c0363375495d114dc",
|
||||||
|
"zh:5a49a2003636f2b8a547d494a6c06d43d62a68299775305408c52eff22b1c11f",
|
||||||
|
"zh:66d4e716920ada84b0c768f4aca4c8948388995462923349a01bd3818d82b618",
|
||||||
|
"zh:7599f652e89a3f18fa4b76a59d115cc63255cc36ce6b273850509ba25031abca",
|
||||||
|
"zh:9c3e38ae7e670de973b6255d7050f526cd2b3ca7c383d7ba7226fc204d97c507",
|
||||||
|
"zh:d04b046023fa9fd69def678f27e001c298ea34fc99ba51f835cda82e496fdb57",
|
||||||
|
"zh:d9acd8810f6660cd51bb4c25596632984ae18e93340c82a102d074c6eac95151",
|
||||||
|
"zh:e161bcb9a22607270b980eeff2ba693335fb62d6978516dff93dd4c91cda99b3",
|
||||||
|
"zh:ef47502f08cfcb5311b7b16a7905e0052bf28359e07cc00ed080ef454e0946cf",
|
||||||
|
"zh:f0640ddb52e7e90c5006ff571f6ad0554e593665320c764c57a3d8b7ec31b490",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
provider "registry.opentofu.org/rancher/rancher2" {
|
||||||
|
version = "14.1.1"
|
||||||
|
constraints = ">= 5.0.0"
|
||||||
|
hashes = [
|
||||||
|
"h1:XguC/YWN5PuuX+oQ9U7HZdrpLZ/z32XFTmWcTZTO/AY=",
|
||||||
|
"zh:11c4c34ba996b633d116e83f48b0baecaa1e0cbf50562abdfd9424cd6f6141a4",
|
||||||
|
"zh:205a3a4b5f078b2eeea06f4a13ab15bcf34864460f789568d384a03e47f0a009",
|
||||||
|
"zh:2dc7df1fc9c6bb23ca9b0be326aac125212d525cde029a3c44c943f82e07c59f",
|
||||||
|
"zh:50752ce808ff054c36e7a594bec9807299f3dbe2febf5ffa501239aa5a4b3125",
|
||||||
|
"zh:515d787138ba9a750506ccc45f95a707ccf6bd2601e6bcc732e39f18e1436837",
|
||||||
|
"zh:8b8ae8881596dc865e3998701ff0048ace2670a73c6ea990dcee0ea9ea8006bc",
|
||||||
|
"zh:a54086f41c125b7460b8cf1b588f778b8e971a131a8713cd12806e3724e198a8",
|
||||||
|
"zh:b346815aac1ba419e12210fc03da57cc8606366a5817c6fbb7ce84bc26a65ef0",
|
||||||
|
"zh:c8b5fae14df8195c3ab21781913f929d3db2f4576d9b6ed5353b663d9f6b7940",
|
||||||
|
"zh:d990f1b187d1c6779d38a2620c685b2c3efc79be4d603ed6b041ecbdfcc1e79b",
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# Read the OAuth client secret from Vault so nothing sensitive is committed.
|
||||||
|
data "vault_kv_secret_v2" "keycloakoidc" {
|
||||||
|
count = var.keycloakoidc != null && var.keycloakoidc.client_secret_vault != null ? 1 : 0
|
||||||
|
mount = var.keycloakoidc.client_secret_vault.mount
|
||||||
|
name = var.keycloakoidc.client_secret_vault.path
|
||||||
|
}
|
||||||
|
|
||||||
|
# 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).
|
||||||
|
resource "rancher2_auth_config_keycloak_oidc" "this" {
|
||||||
|
count = var.keycloakoidc != null ? 1 : 0
|
||||||
|
|
||||||
|
rancher_url = var.keycloakoidc.rancher_url
|
||||||
|
client_id = var.keycloakoidc.client_id
|
||||||
|
client_secret = data.vault_kv_secret_v2.keycloakoidc[0].data["client_secret"]
|
||||||
|
issuer = var.keycloakoidc.issuer
|
||||||
|
auth_endpoint = var.keycloakoidc.auth_endpoint
|
||||||
|
scopes = var.keycloakoidc.scopes
|
||||||
|
access_mode = var.keycloakoidc.access_mode
|
||||||
|
enabled = var.keycloakoidc.enabled
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
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")
|
||||||
|
access_mode = optional(string, "unrestricted")
|
||||||
|
enabled = optional(bool, true)
|
||||||
|
# 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
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
terraform {
|
||||||
|
required_version = ">= 1.10"
|
||||||
|
required_providers {
|
||||||
|
rancher2 = {
|
||||||
|
source = "rancher/rancher2"
|
||||||
|
version = ">= 5.0.0"
|
||||||
|
}
|
||||||
|
vault = {
|
||||||
|
source = "hashicorp/vault"
|
||||||
|
version = ">= 4.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user