diff --git a/apps/overlays/uat/authentik-terraform.yaml b/apps/overlays/uat/authentik-terraform.yaml new file mode 100644 index 0000000..ffe3fb1 --- /dev/null +++ b/apps/overlays/uat/authentik-terraform.yaml @@ -0,0 +1,53 @@ +# ============================================================================= +# Terraform CRD for Flux ToFu Controller — Authentik groombook-uat +# ============================================================================= +# This CRD tells the Flux ToFu Controller to reconcile the Terraform +# workspace at apps/overlays/uat/terraform/ +# +# The ToFu Controller will: +# 1. Clone the groombook/app GitRepository +# 2. Run tofu init + tofu plan/apply in the specified path +# 3. Store Terraform state in a Kubernetes secret (backend.tf) +# 4. Inject TF_VAR_authentik_token from the authentik-credentials secret +# via tf-controller varsFrom (maps secret key to Terraform variable) +# +# ApiVersion: infra.contrib.fluxcd.io/v1alpha2 (tf-controller) +# ============================================================================= + +apiVersion: infra.contrib.fluxcd.io/v1alpha2 +kind: Terraform +metadata: + name: authentik-uat + namespace: groombook-uat + labels: + app.kubernetes.io/name: authentik + app.kubernetes.io/part-of: groombook + app.kubernetes.io/env: uat +spec: + # Reconcile every hour + interval: 1h + + # Path within the GitRepository (groombook/app) + path: ./apps/overlays/uat/terraform + + # Source reference — must match the GitRepository name watching this repo + sourceRef: + kind: GitRepository + name: groombook + + # Auto-approve plans (no manual intervention needed for infrastructure) + approvePlan: "auto" + + # Clean up Terraform resources when this CRD is deleted + destroyResourcesOnDeletion: true + + # Inject TF_VAR_authentik_token from the sealed secret via tf-controller varsFrom + # (maps secret key "authentik_token" to Terraform var.authentik_token) + varsFrom: + - kind: Secret + name: authentik-credentials + - kind: Secret + name: authentik-uat-users-credentials + + runnerPodTemplate: + spec: {} diff --git a/apps/overlays/uat/gitrepository-groombook.yaml b/apps/overlays/uat/gitrepository-groombook.yaml new file mode 100644 index 0000000..99ad157 --- /dev/null +++ b/apps/overlays/uat/gitrepository-groombook.yaml @@ -0,0 +1,19 @@ +--- +apiVersion: source.toolkit.fluxcd.io/v1 +kind: GitRepository +metadata: + name: groombook + namespace: groombook-uat + labels: + app.kubernetes.io/name: groombook + app.kubernetes.io/part-of: groombook + app.kubernetes.io/env: uat +spec: + interval: 15m + provider: github + ref: + branch: fix/gro-844-network-policy + secretRef: + name: cpfarhood-k8s + timeout: 60s + url: https://github.com/groombook/app diff --git a/apps/overlays/uat/kustomization.yaml b/apps/overlays/uat/kustomization.yaml new file mode 100644 index 0000000..f564ce2 --- /dev/null +++ b/apps/overlays/uat/kustomization.yaml @@ -0,0 +1,6 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +namespace: groombook-uat +resources: + - gitrepository-groombook.yaml + - authentik-terraform.yaml diff --git a/apps/overlays/uat/terraform/backend.tf b/apps/overlays/uat/terraform/backend.tf new file mode 100644 index 0000000..08f6c9b --- /dev/null +++ b/apps/overlays/uat/terraform/backend.tf @@ -0,0 +1,21 @@ +# ============================================================================= +# Backend configuration for Terraform state +# ============================================================================= +# Uses Kubernetes backend with tf-controller managed state secret. +# tf-controller creates a Kubernetes Secret named: +# tfstate-- +# i.e. tfstate-authentik-uat-authentik-uat-tf-state +# in the namespace specified by the Terraform CRD metadata.namespace (groombook-uat). +# +# Valid Kubernetes backend attributes for tf-controller: +# secret_suffix, namespace, config_path, cluster_ca_cert, client_certificate, +# client_key, token, exec, host, insecure, username, password, +# in_cluster, load_config, config_paths +# ============================================================================= + +terraform { + backend "kubernetes" { + secret_suffix = "authentik-uat-tf-state" + namespace = "groombook-uat" + } +} diff --git a/apps/overlays/uat/terraform/imports.tf b/apps/overlays/uat/terraform/imports.tf new file mode 100644 index 0000000..1356ec2 --- /dev/null +++ b/apps/overlays/uat/terraform/imports.tf @@ -0,0 +1,12 @@ +# Import existing Authentik resources into Terraform state. +# These blocks are consumed on the first apply and become no-ops thereafter. + +import { + to = authentik_oauth2_provider.groombook-uat + id = "284" +} + +import { + to = authentik_application.groombook-uat + id = "e77a9c45-bed6-4a23-bc62-178f166f099e" +} diff --git a/apps/overlays/uat/terraform/main.tf b/apps/overlays/uat/terraform/main.tf new file mode 100644 index 0000000..4c10104 --- /dev/null +++ b/apps/overlays/uat/terraform/main.tf @@ -0,0 +1,99 @@ +# ============================================================================= +# Terraform configuration for Authentik groombook-uat application +# ============================================================================= +# This Terraform workspace manages the Authentik OAuth2 application and provider +# for the groombook-uat environment. +# +# The authentik_token used for authentication is sourced from the +# `authentik-credentials` SealedSecret (injected as TF_VAR_authentik_token +# by the Terraform CRD runnerPodTemplate.spec.varsFrom). +# +# To import existing resources (run via tf-controller exec or locally with +# AUTHENTIK_TOKEN set): +# tofu import authentik_oauth2_provider.groombook-uat pk-284 +# tofu import authentik_application.groombook-uat e77a9c45-bed6-4a23-bc62-178f166f099e +# ============================================================================= + +# ----------------------------------------------------------------------------- +# Provider configuration +# ----------------------------------------------------------------------------- +terraform { + required_providers { + authentik = { + source = "goauthentik/authentik" + version = "~> 2024.12" + } + } +} + +provider "authentik" { + url = var.authentik_url + api_token = var.authentik_token + tls_verify = true +} + +# ----------------------------------------------------------------------------- +# OAuth2 Provider for groombook-uat +# pk = 284 (existing — imported, not recreated) +# ----------------------------------------------------------------------------- +resource "authentik_oauth2_provider" "groombook-uat" { + name = "groombook-uat-provider" + slug = "groombook-uat" + client_id = "" # managed by imported resource; tracked via ignore_changes + client_secret = "" # managed by imported resource; tracked via ignore_changes + client_type = "confidential" + redirect_uris = ["https://uat.groombook.dev/api/auth/oauth2/callback/authentik"] + signing_key = "authentik signing key" + + # Keep Terraform from overwriting the client_id, client_secret, and signing_key + # which are managed by the imported existing resource + lifecycle { + ignore_changes = [ + client_id, + client_secret, + signing_key, + ] + } +} + +# ----------------------------------------------------------------------------- +# Application for groombook-uat +# pk = e77a9c45-bed6-4a23-bc62-178f166f099e (existing — imported, not recreated) +# ----------------------------------------------------------------------------- +resource "authentik_application" "groombook-uat" { + name = "groombook-uat" + slug = "groombook-uat" + group = "groombook" + policy_ids = [] + description = "GroomBook UAT application" + + # Link to the OAuth2 provider + oauth2_provider = authentik_oauth2_provider.groombook-uat.id + + # Track name, slug, group, and oauth2_provider for drift detection; + # ignore policy_ids and description which may be updated out-of-band + lifecycle { + ignore_changes = [ + policy_ids, + description, + ] + } +} + +# ----------------------------------------------------------------------------- +# Outputs (for reference / verification) +# ----------------------------------------------------------------------------- +output "oauth2_provider_pk" { + description = "Authentik OAuth2 Provider primary key" + value = authentik_oauth2_provider.groombook-uat.pk +} + +output "application_pk" { + description = "Authentik Application primary key" + value = authentik_application.groombook-uat.pk +} + +output "application_slug" { + description = "Authentik Application slug" + value = authentik_application.groombook-uat.slug +} diff --git a/apps/overlays/uat/terraform/terraform.tfvars b/apps/overlays/uat/terraform/terraform.tfvars new file mode 100644 index 0000000..1cf4747 --- /dev/null +++ b/apps/overlays/uat/terraform/terraform.tfvars @@ -0,0 +1,10 @@ +# ============================================================================= +# Terraform variable values for groombook-uat +# ============================================================================= +# NOTE: authentik_token should be provided via AUTHENTIK_TOKEN env var, +# sourced from the authentik-credentials SealedSecret. +# The placeholder value here is not used when running via tf-controller. +# ============================================================================= + +authentik_url = "https://auth.farh.net" +# authentik_token = "" diff --git a/apps/overlays/uat/terraform/users.tf b/apps/overlays/uat/terraform/users.tf new file mode 100644 index 0000000..c5adced --- /dev/null +++ b/apps/overlays/uat/terraform/users.tf @@ -0,0 +1,121 @@ +# ============================================================================= +# Authentik UAT user personas — Terraform resources +# ============================================================================= +# Creates three Authentik users bound to the groombook-uat application: +# - UAT Super User (manager role, superuser) +# - UAT Groomer (staff/groomer role) +# - UAT Customer (no staff record — auth identity only) +# +# Passwords are sourced from sensitive Terraform variables which are injected +# via tf-controller varsFrom from the authentik-uat-users-credentials SealedSecret. +# +# User PKs are exported as outputs — these are the OIDC sub claims in Authentik. +# ============================================================================= + +# ----------------------------------------------------------------------------- +# Group: groombook-uat-users +# ----------------------------------------------------------------------------- +resource "authentik_group" "groombook-uat-users" { + name = "groombook-uat-users" +} + +# ----------------------------------------------------------------------------- +# User: UAT Super User +# ----------------------------------------------------------------------------- +resource "authentik_user" "uat-super" { + name = "UAT Super User" + username = "uat-super" + email = "uat-super@groombook.dev" + password = var.uat_super_password + active = true + # Attributes stored as JSON string per authentik_user schema + attributes_json = jsonencode({ + role = "manager" + }) +} + +# Add uat-super to the group +resource "authentik_group_membership" "uat-super" { + group = authentik_group.groombook-uat-users.id + user = authentik_user.uat-super.pk +} + +# Bind the group to the groombook-uat application via policy binding +# This grants group members authentication access to the application +resource "authentik_policy_binding" "uat-super-group-binding" { + policy = authentik_group.groombook-uat-users.id + target = authentik_application.groombook-uat.pk + binding_type = "group_whitelist" +} + +# ----------------------------------------------------------------------------- +# User: UAT Groomer (Staff) +# ----------------------------------------------------------------------------- +resource "authentik_user" "uat-groomer" { + name = "UAT Groomer" + username = "uat-groomer" + email = "uat-groomer@groombook.dev" + password = var.uat_groomer_password + active = true + attributes_json = jsonencode({ + role = "groomer" + }) +} + +# Add uat-groomer to the group +resource "authentik_group_membership" "uat-groomer" { + group = authentik_group.groombook-uat-users.id + user = authentik_user.uat-groomer.pk +} + +# Bind the group to the groombook-uat application +resource "authentik_policy_binding" "uat-groomer-group-binding" { + policy = authentik_group.groombook-uat-users.id + target = authentik_application.groombook-uat.pk + binding_type = "group_whitelist" +} + +# ----------------------------------------------------------------------------- +# User: UAT Customer +# ----------------------------------------------------------------------------- +resource "authentik_user" "uat-customer" { + name = "UAT Customer" + username = "uat-customer" + email = "uat-customer@groombook.dev" + password = var.uat_customer_password + active = true + attributes_json = jsonencode({ + role = "customer" + }) +} + +# Add uat-customer to the group +resource "authentik_group_membership" "uat-customer" { + group = authentik_group.groombook-uat-users.id + user = authentik_user.uat-customer.pk +} + +# Bind the group to the groombook-uat application +resource "authentik_policy_binding" "uat-customer-group-binding" { + policy = authentik_group.groombook-uat-users.id + target = authentik_application.groombook-uat.pk + binding_type = "group_whitelist" +} + +# ----------------------------------------------------------------------------- +# Outputs — OIDC sub claims (= user PK in Authentik) +# ----------------------------------------------------------------------------- +output "uat_super_user_pk" { + description = "UAT Super User primary key (OIDC sub)" + value = authentik_user.uat-super.pk +} + +output "uat_groomer_user_pk" { + description = "UAT Groomer primary key (OIDC sub)" + value = authentik_user.uat-groomer.pk +} + +output "uat_customer_user_pk" { + description = "UAT Customer primary key (OIDC sub)" + value = authentik_user.uat-customer.pk +} diff --git a/apps/overlays/uat/terraform/variables.tf b/apps/overlays/uat/terraform/variables.tf new file mode 100644 index 0000000..bfb69e8 --- /dev/null +++ b/apps/overlays/uat/terraform/variables.tf @@ -0,0 +1,33 @@ +# ============================================================================= +# Variables for Authentik groombook-uat Terraform workspace +# ============================================================================= + +variable "authentik_url" { + description = "Base URL of the Authentik instance" + type = string + default = "https://auth.farh.net" +} + +variable "authentik_token" { + description = "API token for Authentik (from authentik-credentials secret via AUTHENTIK_TOKEN env var)" + type = string + sensitive = true +} + +variable "uat_super_password" { + description = "Password for the UAT Super User account" + type = string + sensitive = true +} + +variable "uat_groomer_password" { + description = "Password for the UAT Groomer staff account" + type = string + sensitive = true +} + +variable "uat_customer_password" { + description = "Password for the UAT Customer account" + type = string + sensitive = true +}