feat(k8s-dashboard): add Authentik OIDC app for dashboard SSO
Confidential client k8s-dashboard + custom scope mapping emitting aud=[kubernetes,k8s-dashboard] + group-restriction policy (kubernetes-* RBAC groups). Additive — dashboard ingress unchanged. Token via Vault secret/k8s-dashboard. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
549320f79c
commit
011c63c92d
3 changed files with 152 additions and 0 deletions
24
stacks/k8s-dashboard/.terraform.lock.hcl
generated
24
stacks/k8s-dashboard/.terraform.lock.hcl
generated
|
|
@ -24,6 +24,22 @@ provider "registry.terraform.io/cloudflare/cloudflare" {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
provider "registry.terraform.io/gavinbunney/kubectl" {
|
||||||
|
version = "1.19.0"
|
||||||
|
constraints = "~> 1.14"
|
||||||
|
hashes = [
|
||||||
|
"h1:9QkxPjp0x5FZFfJbE+B7hBOoads9gmdfj9aYu5N4Sfc=",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
provider "registry.terraform.io/goauthentik/authentik" {
|
||||||
|
version = "2024.12.1"
|
||||||
|
constraints = "~> 2024.10"
|
||||||
|
hashes = [
|
||||||
|
"h1:roBMd+gi+TGgikH/bMzEI8JfvJiMAQWt+8FmokCrQIs=",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
provider "registry.terraform.io/hashicorp/helm" {
|
provider "registry.terraform.io/hashicorp/helm" {
|
||||||
version = "3.1.1"
|
version = "3.1.1"
|
||||||
hashes = [
|
hashes = [
|
||||||
|
|
@ -91,3 +107,11 @@ provider "registry.terraform.io/hashicorp/vault" {
|
||||||
"zh:ff35fb1ab6add288f0f368981e56f780b50405accd1937131cba1137999c8d83",
|
"zh:ff35fb1ab6add288f0f368981e56f780b50405accd1937131cba1137999c8d83",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
provider "registry.terraform.io/telmate/proxmox" {
|
||||||
|
version = "3.0.2-rc07"
|
||||||
|
constraints = "3.0.2-rc07"
|
||||||
|
hashes = [
|
||||||
|
"h1:zp5hpQJQ4t4zROSLqdltVpBO+Riy9VugtfFbpyTw1aM=",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
|
||||||
108
stacks/k8s-dashboard/authentik.tf
Normal file
108
stacks/k8s-dashboard/authentik.tf
Normal file
|
|
@ -0,0 +1,108 @@
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Authentik OIDC application for the Kubernetes Dashboard (via oauth2-proxy).
|
||||||
|
#
|
||||||
|
# Confidential client `k8s-dashboard`. A custom scope mapping emits
|
||||||
|
# aud = ["kubernetes","k8s-dashboard"] so BOTH the kube-apiserver
|
||||||
|
# (--oidc-client-id=kubernetes) and oauth2-proxy (client_id=k8s-dashboard)
|
||||||
|
# accept the id_token. The existing UI-managed `kubernetes` public client
|
||||||
|
# used by the kubelogin CLI is untouched.
|
||||||
|
#
|
||||||
|
# Provider token: Vault secret/authentik -> tf_api_token (same as
|
||||||
|
# stacks/authentik/authentik_provider.tf).
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
data "vault_kv_secret_v2" "authentik_tf" {
|
||||||
|
mount = "secret"
|
||||||
|
name = "authentik"
|
||||||
|
}
|
||||||
|
|
||||||
|
provider "authentik" {
|
||||||
|
url = "https://authentik.viktorbarzin.me"
|
||||||
|
token = data.vault_kv_secret_v2.authentik_tf.data["tf_api_token"]
|
||||||
|
}
|
||||||
|
|
||||||
|
data "vault_kv_secret_v2" "k8s_dashboard" {
|
||||||
|
mount = "secret"
|
||||||
|
name = "k8s-dashboard"
|
||||||
|
}
|
||||||
|
|
||||||
|
data "authentik_flow" "default_authorization_implicit_consent" {
|
||||||
|
slug = "default-provider-authorization-implicit-consent"
|
||||||
|
}
|
||||||
|
|
||||||
|
data "authentik_flow" "default_provider_invalidation" {
|
||||||
|
slug = "default-provider-invalidation-flow"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Default OIDC scope mappings. `profile` carries the `groups` claim in
|
||||||
|
# Authentik's default expression, which the apiserver reads via
|
||||||
|
# --oidc-groups-claim=groups. offline_access enables refresh tokens.
|
||||||
|
data "authentik_property_mapping_provider_scope" "defaults" {
|
||||||
|
managed_list = [
|
||||||
|
"goauthentik.io/providers/oauth2/scope-openid",
|
||||||
|
"goauthentik.io/providers/oauth2/scope-email",
|
||||||
|
"goauthentik.io/providers/oauth2/scope-profile",
|
||||||
|
"goauthentik.io/providers/oauth2/scope-offline_access",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Custom scope mapping that overrides the audience. It only fires when the
|
||||||
|
# client REQUESTS this scope, so oauth2-proxy must include
|
||||||
|
# `k8s-dashboard-audience` in its --scope (see oauth2_proxy.tf).
|
||||||
|
resource "authentik_property_mapping_provider_scope" "k8s_dashboard_aud" {
|
||||||
|
name = "k8s-dashboard audience"
|
||||||
|
scope_name = "k8s-dashboard-audience"
|
||||||
|
expression = "return {\"aud\": [\"kubernetes\", \"k8s-dashboard\"]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "authentik_provider_oauth2" "k8s_dashboard" {
|
||||||
|
name = "k8s-dashboard"
|
||||||
|
client_id = data.vault_kv_secret_v2.k8s_dashboard.data["oauth2_proxy_client_id"]
|
||||||
|
client_secret = data.vault_kv_secret_v2.k8s_dashboard.data["oauth2_proxy_client_secret"]
|
||||||
|
client_type = "confidential"
|
||||||
|
|
||||||
|
authorization_flow = data.authentik_flow.default_authorization_implicit_consent.id
|
||||||
|
invalidation_flow = data.authentik_flow.default_provider_invalidation.id
|
||||||
|
|
||||||
|
allowed_redirect_uris = [
|
||||||
|
{
|
||||||
|
matching_mode = "strict"
|
||||||
|
url = "https://k8s.viktorbarzin.me/oauth2/callback"
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
access_token_validity = "hours=1"
|
||||||
|
refresh_token_validity = "days=30"
|
||||||
|
include_claims_in_id_token = true
|
||||||
|
|
||||||
|
property_mappings = concat(
|
||||||
|
data.authentik_property_mapping_provider_scope.defaults.ids,
|
||||||
|
[authentik_property_mapping_provider_scope.k8s_dashboard_aud.id],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "authentik_application" "k8s_dashboard" {
|
||||||
|
name = "Kubernetes Dashboard"
|
||||||
|
slug = "k8s-dashboard"
|
||||||
|
protocol_provider = authentik_provider_oauth2.k8s_dashboard.id
|
||||||
|
meta_launch_url = "https://k8s.viktorbarzin.me"
|
||||||
|
policy_engine_mode = "any"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Restrict who can complete the OIDC flow to the K8s RBAC groups.
|
||||||
|
resource "authentik_policy_expression" "k8s_dashboard_groups" {
|
||||||
|
name = "k8s-dashboard-group-access"
|
||||||
|
expression = <<-EOT
|
||||||
|
return (
|
||||||
|
ak_is_group_member(request.user, name="kubernetes-admins")
|
||||||
|
or ak_is_group_member(request.user, name="kubernetes-power-users")
|
||||||
|
or ak_is_group_member(request.user, name="kubernetes-namespace-owners")
|
||||||
|
)
|
||||||
|
EOT
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "authentik_policy_binding" "k8s_dashboard_groups" {
|
||||||
|
target = authentik_application.k8s_dashboard.uuid
|
||||||
|
policy = authentik_policy_expression.k8s_dashboard_groups.id
|
||||||
|
order = 0
|
||||||
|
}
|
||||||
|
|
@ -9,6 +9,21 @@ terraform {
|
||||||
source = "cloudflare/cloudflare"
|
source = "cloudflare/cloudflare"
|
||||||
version = "~> 4"
|
version = "~> 4"
|
||||||
}
|
}
|
||||||
|
authentik = {
|
||||||
|
source = "goauthentik/authentik"
|
||||||
|
version = "~> 2024.10"
|
||||||
|
}
|
||||||
|
# kubectl (gavinbunney) — workaround for hashicorp/kubernetes
|
||||||
|
# `kubernetes_manifest` panics on Kyverno CRDs. See beads code-e2dp.
|
||||||
|
# Declared for all stacks but only used where opted-in.
|
||||||
|
kubectl = {
|
||||||
|
source = "gavinbunney/kubectl"
|
||||||
|
version = "~> 1.14"
|
||||||
|
}
|
||||||
|
proxmox = {
|
||||||
|
source = "telmate/proxmox"
|
||||||
|
version = "3.0.2-rc07"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -31,3 +46,8 @@ provider "vault" {
|
||||||
address = "https://vault.viktorbarzin.me"
|
address = "https://vault.viktorbarzin.me"
|
||||||
skip_child_token = true
|
skip_child_token = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
provider "kubectl" {
|
||||||
|
config_path = var.kube_config_path
|
||||||
|
load_config_file = true
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue