k8s/iam: add cloudnativepg postgres and vault + authentik

- PostgreSQL 3-replica HA with pgvector
- Vault S3 storage backend (MinIO)
- Authentik federated OIDC provider
- Vault auto-unseal via postStart hook
This commit is contained in:
Story Crater Bot
2026-08-18 15:08:00 -07:00
parent 7da222e243
commit 831dd50805
20 changed files with 3047 additions and 0 deletions
@@ -0,0 +1,8 @@
# Copy to ~/.authentik/.env and fill in your values.
# Port-forward Authentik first: kubectl port-forward svc/authentik-server -n iam 7000:80
AUTHENTIK_BASE_URL=http://localhost:7000
APP_SLUG=go-example
OIDC_CLIENT_ID=go-example
OIDC_CLIENT_SECRET=your-client-secret-here
REDIRECT_URL=http://localhost:8080/callback
+14
View File
@@ -0,0 +1,14 @@
module homelab/go-example-oidc
go 1.22
require (
github.com/coreos/go-oidc/v3 v3.11.0
github.com/joho/godotenv v1.5.1
golang.org/x/oauth2 v0.24.0
)
require (
github.com/go-jose/go-jose/v4 v4.0.2 // indirect
golang.org/x/crypto v0.25.0 // indirect
)
+20
View File
@@ -0,0 +1,20 @@
github.com/coreos/go-oidc/v3 v3.11.0 h1:Ia3MxdwpSw702YW0xgfmP1GVCMA9aEFWu12XUZ3/OtI=
github.com/coreos/go-oidc/v3 v3.11.0/go.mod h1:gE3LgjOgFoHi9a4ce4/tJczr0Ai2/BoDhf0r5lltWI0=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-jose/go-jose/v4 v4.0.2 h1:R3l3kkBds16bO7ZFAEEcofK0MkrAJt3jlJznWZG0nvk=
github.com/go-jose/go-jose/v4 v4.0.2/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE=
golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+90
View File
@@ -0,0 +1,90 @@
// Minimal OIDC Authorization Code flow against Authentik.
//
// Setup:
// 1. In Authentik create a provider + app (slug "go-example", redirect URI http://localhost:8080/callback).
// 2. Fill in ~/.authentik/.env (see .env.example).
// 3. go mod tidy && go run .
// 4. Open http://localhost:8080/login
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"path/filepath"
"github.com/coreos/go-oidc/v3/oidc"
"github.com/joho/godotenv"
"golang.org/x/oauth2"
)
// oauthState is a fixed random value for this process — good enough for a local demo.
// In production, generate a per-request random state and store it in a cookie.
var oauthState = "homelab-oidc-example"
func main() {
// Load ~/.authentik/.env; shell env vars already set take precedence.
home, _ := os.UserHomeDir()
godotenv.Load(filepath.Join(home, ".authentik", ".env"))
ctx := context.Background()
// go-oidc discovers the token endpoint, auth endpoint, and JWKS URI automatically
// from Authentik's /.well-known/openid-configuration.
issuer := os.Getenv("AUTHENTIK_BASE_URL") + "/application/o/" + os.Getenv("APP_SLUG")
provider, err := oidc.NewProvider(ctx, issuer)
if err != nil {
log.Fatalf("OIDC discovery failed (%s): %v", issuer, err)
}
cfg := &oauth2.Config{
ClientID: os.Getenv("OIDC_CLIENT_ID"),
ClientSecret: os.Getenv("OIDC_CLIENT_SECRET"),
RedirectURL: os.Getenv("REDIRECT_URL"),
Endpoint: provider.Endpoint(),
Scopes: []string{oidc.ScopeOpenID, "email", "profile"},
}
verifier := provider.Verifier(&oidc.Config{ClientID: cfg.ClientID})
// /login — redirect the browser to Authentik's authorization endpoint
http.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, cfg.AuthCodeURL(oauthState), http.StatusFound)
})
// /callback — Authentik redirects here with ?code=...&state=...
http.HandleFunc("/callback", func(w http.ResponseWriter, r *http.Request) {
if r.URL.Query().Get("state") != oauthState {
http.Error(w, "state mismatch", http.StatusBadRequest)
return
}
// Exchange the authorization code for tokens
token, err := cfg.Exchange(ctx, r.URL.Query().Get("code"))
if err != nil {
http.Error(w, "token exchange: "+err.Error(), http.StatusInternalServerError)
return
}
// Verify the ID token signature against Authentik's JWKS, then extract claims
rawID, _ := token.Extra("id_token").(string)
idToken, err := verifier.Verify(ctx, rawID)
if err != nil {
http.Error(w, "id_token verify: "+err.Error(), http.StatusInternalServerError)
return
}
var claims map[string]any
idToken.Claims(&claims)
w.Header().Set("Content-Type", "application/json")
enc := json.NewEncoder(w)
enc.SetIndent("", " ")
enc.Encode(claims)
})
fmt.Println("open http://localhost:8080/login")
log.Fatal(http.ListenAndServe(":8080", nil))
}