Initial commit

This commit is contained in:
Scooby Husky
2026-03-09 20:21:35 -05:00
commit aacb8eebbe
314 changed files with 21766 additions and 0 deletions
@@ -0,0 +1,20 @@
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: authentik
namespace: authentik
spec:
parentRefs:
- name: edge
namespace: gateway
sectionName: https
hostnames:
- auth.kube.huskypup.net
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: authentik-server
port: 80
@@ -0,0 +1,179 @@
# Nginx proxy that rewrites Host header for Authentik ext_authz
#
# Problem: Envoy's ext_authz HTTP filter forwards the ORIGINAL request's Host
# header (e.g., prometheus.kube.huskypup.net) to Authentik. But Authentik's
# embedded outpost only serves /outpost.goauthentik.io/auth/nginx on the
# brand domain (auth.kube.huskypup.net), returning 404 for other hosts.
#
# Solution: This lightweight nginx proxy sits between Istio's ext_authz filter
# and Authentik. It rewrites the Host header to auth.kube.huskypup.net while
# preserving all other headers (X-Forwarded-Host, cookies, etc.) so Authentik
# knows the original service being accessed.
#
# Flow:
# 1. Client → Istio ingress gateway (Host: prometheus.kube.huskypup.net)
# 2. ext_authz filter → this proxy (Host: prometheus.kube.huskypup.net)
# 3. This proxy → Authentik (Host: auth.kube.huskypup.net, X-Forwarded-Host preserved)
# 4. Authentik checks auth, returns 200 (allow) or 302 (redirect to login)
---
apiVersion: v1
kind: ConfigMap
metadata:
name: ext-authz-proxy-config
namespace: authentik
data:
nginx.conf: |
load_module modules/ngx_http_js_module.so;
worker_processes 1;
error_log /dev/stderr warn;
pid /tmp/nginx.pid;
events {
worker_connections 256;
}
http {
access_log /dev/stdout;
proxy_temp_path /tmp/proxy_temp;
client_body_temp_path /tmp/client_temp;
js_path /etc/nginx/njs/;
js_import cookie from cookie_domain.js;
server {
listen 4180;
location / {
# Rewrite all paths to the exact Authentik traefik handler path.
# Envoy ext_authz appends the original request path to pathPrefix,
# creating paths like /auth/traefik/ which Authentik's Go router
# doesn't match (exact path only, no trailing slash).
rewrite ^ /outpost.goauthentik.io/auth/traefik break;
proxy_pass http://authentik-server.authentik.svc.cluster.local;
proxy_http_version 1.1;
proxy_set_header Host auth.kube.huskypup.net;
proxy_set_header Connection "";
proxy_pass_request_headers on;
# Add Domain=kube.huskypup.net to Set-Cookie headers from Authentik.
# The outpost creates session cookies without Domain attribute, causing
# browser to scope them to the original request domain (e.g., frigate.kube.huskypup.net).
# The callback goes to auth.kube.huskypup.net, so the cookie must be domain-scoped.
js_header_filter cookie.addDomain;
}
}
}
cookie_domain.js: |
function addDomain(r) {
var cookies = r.headersOut['Set-Cookie'];
if (cookies) {
if (!Array.isArray(cookies)) {
cookies = [cookies];
}
var modified = cookies.map(function(c) {
if (c.indexOf('Domain=') === -1) {
return c + '; Domain=kube.huskypup.net';
}
return c;
});
r.headersOut['Set-Cookie'] = modified;
}
}
export default { addDomain };
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: ext-authz-proxy
namespace: authentik
labels:
app: ext-authz-proxy
spec:
replicas: 2
selector:
matchLabels:
app: ext-authz-proxy
template:
metadata:
labels:
app: ext-authz-proxy
spec:
automountServiceAccountToken: false
containers:
- name: nginx
image: nginx:1.27-alpine
ports:
- containerPort: 4180
protocol: TCP
volumeMounts:
- name: config
mountPath: /etc/nginx/nginx.conf
subPath: nginx.conf
readOnly: true
- name: njs
mountPath: /etc/nginx/njs/cookie_domain.js
subPath: cookie_domain.js
readOnly: true
- name: tmp
mountPath: /tmp
- name: cache
mountPath: /var/cache/nginx
resources:
requests:
cpu: 10m
memory: 32Mi
limits:
cpu: 50m
memory: 64Mi
securityContext:
runAsNonRoot: true
runAsUser: 10101
runAsGroup: 10101
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
seccompProfile:
type: RuntimeDefault
readinessProbe:
tcpSocket:
port: 4180
initialDelaySeconds: 2
periodSeconds: 5
livenessProbe:
tcpSocket:
port: 4180
initialDelaySeconds: 5
periodSeconds: 10
volumes:
- name: config
configMap:
name: ext-authz-proxy-config
- name: njs
configMap:
name: ext-authz-proxy-config
- name: tmp
emptyDir: {}
- name: cache
emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
name: ext-authz-proxy
namespace: authentik
labels:
app: ext-authz-proxy
spec:
selector:
app: ext-authz-proxy
ports:
- port: 4180
targetPort: 4180
protocol: TCP
@@ -0,0 +1,63 @@
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: authentik
namespace: authentik
spec:
hosts:
- auth.kube.huskypup.net
gateways:
- istio-system/edge
http:
# Allow NetBird silent auth in an iframe (prompt=none)
- match:
- uri:
prefix: /application/o/authorize/
headers:
response:
remove:
- x-frame-options
set:
content-security-policy: "frame-ancestors https://netbird.kube.huskypup.net"
route:
- destination:
host: authentik-server.authentik.svc.cluster.local
port:
number: 80
# Ensure CORS headers are present even on 401 responses
- match:
- uri:
prefix: /application/o/userinfo/
- uri:
prefix: /application/o/token/
headers:
response:
set:
access-control-allow-origin: "https://netbird.kube.huskypup.net"
access-control-allow-credentials: "true"
access-control-allow-methods: "GET, POST, OPTIONS"
access-control-allow-headers: "authorization, content-type"
vary: "Origin"
route:
- destination:
host: authentik-server.authentik.svc.cluster.local
port:
number: 80
# Forward auth endpoint for Istio ext_authz
- match:
- uri:
prefix: /outpost.goauthentik.io
route:
- destination:
host: authentik-server.authentik.svc.cluster.local
port:
number: 80
# All other traffic goes to Authentik
- route:
- destination:
host: authentik-server.authentik.svc.cluster.local
port:
number: 80
@@ -0,0 +1,36 @@
apiVersion: batch/v1
kind: Job
metadata:
name: authentik-oauth-sync
namespace: authentik
annotations:
argocd.argoproj.io/hook: PostSync
argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
argocd.argoproj.io/sync-wave: "1"
spec:
backoffLimit: 3
template:
spec:
serviceAccountName: argocd-hook-sa
containers:
- name: oauth-sync
image: bitnami/kubectl:1.31
command:
- /bin/bash
- -c
- |
set -euo pipefail
echo "=== Authentik PostSync - OAuth Sync ==="
echo "Waiting for Authentik server..."
kubectl -n authentik rollout status deploy/authentik-server --timeout=600s
echo "Waiting for Authentik worker..."
kubectl -n authentik rollout status deploy/authentik-worker --timeout=300s
echo "OAuth sync scripts should be run manually or via a separate automation."
echo "Scripts are in the scripts/ directory of the Homelabv4 repo."
echo "Run: bash scripts/sync-*-oauth.sh"
echo "=== Authentik PostSync Complete ==="
restartPolicy: Never
@@ -0,0 +1,48 @@
apiVersion: batch/v1
kind: Job
metadata:
name: authentik-presync
namespace: authentik
annotations:
argocd.argoproj.io/hook: PreSync
argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
spec:
backoffLimit: 3
template:
spec:
serviceAccountName: argocd-hook-sa
containers:
- name: presync
image: bitnami/kubectl:1.31
command:
- /bin/bash
- -c
- |
set -euo pipefail
echo "=== Authentik PreSync ==="
# Wait for CNPG cluster to be ready (applied by ArgoCD as sync-wave resource)
echo "Waiting for Authentik PostgreSQL cluster..."
for i in $(seq 1 60); do
PHASE=$(kubectl -n authentik get clusters.postgresql.cnpg.io pg-authentik -o jsonpath='{.status.phase}' 2>/dev/null || echo "")
if [ "$PHASE" = "Cluster in healthy state" ] || [ "$PHASE" = "Healthy" ]; then
echo " PostgreSQL cluster ready"
break
fi
echo " waiting for pg-authentik... (attempt $i/60, phase=$PHASE)"
sleep 5
done
# Generate Authentik secret key if it doesn't exist
if ! kubectl -n authentik get secret authentik >/dev/null 2>&1; then
echo "Generating Authentik secret key..."
SECRET_KEY=$(openssl rand -hex 50)
kubectl -n authentik create secret generic authentik \
--from-literal=AUTHENTIK_SECRET_KEY="$SECRET_KEY"
echo " Authentik secret key generated"
else
echo " Authentik secret already exists"
fi
echo "=== Authentik PreSync Complete ==="
restartPolicy: Never