Skip to content

Kubernetes Vault PKI Access Implementation Plan

For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.

Goal: Enable identity-based kubectl access via Vault-issued short-lived client certificates, authenticated through Authentik SSO.

Architecture: Users authenticate to Vault via OIDC (Authentik), then request certificates from Vault PKI based on their group membership. Kubernetes validates certificates against the cluster CA and maps cert Organization field to RBAC groups.

Tech Stack: Terraform (Vault provider, Kubernetes provider, Authentik provider), Bash scripting

Design Document: docs/plans/2025-12-26-k8s-oidc-access-design.md


Phase 1: Authentik Groups

Task 1.1: Create Kubernetes Access Groups in Authentik

Files: - Create: tf/authentik/kubernetes-groups.tf

Step 1: Create the groups file

# tf/authentik/kubernetes-groups.tf
# Authentik groups for Kubernetes access tiers

resource "authentik_group" "k8s_admins" {
  name         = "k8s-admins"
  is_superuser = false
}

resource "authentik_group" "k8s_developers" {
  name         = "k8s-developers"
  is_superuser = false
}

resource "authentik_group" "k8s_viewers" {
  name         = "k8s-viewers"
  is_superuser = false
}

Step 2: Validate Terraform configuration

cd tf/authentik
terraform validate

Expected: Success! The configuration is valid.

Step 3: Plan and verify changes

terraform plan -out=tfplan

Expected: Plan: 3 to add, 0 to change, 0 to destroy.

Step 4: Apply changes

terraform apply tfplan

Expected: Apply complete! Resources: 3 added

Step 5: Commit

git add tf/authentik/kubernetes-groups.tf
git commit -m "feat(authentik): add k8s access groups"

Phase 2: Vault PKI Roles

Task 2.1: Create PKI Roles for K8s Client Certificates

Files: - Create: tf/vault/pki-k8s-roles.tf

Reference: Pattern from tf/vault/pki-router-hosts.tf

Step 1: Create the PKI roles file

# tf/vault/pki-k8s-roles.tf
# PKI roles for Kubernetes client certificate issuance.
# Each role has a fixed Organization field that maps to K8s RBAC groups.

# =============================================================================
# Admin Role - Full cluster access
# =============================================================================

resource "vault_pki_secret_backend_role" "k8s_admin" {
  backend = "fzymgc-house/v1/ica1/v1"
  name    = "k8s-admin"

  # Allowed subject names - users authenticate as user@fzymgc.house
  allowed_domains    = ["fzymgc.house"]
  allow_bare_domains = true
  allow_subdomains   = false

  # Extended Key Usage: clientAuth only
  server_flag = false
  client_flag = true

  # Key configuration: ECDSA P-256
  key_type = "ec"
  key_bits = 256

  # Certificate TTL: 8 hours default, 24 hours max
  ttl     = "8h"
  max_ttl = "24h"

  # Fixed organization - maps to K8s group
  organization = ["k8s-admins"]

  # Disable hostname enforcement for client certs
  allow_any_name    = false
  enforce_hostnames = false
}

# =============================================================================
# Developer Role - Read/write workloads
# =============================================================================

resource "vault_pki_secret_backend_role" "k8s_developer" {
  backend = "fzymgc-house/v1/ica1/v1"
  name    = "k8s-developer"

  allowed_domains    = ["fzymgc.house"]
  allow_bare_domains = true
  allow_subdomains   = false

  server_flag = false
  client_flag = true

  key_type = "ec"
  key_bits = 256

  ttl     = "8h"
  max_ttl = "24h"

  organization = ["k8s-developers"]

  allow_any_name    = false
  enforce_hostnames = false
}

# =============================================================================
# Viewer Role - Read-only access
# =============================================================================

resource "vault_pki_secret_backend_role" "k8s_viewer" {
  backend = "fzymgc-house/v1/ica1/v1"
  name    = "k8s-viewer"

  allowed_domains    = ["fzymgc.house"]
  allow_bare_domains = true
  allow_subdomains   = false

  server_flag = false
  client_flag = true

  key_type = "ec"
  key_bits = 256

  ttl     = "8h"
  max_ttl = "24h"

  organization = ["k8s-viewers"]

  allow_any_name    = false
  enforce_hostnames = false
}

Step 2: Validate Terraform configuration

cd tf/vault
terraform validate

Expected: Success! The configuration is valid.

Step 3: Plan and verify changes

terraform plan -out=tfplan

Expected: Plan: 3 to add, 0 to change, 0 to destroy.

Step 4: Apply changes

terraform apply tfplan

Expected: Apply complete! Resources: 3 added

Step 5: Verify roles exist in Vault

vault list fzymgc-house/v1/ica1/v1/roles

Expected output should include: k8s-admin, k8s-developer, k8s-viewer

Step 6: Commit

git add tf/vault/pki-k8s-roles.tf
git commit -m "feat(vault): add PKI roles for k8s client certs"

Phase 3: Vault Policies

Task 3.1: Create Policies for Certificate Issuance

Files: - Create: tf/vault/policy-k8s-access.tf

Step 1: Create the policies file

# tf/vault/policy-k8s-access.tf
# Vault policies controlling which K8s PKI roles users can access.
# Each policy corresponds to an Authentik group.

# =============================================================================
# Admin Policy - Can issue all cert types
# =============================================================================

resource "vault_policy" "k8s_admin_cert" {
  name = "k8s-admin-cert"

  policy = <<-EOT
    # K8s admin can issue admin, developer, and viewer certs
    path "fzymgc-house/v1/ica1/v1/issue/k8s-admin" {
      capabilities = ["create", "update"]
    }
    path "fzymgc-house/v1/ica1/v1/issue/k8s-developer" {
      capabilities = ["create", "update"]
    }
    path "fzymgc-house/v1/ica1/v1/issue/k8s-viewer" {
      capabilities = ["create", "update"]
    }
    # Read CA chain for kubeconfig
    path "fzymgc-house/v1/ica1/v1/ca_chain" {
      capabilities = ["read"]
    }
  EOT
}

# =============================================================================
# Developer Policy - Can issue developer and viewer certs
# =============================================================================

resource "vault_policy" "k8s_developer_cert" {
  name = "k8s-developer-cert"

  policy = <<-EOT
    # K8s developer can issue developer and viewer certs
    path "fzymgc-house/v1/ica1/v1/issue/k8s-developer" {
      capabilities = ["create", "update"]
    }
    path "fzymgc-house/v1/ica1/v1/issue/k8s-viewer" {
      capabilities = ["create", "update"]
    }
    # Read CA chain for kubeconfig
    path "fzymgc-house/v1/ica1/v1/ca_chain" {
      capabilities = ["read"]
    }
  EOT
}

# =============================================================================
# Viewer Policy - Can only issue viewer certs
# =============================================================================

resource "vault_policy" "k8s_viewer_cert" {
  name = "k8s-viewer-cert"

  policy = <<-EOT
    # K8s viewer can only issue viewer certs
    path "fzymgc-house/v1/ica1/v1/issue/k8s-viewer" {
      capabilities = ["create", "update"]
    }
    # Read CA chain for kubeconfig
    path "fzymgc-house/v1/ica1/v1/ca_chain" {
      capabilities = ["read"]
    }
  EOT
}

Step 2: Validate and plan

cd tf/vault
terraform validate && terraform plan -out=tfplan

Expected: Plan: 3 to add

Step 3: Apply changes

terraform apply tfplan

Step 4: Verify policies exist

vault policy list | rg k8s

Expected: k8s-admin-cert, k8s-developer-cert, k8s-viewer-cert

Step 5: Commit

git add tf/vault/policy-k8s-access.tf
git commit -m "feat(vault): add policies for k8s cert issuance"

Phase 4: OIDC Group Mapping

Task 4.1: Create Vault Identity Groups for K8s Access

Files: - Modify: tf/vault/groups-and-roles.tf

Step 1: Add K8s identity groups to existing file

Append to tf/vault/groups-and-roles.tf:

# =============================================================================
# Kubernetes Access Groups
# =============================================================================
# External groups that map to Authentik groups via OIDC.
# When users authenticate via OIDC with these Authentik groups,
# they receive the corresponding Vault policies.

resource "vault_identity_group" "k8s_admins" {
  name     = "k8s-admins"
  type     = "external"
  policies = [vault_policy.k8s_admin_cert.name]
}

resource "vault_identity_group" "k8s_developers" {
  name     = "k8s-developers"
  type     = "external"
  policies = [vault_policy.k8s_developer_cert.name]
}

resource "vault_identity_group" "k8s_viewers" {
  name     = "k8s-viewers"
  type     = "external"
  policies = [vault_policy.k8s_viewer_cert.name]
}

Step 2: Validate and plan

terraform validate && terraform plan -out=tfplan

Expected: Plan: 3 to add

Step 3: Apply changes

terraform apply tfplan

Step 4: Commit

git add tf/vault/groups-and-roles.tf
git commit -m "feat(vault): add identity groups for k8s access"

Task 4.2: Create OIDC Group Aliases

Files: - Create: tf/vault/oidc-k8s-group-aliases.tf

Note: This maps Authentik group names to Vault identity groups via the OIDC auth backend.

Step 1: Check existing OIDC auth accessor

vault auth list -format=json | jq -r '.["oidc/"].accessor'

Save this accessor ID for the next step.

Step 2: Create the group aliases file

# tf/vault/oidc-k8s-group-aliases.tf
# Map Authentik group claims to Vault identity groups

data "vault_auth_backend" "oidc" {
  path = "oidc"
}

resource "vault_identity_group_alias" "k8s_admins" {
  name           = "k8s-admins"
  mount_accessor = data.vault_auth_backend.oidc.accessor
  canonical_id   = vault_identity_group.k8s_admins.id
}

resource "vault_identity_group_alias" "k8s_developers" {
  name           = "k8s-developers"
  mount_accessor = data.vault_auth_backend.oidc.accessor
  canonical_id   = vault_identity_group.k8s_developers.id
}

resource "vault_identity_group_alias" "k8s_viewers" {
  name           = "k8s-viewers"
  mount_accessor = data.vault_auth_backend.oidc.accessor
  canonical_id   = vault_identity_group.k8s_viewers.id
}

Step 3: Validate and plan

terraform validate && terraform plan -out=tfplan

Expected: Plan: 3 to add

Step 4: Apply changes

terraform apply tfplan

Step 5: Commit

git add tf/vault/oidc-k8s-group-aliases.tf
git commit -m "feat(vault): add OIDC group aliases for k8s access"

Phase 5: Kubernetes RBAC

Task 5.1: Create ClusterRoleBindings for Cert-Based Groups

Files: - Create: tf/vault/k8s-rbac-vault-certs.tf

Step 1: Create the RBAC file

# tf/vault/k8s-rbac-vault-certs.tf
# Kubernetes RBAC bindings for Vault-issued certificate groups.
# Maps certificate Organization field to ClusterRoles.

resource "kubernetes_cluster_role_binding" "vault_cert_admins" {
  metadata {
    name = "vault-cert-cluster-admins"
    labels = {
      "app.kubernetes.io/managed-by" = "terraform"
      "app.kubernetes.io/component"  = "rbac"
    }
  }

  role_ref {
    api_group = "rbac.authorization.k8s.io"
    kind      = "ClusterRole"
    name      = "cluster-admin"
  }

  subject {
    kind      = "Group"
    name      = "k8s-admins"
    api_group = "rbac.authorization.k8s.io"
  }
}

resource "kubernetes_cluster_role_binding" "vault_cert_developers" {
  metadata {
    name = "vault-cert-developers"
    labels = {
      "app.kubernetes.io/managed-by" = "terraform"
      "app.kubernetes.io/component"  = "rbac"
    }
  }

  role_ref {
    api_group = "rbac.authorization.k8s.io"
    kind      = "ClusterRole"
    name      = "edit"
  }

  subject {
    kind      = "Group"
    name      = "k8s-developers"
    api_group = "rbac.authorization.k8s.io"
  }
}

resource "kubernetes_cluster_role_binding" "vault_cert_viewers" {
  metadata {
    name = "vault-cert-viewers"
    labels = {
      "app.kubernetes.io/managed-by" = "terraform"
      "app.kubernetes.io/component"  = "rbac"
    }
  }

  role_ref {
    api_group = "rbac.authorization.k8s.io"
    kind      = "ClusterRole"
    name      = "view"
  }

  subject {
    kind      = "Group"
    name      = "k8s-viewers"
    api_group = "rbac.authorization.k8s.io"
  }
}

Step 2: Validate and plan

terraform validate && terraform plan -out=tfplan

Expected: Plan: 3 to add

Step 3: Apply changes

terraform apply tfplan

Step 4: Verify ClusterRoleBindings exist

kubectl get clusterrolebindings | rg vault-cert

Expected: vault-cert-cluster-admins, vault-cert-developers, vault-cert-viewers

Step 5: Commit

git add tf/vault/k8s-rbac-vault-certs.tf
git commit -m "feat(vault): add k8s RBAC for vault cert groups"

Phase 6: Helper Script

Task 6.1: Create k8s-login.sh Helper Script

Files: - Create: scripts/k8s-login.sh

Step 1: Create scripts directory if needed

mkdir -p scripts

Step 2: Create the helper script

#!/usr/bin/env bash
# k8s-login.sh - Authenticate to Kubernetes using Vault-issued certificates
#
# Usage: k8s-login.sh [admin|developer|viewer]
#
# Prerequisites:
#   - vault CLI installed and in PATH
#   - kubectl CLI installed and in PATH
#   - jq installed and in PATH
#   - Authenticated to Vault: vault login -method=oidc

set -euo pipefail

ROLE="${1:-viewer}"
CLUSTER="fzymgc-house"
API_SERVER="https://192.168.20.140:6443"
VAULT_PKI_PATH="fzymgc-house/v1/ica1/v1"
TTL="${K8S_CERT_TTL:-8h}"

# Validate role
case "$ROLE" in
  admin|developer|viewer) ;;
  *)
    echo "Usage: k8s-login [admin|developer|viewer]"
    echo ""
    echo "Roles:"
    echo "  admin     - Full cluster-admin access"
    echo "  developer - Read/write workloads (edit role)"
    echo "  viewer    - Read-only access (view role)"
    exit 1
    ;;
esac

# Check prerequisites
command -v vault >/dev/null 2>&1 || { echo "Error: vault CLI not found"; exit 1; }
command -v kubectl >/dev/null 2>&1 || { echo "Error: kubectl CLI not found"; exit 1; }
command -v jq >/dev/null 2>&1 || { echo "Error: jq not found"; exit 1; }

# Check Vault authentication
if ! vault token lookup >/dev/null 2>&1; then
  echo "Error: Not authenticated to Vault"
  echo "Run: vault login -method=oidc"
  exit 1
fi

# Get username from current Vault token
USERNAME=$(vault token lookup -format=json | jq -r '.data.display_name // .data.entity_id')

echo "Requesting $ROLE certificate for $USERNAME (TTL: $TTL)..."

# Issue certificate from Vault
CERT_DATA=$(vault write -format=json "$VAULT_PKI_PATH/issue/k8s-$ROLE" \
  common_name="$USERNAME" \
  ttl="$TTL")

# Extract cert, key, and CA
CLIENT_CERT=$(echo "$CERT_DATA" | jq -r '.data.certificate')
CLIENT_KEY=$(echo "$CERT_DATA" | jq -r '.data.private_key')
CA_CHAIN=$(echo "$CERT_DATA" | jq -r '.data.ca_chain[0]')

# Create temp files for kubectl (process substitution doesn't work with --embed-certs)
CERT_FILE=$(mktemp)
KEY_FILE=$(mktemp)
CA_FILE=$(mktemp)
trap "rm -f $CERT_FILE $KEY_FILE $CA_FILE" EXIT

echo "$CLIENT_CERT" > "$CERT_FILE"
echo "$CLIENT_KEY" > "$KEY_FILE"
echo "$CA_CHAIN" > "$CA_FILE"

# Update kubeconfig
kubectl config set-cluster "$CLUSTER" \
  --server="$API_SERVER" \
  --certificate-authority="$CA_FILE" \
  --embed-certs=true

kubectl config set-credentials "$CLUSTER-$ROLE" \
  --client-certificate="$CERT_FILE" \
  --client-key="$KEY_FILE" \
  --embed-certs=true

kubectl config set-context "$CLUSTER-$ROLE" \
  --cluster="$CLUSTER" \
  --user="$CLUSTER-$ROLE"

kubectl config use-context "$CLUSTER-$ROLE"

echo "✓ Configured context: $CLUSTER-$ROLE (expires in $TTL)"
echo ""
echo "Test with: kubectl get nodes"

Step 3: Make script executable

chmod +x scripts/k8s-login.sh

Step 4: Commit

git add scripts/k8s-login.sh
git commit -m "feat: add k8s-login.sh helper script"

Phase 7: Documentation

Task 7.1: Create User Guide

Files: - Create: docs/kubernetes-access.md

Step 1: Create the documentation

# Kubernetes Access Guide

This guide explains how to authenticate to the fzymgc-house Kubernetes cluster using Vault-issued certificates.

## Prerequisites

- `vault` CLI installed
- `kubectl` CLI installed
- `jq` installed
- Member of one of: `k8s-admins`, `k8s-developers`, `k8s-viewers` in Authentik

## Quick Start

```bash
# 1. Authenticate to Vault via Authentik SSO
vault login -method=oidc

# 2. Get a Kubernetes certificate and configure kubectl
./scripts/k8s-login.sh admin    # or: developer, viewer

# 3. Use kubectl
kubectl get nodes

Access Levels

Role ClusterRole Capabilities
admin cluster-admin Full cluster access
developer edit Create/modify workloads, no RBAC changes
viewer view Read-only access

Certificate Lifetime

  • Default: 8 hours
  • Maximum: 24 hours
  • Override: K8S_CERT_TTL=4h ./scripts/k8s-login.sh admin

Troubleshooting

"Not authenticated to Vault"

Run vault login -method=oidc first.

"Permission denied" when issuing certificate

Your Authentik group doesn't have access to that role level: - k8s-admins can issue: admin, developer, viewer - k8s-developers can issue: developer, viewer - k8s-viewers can issue: viewer only

Certificate expired

Re-run ./scripts/k8s-login.sh <role> to get a fresh certificate.

Break-Glass Access

For emergency access when Vault/Authentik are unavailable, use the static admin kubeconfig:

export KUBECONFIG=~/.kube/configs/fzymgc-house-admin.yml
kubectl get nodes

This should only be used for break-glass scenarios.

**Step 2: Commit**

```bash
git add docs/kubernetes-access.md
git commit -m "docs: add kubernetes access user guide"


Phase 8: Testing

Task 8.1: End-to-End Test

Step 1: Add yourself to k8s-admins group in Authentik

Via Authentik admin UI: Users → [Your user] → Groups → Add to k8s-admins

Step 2: Test Vault OIDC login

vault login -method=oidc

Expected: Browser opens, authenticate via Authentik, success message.

Step 3: Verify group membership in token

vault token lookup -format=json | jq '.data.identity_policies'

Expected: Should include k8s-admin-cert

Step 4: Test certificate issuance

vault write fzymgc-house/v1/ica1/v1/issue/k8s-admin \
  common_name="test@fzymgc.house" \
  ttl="1h"

Expected: Returns certificate, private_key, ca_chain

Step 5: Test helper script

./scripts/k8s-login.sh admin

Expected: ✓ Configured context: fzymgc-house-admin

Step 6: Test kubectl access

kubectl get nodes

Expected: List of cluster nodes

Step 7: Test RBAC enforcement

As viewer, try to create a pod (should fail):

./scripts/k8s-login.sh viewer
kubectl run test --image=nginx

Expected: Error from server (Forbidden)

Step 8: Final commit

git add -A
git commit -m "feat: complete k8s vault pki access implementation"

Verification Checklist

  • [ ] Authentik groups created (k8s-admins, k8s-developers, k8s-viewers)
  • [ ] Vault PKI roles created (k8s-admin, k8s-developer, k8s-viewer)
  • [ ] Vault policies created (k8s-admin-cert, k8s-developer-cert, k8s-viewer-cert)
  • [ ] Vault identity groups created and mapped to OIDC
  • [ ] K8s ClusterRoleBindings created
  • [ ] Helper script works
  • [ ] Documentation complete
  • [ ] RBAC enforcement verified