Compare commits

...
Author SHA1 Message Date
Gitea Actions Bot 23c559d7f6 Auto-update README with current k8s applications
Keycloak Terraform / Terraform (pull_request) Successful in 18s
Generated by CI/CD workflow on 2026-07-30 22:17:14

This PR updates the README.md file with the current list of applications found in the k8s/ directory structure.
2026-07-30 22:17:14 +00:00
Ultradesu 80b792ce8c Fixed gitea runner scheduling. Bump amnezia
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 13s
Check with kubeconform / lint (push) Successful in 10s
Auto-update README / Generate README and Create MR (push) Successful in 8s
2026-07-30 23:16:36 +01:00
Ultradesu e40a4d0c72 Fixed gitea runner k8s auth
Check with kubeconform / lint (push) Successful in 18s
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 14s
Auto-update README / Generate README and Create MR (push) Failing after 16m47s
2026-07-30 16:48:37 +01:00
Ultradesu 03d580999a Dropped authentik TF code
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 7s
2026-07-30 16:42:33 +01:00
Ultradesu a28fed03ed TF fmt
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 8s
Keycloak Terraform / Terraform (push) Successful in 27s
2026-07-30 16:39:52 +01:00
Ultradesu 99a69257e6 Bump argocd
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 5s
Check with kubeconform / lint (push) Successful in 7s
Auto-update README / Generate README and Create MR (push) Successful in 7s
2026-07-30 16:30:53 +01:00
Ultradesu f0f455c7dd Added firefly-iii open banking importer
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 6s
Check with kubeconform / lint (push) Successful in 6s
Auto-update README / Generate README and Create MR (push) Successful in 7s
2026-07-30 16:19:37 +01:00
Ultradesu ede07b72e8 Added firefly-iii open banking importer
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 12s
Check with kubeconform / lint (push) Successful in 7s
Auto-update README / Generate README and Create MR (push) Successful in 5s
2026-07-30 16:06:46 +01:00
Ultradesu 4662797118 Added firefly-iii open banking importer
Check with kubeconform / lint (push) Successful in 7s
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 5s
Auto-update README / Generate README and Create MR (push) Successful in 7s
2026-07-30 16:00:32 +01:00
Ultradesu 505bfec45a Added firefly-iii open banking importer
Check with kubeconform / lint (push) Successful in 7s
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 5s
Auto-update README / Generate README and Create MR (push) Successful in 6s
2026-07-30 15:50:25 +01:00
Ultradesu a3b5811b83 Added firefly-iii open banking importer
Check with kubeconform / lint (push) Successful in 8s
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 8s
Auto-update README / Generate README and Create MR (push) Successful in 8s
2026-07-30 15:44:58 +01:00
Ultradesu f58f979b3b Added firefly-iii
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 6s
Check with kubeconform / lint (push) Successful in 7s
Auto-update README / Generate README and Create MR (push) Successful in 6s
2026-07-30 14:42:23 +01:00
Ultradesu c4a3623e55 Added firefly-iii
Check with kubeconform / lint (push) Successful in 6s
Auto-update README / Generate README and Create MR (push) Successful in 9s
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 5s
2026-07-30 14:37:20 +01:00
Ultradesu dd3693095f Added firefly-iii
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 7s
Check with kubeconform / lint (push) Successful in 6s
Auto-update README / Generate README and Create MR (push) Successful in 11s
2026-07-30 14:35:50 +01:00
Ultradesu 4273e62d68 Added firefly-iii
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 6s
Check with kubeconform / lint (push) Successful in 6s
Auto-update README / Generate README and Create MR (push) Successful in 8s
2026-07-30 13:57:55 +01:00
Ultradesu 5d8a582e1e Added firefly-iii
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 8s
Check with kubeconform / lint (push) Successful in 5s
Auto-update README / Generate README and Create MR (push) Successful in 5s
2026-07-30 12:25:17 +01:00
Ultradesu 31714fe357 Added firefly-iii
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 6s
Check with kubeconform / lint (push) Successful in 5s
Auto-update README / Generate README and Create MR (push) Successful in 5s
2026-07-30 12:22:42 +01:00
Ultradesu 2e22f98eb4 Added firefly-iii
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 5s
Check with kubeconform / lint (push) Successful in 6s
Auto-update README / Generate README and Create MR (push) Successful in 5s
2026-07-30 12:21:52 +01:00
Ultradesu 69186d807e Added firefly-iii
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 6s
Check with kubeconform / lint (push) Successful in 10s
Auto-update README / Generate README and Create MR (push) Successful in 8s
2026-07-30 12:18:12 +01:00
Ultradesu 719504a399 Fix amnezia iptables rules
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 6s
Check with kubeconform / lint (push) Successful in 7s
Auto-update README / Generate README and Create MR (push) Successful in 5s
2026-07-28 13:17:12 +01:00
Ultradesu 1059e430a0 Fix amnezia iptables rules
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 11s
Check with kubeconform / lint (push) Successful in 9s
Auto-update README / Generate README and Create MR (push) Successful in 8s
2026-07-28 13:04:19 +01:00
Ultradesu f3d199415f moved TS
Check with kubeconform / lint (push) Successful in 14s
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 11s
Auto-update README / Generate README and Create MR (push) Successful in 14s
2026-07-20 03:38:16 +03:00
Ultradesu ef0123b5fb fixed longhorn
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 30s
Check with kubeconform / lint (push) Successful in 2m43s
Auto-update README / Generate README and Create MR (push) Successful in 17s
2026-07-20 02:23:09 +03:00
Ultradesu 207b04d0a8 Moved longhorn to new NS
Auto-update README / Generate README and Create MR (push) Successful in 1m14s
Check with kubeconform / lint (push) Successful in 24s
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Failing after 11m44s
2026-07-20 01:26:36 +03:00
Ultradesu 5ef058fbef Fixed dex probes
Check with kubeconform / lint (push) Successful in 2m46s
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 15s
Auto-update README / Generate README and Create MR (push) Successful in 18s
2026-07-20 01:19:39 +03:00
Ultradesu ff57d1a07c Fixed doka2-lobby-list steam creds storage
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 6s
Check with kubeconform / lint (push) Successful in 6s
Auto-update README / Generate README and Create MR (push) Successful in 4s
2026-07-16 12:40:09 +03:00
ab a93a29655b added doka2-lobby-list
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 6s
Check with kubeconform / lint (push) Successful in 6s
Auto-update README / Generate README and Create MR (push) Successful in 5s
2026-07-13 16:07:43 +03:00
ab bd8963bcb9 added doka2-lobby-list
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 5s
Check with kubeconform / lint (push) Successful in 5s
Auto-update README / Generate README and Create MR (push) Successful in 5s
2026-07-13 16:05:46 +03:00
ab 55be79a361 added doka2-lobby-list
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 5s
Check with kubeconform / lint (push) Successful in 7s
Auto-update README / Generate README and Create MR (push) Successful in 4s
2026-07-13 16:05:04 +03:00
ab 62bd2291d0 Moved to PSQL
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 8s
Check with kubeconform / lint (push) Successful in 5m7s
Auto-update README / Generate README and Create MR (push) Successful in 5s
2026-07-11 14:59:11 +03:00
ab 0e08a46926 Moved to PSQL
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 8s
Check with kubeconform / lint (push) Successful in 8s
Auto-update README / Generate README and Create MR (push) Failing after 2m19s
2026-07-11 14:40:05 +03:00
ab c4432de7d5 Merge branch 'main' of ssh://gt.hexor.cy:30022/ab/homelab
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 14s
Check with kubeconform / lint (push) Successful in 9s
Auto-update README / Generate README and Create MR (push) Successful in 7s
2026-07-11 14:07:24 +03:00
ab 6dbf61f182 Added web_petting 2026-07-11 14:07:06 +03:00
Ultradesu 859a118e0a awg migration
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 7s
Check with kubeconform / lint (push) Successful in 8s
Auto-update README / Generate README and Create MR (push) Successful in 11s
2026-07-06 10:42:52 +03:00
ab dc8e66e604 Update k8s/apps/amnezia/fellow-deployment.yaml
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 10s
Check with kubeconform / lint (push) Successful in 12s
Auto-update README / Generate README and Create MR (push) Successful in 6s
2026-07-05 10:33:41 +00:00
ab 90bce2980e Migrated awg
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 5s
Check with kubeconform / lint (push) Successful in 4s
Auto-update README / Generate README and Create MR (push) Successful in 5s
2026-07-02 16:58:29 +03:00
ab 69947c9eee Migrated awg
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 6s
Check with kubeconform / lint (push) Successful in 5s
Auto-update README / Generate README and Create MR (push) Successful in 5s
2026-07-02 16:56:27 +03:00
ab 4d98223004 Migrated awg
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 2m30s
Check with kubeconform / lint (push) Successful in 7s
Auto-update README / Generate README and Create MR (push) Successful in 5s
2026-07-02 16:55:48 +03:00
ab fdd79fcff3 Migrated awg
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 6s
Check with kubeconform / lint (push) Successful in 7s
Auto-update README / Generate README and Create MR (push) Successful in 5s
2026-07-02 16:39:08 +03:00
Ultradesu 04044b32e0 AWG: Added migration
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 6s
Check with kubeconform / lint (push) Successful in 6s
Auto-update README / Generate README and Create MR (push) Successful in 6s
2026-07-01 13:43:17 +03:00
Ultradesu aa4c9dce08 Added amnezia-fellow DB user
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 7s
Check with kubeconform / lint (push) Successful in 6s
Auto-update README / Generate README and Create MR (push) Successful in 5s
2026-07-01 13:09:46 +03:00
Ultradesu c31ca20fb0 Fix amnezia exporter's config
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 6s
Check with kubeconform / lint (push) Successful in 7s
Auto-update README / Generate README and Create MR (push) Successful in 4s
2026-06-30 14:00:07 +03:00
Ultradesu b42ba8d68f Added config reload
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 2m33s
Check with kubeconform / lint (push) Successful in 7s
Auto-update README / Generate README and Create MR (push) Successful in 5s
2026-06-29 21:08:54 +03:00
Ultradesu f8c69c2434 Added config reload
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 58s
Check with kubeconform / lint (push) Successful in 7s
Auto-update README / Generate README and Create MR (push) Successful in 5s
2026-06-29 21:00:55 +03:00
Ultradesu 67104123a5 Added amnezia exporter
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 8s
Check with kubeconform / lint (push) Successful in 7s
Auto-update README / Generate README and Create MR (push) Successful in 11s
2026-06-29 20:46:16 +03:00
ab 976ea1fbe1 Update terraform/keycloak/terraform.tfvars
Keycloak Terraform / Terraform (push) Failing after 3m2s
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 6s
2026-06-29 14:07:10 +00:00
ab 7cfcfac94c Update terraform/keycloak/terraform.tfvars
Keycloak Terraform / Terraform (push) Successful in 18s
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 5s
2026-06-29 14:03:55 +00:00
Ultradesu 4b981e3d97 Fix: amnezia-fellow
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 6s
Check with kubeconform / lint (push) Successful in 7s
Auto-update README / Generate README and Create MR (push) Successful in 8s
2026-06-29 16:49:37 +03:00
Ultradesu f53ab23d8e Fix: amnezia-fellow
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 2m39s
Check with kubeconform / lint (push) Successful in 6s
Auto-update README / Generate README and Create MR (push) Successful in 6s
2026-06-29 16:23:23 +03:00
Ultradesu df1aa96316 added amnezia-dellow
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 6s
Check with kubeconform / lint (push) Successful in 8s
Auto-update README / Generate README and Create MR (push) Failing after 2m16s
2026-06-29 16:04:04 +03:00
ab 3d58baaf2f Added tmp job for unban me in gitea
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 37s
Check with kubeconform / lint (push) Failing after 12s
Auto-update README / Generate README and Create MR (push) Successful in 15s
2026-06-28 11:50:56 +03:00
ab 78c1519398 Adde prom auth ingress
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 6s
Check with kubeconform / lint (push) Successful in 7s
Auto-update README / Generate README and Create MR (push) Successful in 7s
2026-06-20 17:12:03 +03:00
ab d8a5a916e1 Added prom.hexor.cy rsauth-proxy app
Keycloak Terraform / Terraform (push) Successful in 22s
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 2m35s
2026-06-20 16:55:39 +03:00
ab a840dd674a Update k8s/core/prom-stack/prom-values.yaml
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 8s
Check with kubeconform / lint (push) Successful in 8s
Auto-update README / Generate README and Create MR (push) Successful in 11s
2026-06-19 17:45:35 +00:00
ab a094d3b925 Added Gitea captcha
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 5s
Check with kubeconform / lint (push) Successful in 5s
Auto-update README / Generate README and Create MR (push) Successful in 5s
2026-06-18 02:32:33 +01:00
ab 9508a8483c Added Gitea captcha
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 6s
Check with kubeconform / lint (push) Successful in 5s
Auto-update README / Generate README and Create MR (push) Successful in 5s
2026-06-18 02:30:49 +01:00
ab c5919259f6 Added Gitea captcha
Check with kubeconform / lint (push) Successful in 14s
Auto-update README / Generate README and Create MR (push) Failing after 2m57s
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Failing after 16m35s
2026-06-18 02:27:24 +01:00
Ultradesu 83de150f87 Fix amnezia iptables
Check with kubeconform / lint (push) Successful in 12s
Auto-update README / Generate README and Create MR (push) Failing after 10m47s
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Failing after 13m0s
2026-06-18 00:06:14 +01:00
Ultradesu 70d785769e Update longhorn tolerations
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 13s
Check with kubeconform / lint (push) Successful in 10s
Auto-update README / Generate README and Create MR (push) Successful in 10s
2026-06-17 15:27:17 +01:00
72 changed files with 2342 additions and 2068 deletions
+4
View File
@@ -42,7 +42,10 @@ ArgoCD homelab project
| Application | Status |
| :--- | :---: |
| **amnezia** | [![amnezia](https://ag.hexor.cy/api/badge?name=amnezia&revision=true)](https://ag.hexor.cy/applications/argocd/amnezia) |
| **comfyui** | [![comfyui](https://ag.hexor.cy/api/badge?name=comfyui&revision=true)](https://ag.hexor.cy/applications/argocd/comfyui) |
| **doka2-lobby-list** | [![doka2-lobby-list](https://ag.hexor.cy/api/badge?name=doka2-lobby-list&revision=true)](https://ag.hexor.cy/applications/argocd/doka2-lobby-list) |
| **firefly-iii** | [![firefly-iii](https://ag.hexor.cy/api/badge?name=firefly-iii&revision=true)](https://ag.hexor.cy/applications/argocd/firefly-iii) |
| **furumi** | [![furumi](https://ag.hexor.cy/api/badge?name=furumi&revision=true)](https://ag.hexor.cy/applications/argocd/furumi) |
| **gitea** | [![gitea](https://ag.hexor.cy/api/badge?name=gitea&revision=true)](https://ag.hexor.cy/applications/argocd/gitea) |
| **greece-notifier** | [![greece-notifier](https://ag.hexor.cy/api/badge?name=greece-notifier&revision=true)](https://ag.hexor.cy/applications/argocd/greece-notifier) |
@@ -53,6 +56,7 @@ ArgoCD homelab project
| **k8s-secrets** | [![k8s-secrets](https://ag.hexor.cy/api/badge?name=k8s-secrets&revision=true)](https://ag.hexor.cy/applications/argocd/k8s-secrets) |
| **khm** | [![khm](https://ag.hexor.cy/api/badge?name=khm&revision=true)](https://ag.hexor.cy/applications/argocd/khm) |
| **lidarr** | [![lidarr](https://ag.hexor.cy/api/badge?name=lidarr&revision=true)](https://ag.hexor.cy/applications/argocd/lidarr) |
| **llamacpp** | [![llamacpp](https://ag.hexor.cy/api/badge?name=llamacpp&revision=true)](https://ag.hexor.cy/applications/argocd/llamacpp) |
| **matrix** | [![matrix](https://ag.hexor.cy/api/badge?name=matrix&revision=true)](https://ag.hexor.cy/applications/argocd/matrix) |
| **mtproxy** | [![mtproxy](https://ag.hexor.cy/api/badge?name=mtproxy&revision=true)](https://ag.hexor.cy/applications/argocd/mtproxy) |
| **n8n** | [![n8n](https://ag.hexor.cy/api/badge?name=n8n&revision=true)](https://ag.hexor.cy/applications/argocd/n8n) |
+400 -20
View File
@@ -10,12 +10,19 @@ data:
PORT="${1:-5847}"
VPN_CIDR="${2:-10.8.0.0/16}"
POLICY_FILE="${3:-/run/amnezia/policy/policy.conf}"
FORWARD_CHAIN="AMNEZIAWG-FORWARD"
POLICY_CHAIN_A="AMNEZIAWG-POLICY-A"
POLICY_CHAIN_B="AMNEZIAWG-POLICY-B"
IPTABLES=(iptables -w 30)
external_interface() {
ip route get 1.1.1.1 | awk '{for (i=1;i<=NF;i++) if ($i=="dev") {print $(i+1); exit}}'
}
ensure_insert_rule() {
ensure_rule() {
local mode="$1"
shift
local table_args=()
if [ "${1:-}" = "-t" ]; then
table_args=("$1" "$2")
@@ -25,12 +32,12 @@ data:
local chain="$1"
shift
if ! iptables "${table_args[@]}" -C "${chain}" "$@" >/dev/null 2>&1; then
iptables "${table_args[@]}" -I "${chain}" 1 "$@"
if ! "${IPTABLES[@]}" "${table_args[@]}" -C "${chain}" "$@" >/dev/null 2>&1; then
"${IPTABLES[@]}" "${table_args[@]}" "${mode}" "${chain}" "$@"
fi
}
ensure_append_rule() {
delete_rule() {
local table_args=()
if [ "${1:-}" = "-t" ]; then
table_args=("$1" "$2")
@@ -40,8 +47,88 @@ data:
local chain="$1"
shift
if ! iptables "${table_args[@]}" -C "${chain}" "$@" >/dev/null 2>&1; then
iptables "${table_args[@]}" -A "${chain}" "$@"
while "${IPTABLES[@]}" "${table_args[@]}" -D "${chain}" "$@" >/dev/null 2>&1; do
true
done
}
ensure_chain() {
"${IPTABLES[@]}" -N "$1" >/dev/null 2>&1 || true
}
valid_ipv4_32() {
local value="$1"
[[ "${value}" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}/32$ ]] || return 1
local address="${value%/32}"
local octet
IFS=. read -r -a octets <<< "${address}"
[ "${#octets[@]}" -eq 4 ] || return 1
for octet in "${octets[@]}"; do
[[ "${octet}" =~ ^[0-9]+$ ]] || return 1
[ "${octet}" -le 255 ] || return 1
done
}
routed_through_awg0() {
ip -4 route get "${1%/32}" 2>/dev/null | grep -Eq '(^|[[:space:]])dev awg0([[:space:]]|$)'
}
active_policy_chain() {
"${IPTABLES[@]}" -S "${FORWARD_CHAIN}" 2>/dev/null \
| awk '$1 == "-A" && $3 == "-j" && $4 ~ /^AMNEZIAWG-POLICY-[AB]$/ { print $4; exit }'
}
apply_policy() {
local active inactive source destination extra
active="$(active_policy_chain || true)"
if [ "${active}" = "${POLICY_CHAIN_A}" ]; then
inactive="${POLICY_CHAIN_B}"
else
inactive="${POLICY_CHAIN_A}"
fi
ensure_chain "${inactive}"
"${IPTABLES[@]}" -F "${inactive}"
if [ -f "${POLICY_FILE}" ]; then
while read -r source destination extra; do
[ -n "${source:-}" ] || continue
[[ "${source}" == \#* ]] && continue
if [ -n "${extra:-}" ] \
|| ! valid_ipv4_32 "${source}" \
|| ! valid_ipv4_32 "${destination:-}" \
|| ! routed_through_awg0 "${source}" \
|| ! routed_through_awg0 "${destination}"; then
echo "Invalid or out-of-tunnel policy line: ${source:-} ${destination:-} ${extra:-}" >&2
"${IPTABLES[@]}" -F "${inactive}"
return 1
fi
"${IPTABLES[@]}" -A "${inactive}" \
-i awg0 -o awg0 -s "${source}" -d "${destination}" \
-m comment --comment amneziawg-group-pair -j ACCEPT
done < "${POLICY_FILE}"
fi
# This DROP is deliberately restricted to packets entering and leaving
# awg0. It cannot match Kubernetes, CNI, kubelet, Tailscale, or host traffic.
"${IPTABLES[@]}" -A "${inactive}" \
-i awg0 -o awg0 -s "${VPN_CIDR}" -d "${VPN_CIDR}" \
-m comment --comment amneziawg-client-isolation -j DROP
"${IPTABLES[@]}" -A "${inactive}" \
-i awg0 -o "${EXT_IF}" -s "${VPN_CIDR}" \
-m conntrack --ctstate NEW,ESTABLISHED,RELATED \
-m comment --comment amneziawg-internet-out -j ACCEPT
"${IPTABLES[@]}" -A "${inactive}" \
-i "${EXT_IF}" -o awg0 -d "${VPN_CIDR}" \
-m conntrack --ctstate ESTABLISHED,RELATED \
-m comment --comment amneziawg-internet-return -j ACCEPT
"${IPTABLES[@]}" -A "${inactive}" -j RETURN
# Insert the complete new policy before removing the old jump. There is
# never a window with a partially built or absent active policy.
"${IPTABLES[@]}" -I "${FORWARD_CHAIN}" 1 -j "${inactive}"
if [ -n "${active}" ]; then
"${IPTABLES[@]}" -D "${FORWARD_CHAIN}" -j "${active}"
fi
}
@@ -55,13 +142,40 @@ data:
fi
sysctl -w net.ipv4.ip_forward=1
sysctl -w net.ipv4.conf.awg0.send_redirects=0
ensure_insert_rule INPUT -i "${EXT_IF}" -p udp --dport "${PORT}" -m comment --comment amneziawg-allow-external -j ACCEPT
ensure_insert_rule INPUT -i tailscale0 -p udp --dport "${PORT}" -m comment --comment amneziawg-block-tailscale -j DROP
ensure_append_rule INPUT -i awg0 -m comment --comment amneziawg-awg-input -j ACCEPT
ensure_append_rule FORWARD -i awg0 -m comment --comment amneziawg-forward-in -j ACCEPT
ensure_append_rule FORWARD -o awg0 -m comment --comment amneziawg-forward-out -j ACCEPT
ensure_append_rule -t nat POSTROUTING -s "${VPN_CIDR}" -o "${EXT_IF}" -m comment --comment amneziawg-masquerade -j MASQUERADE
# Remove rules written by older revisions. In particular, the unqualified
# tailscale UDP DROP also blocks Flannel VXLAN (UDP/8472).
delete_rule INPUT -i tailscale0 -p udp -m comment --comment amneziawg-block-tailscale -j DROP
delete_rule INPUT -i "${EXT_IF}" -p udp -m comment --comment amneziawg-allow-external -j ACCEPT
delete_rule INPUT -i "${EXT_IF}" -p udp --dport "${PORT}" -m comment --comment amneziawg-allow-external -j ACCEPT
delete_rule INPUT -i tailscale0 -p udp --dport "${PORT}" -m comment --comment amneziawg-block-tailscale -j DROP
delete_rule INPUT -i awg0 -m comment --comment amneziawg-awg-input -j ACCEPT
delete_rule FORWARD -i awg0 -m comment --comment amneziawg-forward-in -j ACCEPT
delete_rule FORWARD -o awg0 -m comment --comment amneziawg-forward-out -j ACCEPT
delete_rule INPUT -m comment --comment amneziawg-input-jump -j AMNEZIAWG-INPUT
"${IPTABLES[@]}" -F AMNEZIAWG-INPUT >/dev/null 2>&1 || true
"${IPTABLES[@]}" -X AMNEZIAWG-INPUT >/dev/null 2>&1 || true
ensure_chain "${FORWARD_CHAIN}"
ensure_chain "${POLICY_CHAIN_A}"
ensure_chain "${POLICY_CHAIN_B}"
if ! "${IPTABLES[@]}" -S "${FORWARD_CHAIN}" 2>/dev/null | grep -q -- '-j RETURN'; then
"${IPTABLES[@]}" -A "${FORWARD_CHAIN}" -j RETURN
fi
apply_policy
delete_rule "${FORWARD_CHAIN}" -i awg0 -m comment --comment amneziawg-forward-in -j ACCEPT
delete_rule "${FORWARD_CHAIN}" -o awg0 -m comment --comment amneziawg-forward-out -j ACCEPT
# The jump is first, but non-awg traffic immediately RETURNs to the original
# host FORWARD chain without an ACCEPT or DROP decision.
delete_rule FORWARD -m comment --comment amneziawg-forward-jump -j "${FORWARD_CHAIN}"
"${IPTABLES[@]}" -I FORWARD 1 \
-m comment --comment amneziawg-forward-jump -j "${FORWARD_CHAIN}"
ensure_rule -A -t nat POSTROUTING -s "${VPN_CIDR}" -o "${EXT_IF}" -m comment --comment amneziawg-masquerade -j MASQUERADE
/scripts/firewall-check.sh "${PORT}" "${VPN_CIDR}"
firewall-down.sh: |
#!/usr/bin/env bash
@@ -69,6 +183,10 @@ data:
PORT="${1:-5847}"
VPN_CIDR="${2:-10.8.0.0/16}"
FORWARD_CHAIN="AMNEZIAWG-FORWARD"
POLICY_CHAIN_A="AMNEZIAWG-POLICY-A"
POLICY_CHAIN_B="AMNEZIAWG-POLICY-B"
IPTABLES=(iptables -w 30)
external_interface() {
ip route get 1.1.1.1 | awk '{for (i=1;i<=NF;i++) if ($i=="dev") {print $(i+1); exit}}'
@@ -84,7 +202,7 @@ data:
local chain="$1"
shift
while iptables "${table_args[@]}" -D "${chain}" "$@" >/dev/null 2>&1; do
while "${IPTABLES[@]}" "${table_args[@]}" -D "${chain}" "$@" >/dev/null 2>&1; do
true
done
}
@@ -95,22 +213,97 @@ data:
fi
if [ -n "${EXT_IF}" ]; then
delete_rule INPUT -i "${EXT_IF}" -p udp -m comment --comment amneziawg-allow-external -j ACCEPT
delete_rule INPUT -i "${EXT_IF}" -p udp --dport "${PORT}" -m comment --comment amneziawg-allow-external -j ACCEPT
delete_rule -t nat POSTROUTING -s "${VPN_CIDR}" -o "${EXT_IF}" -m comment --comment amneziawg-masquerade -j MASQUERADE
fi
delete_rule INPUT -i tailscale0 -p udp --dport "${PORT}" -m comment --comment amneziawg-block-tailscale -j DROP
delete_rule INPUT -i tailscale0 -p udp -m comment --comment amneziawg-block-tailscale -j DROP
delete_rule INPUT -i awg0 -m comment --comment amneziawg-awg-input -j ACCEPT
delete_rule FORWARD -i awg0 -m comment --comment amneziawg-forward-in -j ACCEPT
delete_rule FORWARD -o awg0 -m comment --comment amneziawg-forward-out -j ACCEPT
delete_rule INPUT -m comment --comment amneziawg-input-jump -j AMNEZIAWG-INPUT
delete_rule FORWARD -m comment --comment amneziawg-forward-jump -j "${FORWARD_CHAIN}"
"${IPTABLES[@]}" -F AMNEZIAWG-INPUT >/dev/null 2>&1 || true
"${IPTABLES[@]}" -X AMNEZIAWG-INPUT >/dev/null 2>&1 || true
"${IPTABLES[@]}" -F "${FORWARD_CHAIN}" >/dev/null 2>&1 || true
"${IPTABLES[@]}" -F "${POLICY_CHAIN_A}" >/dev/null 2>&1 || true
"${IPTABLES[@]}" -F "${POLICY_CHAIN_B}" >/dev/null 2>&1 || true
"${IPTABLES[@]}" -X "${POLICY_CHAIN_A}" >/dev/null 2>&1 || true
"${IPTABLES[@]}" -X "${POLICY_CHAIN_B}" >/dev/null 2>&1 || true
"${IPTABLES[@]}" -X "${FORWARD_CHAIN}" >/dev/null 2>&1 || true
firewall-check.sh: |
#!/usr/bin/env bash
set -euo pipefail
PORT="${1:-5847}"
VPN_CIDR="${2:-10.8.0.0/16}"
FORWARD_CHAIN="AMNEZIAWG-FORWARD"
IPTABLES=(iptables -w 5)
external_interface() {
ip route get 1.1.1.1 | awk '{for (i=1;i<=NF;i++) if ($i=="dev") {print $(i+1); exit}}'
}
EXT_IF="$(external_interface || true)"
if [ -z "${EXT_IF}" ]; then
EXT_IF="$(ip route show default | awk '{print $5; exit}')"
fi
if [ -z "${EXT_IF}" ]; then
echo "Unable to detect external interface" >&2
exit 1
fi
if "${IPTABLES[@]}" -C INPUT -i tailscale0 -p udp -m comment --comment amneziawg-block-tailscale -j DROP >/dev/null 2>&1; then
echo "Unsafe broad UDP DROP on tailscale0 is present" >&2
exit 1
fi
if "${IPTABLES[@]}" -C INPUT -i "${EXT_IF}" -p udp -m comment --comment amneziawg-allow-external -j ACCEPT >/dev/null 2>&1; then
echo "Unsafe broad UDP ACCEPT on ${EXT_IF} is present" >&2
exit 1
fi
if "${IPTABLES[@]}" -C INPUT -m comment --comment amneziawg-input-jump -j AMNEZIAWG-INPUT >/dev/null 2>&1; then
echo "Unexpected AmneziaWG INPUT chain is present" >&2
exit 1
fi
FIRST_FORWARD_RULE="$("${IPTABLES[@]}" -S FORWARD | grep '^-A FORWARD ' | sed -n '1p')"
[[ "${FIRST_FORWARD_RULE}" == *"--comment amneziawg-forward-jump"* ]]
[[ "${FIRST_FORWARD_RULE}" == *"-j ${FORWARD_CHAIN}"* ]]
ACTIVE_POLICY="$("${IPTABLES[@]}" -S "${FORWARD_CHAIN}" 2>/dev/null \
| awk '$1 == "-A" && $3 == "-j" && $4 ~ /^AMNEZIAWG-POLICY-[AB]$/ { print $4; exit }')"
[ -n "${ACTIVE_POLICY}" ]
"${IPTABLES[@]}" -C "${ACTIVE_POLICY}" \
-i awg0 -o awg0 -s "${VPN_CIDR}" -d "${VPN_CIDR}" \
-m comment --comment amneziawg-client-isolation -j DROP
"${IPTABLES[@]}" -C "${ACTIVE_POLICY}" \
-i awg0 -o "${EXT_IF}" -s "${VPN_CIDR}" \
-m conntrack --ctstate NEW,ESTABLISHED,RELATED \
-m comment --comment amneziawg-internet-out -j ACCEPT
"${IPTABLES[@]}" -C "${ACTIVE_POLICY}" \
-i "${EXT_IF}" -o awg0 -d "${VPN_CIDR}" \
-m conntrack --ctstate ESTABLISHED,RELATED \
-m comment --comment amneziawg-internet-return -j ACCEPT
"${IPTABLES[@]}" -t nat -C POSTROUTING -s "${VPN_CIDR}" -o "${EXT_IF}" -m comment --comment amneziawg-masquerade -j MASQUERADE
[ "$(sysctl -n net.ipv4.conf.awg0.send_redirects)" = "0" ]
awg show awg0 >/dev/null 2>&1
run.sh: |
#!/usr/bin/env bash
set -euo pipefail
SERVER_CONFIG="/etc/amnezia/server/awg0.conf"
CLIENTS_DIR="/etc/amnezia/clients"
CLIENTS_DIR="${AMNEZIAWG_CLIENTS_DIR:-/run/amnezia/clients}"
POLICY_FILE="${AMNEZIAWG_POLICY_FILE:-/run/amnezia/policy/policy.conf}"
CONFIG_GENERATION="${AMNEZIAWG_CONFIG_GENERATION:-/run/amnezia/config-generation}"
RUNTIME_CONFIG="/run/amnezia/awg0.conf"
SYNC_CONFIG="/run/amnezia/awg0.sync.conf"
STATUS_FILE="/run/amnezia/reload-status"
RELOAD_INTERVAL="${AMNEZIAWG_RELOAD_INTERVAL:-10}"
cleanup() {
if awg show awg0 >/dev/null 2>&1; then
@@ -120,32 +313,219 @@ data:
render_config() {
mkdir -p "$(dirname "${RUNTIME_CONFIG}")"
cp "${SERVER_CONFIG}" "${RUNTIME_CONFIG}"
chmod 0600 "${RUNTIME_CONFIG}"
local tmp_config="${RUNTIME_CONFIG}.tmp"
cp "${SERVER_CONFIG}" "${tmp_config}"
chmod 0600 "${tmp_config}"
local clients_found=0
for client_config in "${CLIENTS_DIR}"/*; do
[ -f "${client_config}" ] || continue
[ -s "${client_config}" ] || continue
printf '\n' >> "${RUNTIME_CONFIG}"
cat "${client_config}" >> "${RUNTIME_CONFIG}"
printf '\n' >> "${tmp_config}"
cat "${client_config}" >> "${tmp_config}"
clients_found=1
done
if [ "${clients_found}" = "0" ]; then
echo "No client peer configs found in ${CLIENTS_DIR}; starting without peers"
fi
mv "${tmp_config}" "${RUNTIME_CONFIG}"
chmod 0600 "${RUNTIME_CONFIG}"
}
runtime_config_hash() {
if [ -f "${CONFIG_GENERATION}" ]; then
sha256sum "${CONFIG_GENERATION}" | awk '{print $1}'
else
printf 'missing\n'
fi
}
write_reload_status() {
local state="${1}"
local hash="${2:-}"
local applied_at_ms=""
if [ "${state}" = "applied" ]; then
applied_at_ms="$(($(date +%s) * 1000))"
fi
mkdir -p "$(dirname "${STATUS_FILE}")"
{
printf 'state=%s\n' "${state}"
printf 'hash=%s\n' "${hash}"
printf 'applied_at_ms=%s\n' "${applied_at_ms}"
} > "${STATUS_FILE}.tmp"
mv "${STATUS_FILE}.tmp" "${STATUS_FILE}"
}
apply_live_config() {
# Fail closed during a peer/key and policy transition. /dev/null produces
# an Internet-only policy with all awg0-to-awg0 traffic isolated.
/scripts/firewall-up.sh 5847 10.8.0.0/16 /dev/null
render_config
awg-quick strip "${RUNTIME_CONFIG}" > "${SYNC_CONFIG}"
chmod 0600 "${SYNC_CONFIG}"
awg syncconf awg0 "${SYNC_CONFIG}"
/scripts/firewall-up.sh 5847 10.8.0.0/16 "${POLICY_FILE}"
}
watch_client_config() {
local last_hash="${1}"
while true; do
sleep "${RELOAD_INTERVAL}" &
wait "$!" || return 0
local current_hash
current_hash="$(runtime_config_hash)"
if [ "${current_hash}" = "${last_hash}" ]; then
continue
fi
echo "Detected AmneziaWG client peer config change; applying with awg syncconf"
if apply_live_config; then
last_hash="${current_hash}"
write_reload_status applied "${current_hash}"
awg show awg0 || true
else
echo "ERROR: failed to hot-reload AmneziaWG client peer config" >&2
write_reload_status error "${current_hash}"
fi
done
}
trap cleanup EXIT
trap 'exit 0' TERM INT
initial_hash="$(runtime_config_hash)"
render_config
cleanup
awg-quick up "${RUNTIME_CONFIG}"
awg show awg0 || true
write_reload_status applied "${initial_hash}"
watch_client_config "${initial_hash}"
client-secret-sync.sh: |
#!/usr/bin/env bash
set -euo pipefail
CLIENT_SECRET="${AMNEZIAWG_CLIENT_SECRET:-amneziawg-clients}"
CLIENT_SECRET_KEY="${AMNEZIAWG_CLIENT_SECRET_KEY:-peers.conf}"
CLIENTS_DIR="${AMNEZIAWG_CLIENTS_DIR:-/run/amnezia/clients}"
PEERS_FILE="${CLIENTS_DIR}/peers.conf"
POLICY_FILE="${AMNEZIAWG_POLICY_FILE:-/run/amnezia/policy/policy.conf}"
CONFIG_GENERATION="${AMNEZIAWG_CONFIG_GENERATION:-/run/amnezia/config-generation}"
SYNC_INTERVAL="${AMNEZIAWG_CLIENT_SECRET_SYNC_INTERVAL:-5}"
NAMESPACE="${POD_NAMESPACE:-$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)}"
write_empty_once() {
mkdir -p "${CLIENTS_DIR}"
if [ ! -f "${PEERS_FILE}" ]; then
: > "${PEERS_FILE}"
chmod 0600 "${PEERS_FILE}"
fi
mkdir -p "$(dirname "${POLICY_FILE}")"
if [ ! -f "${POLICY_FILE}" ]; then
: > "${POLICY_FILE}"
chmod 0600 "${POLICY_FILE}"
fi
{
sha256sum "${PEERS_FILE}"
sha256sum "${POLICY_FILE}"
} | sha256sum | awk '{print $1}' > "${CONFIG_GENERATION}"
}
sync_once() {
mkdir -p "${CLIENTS_DIR}"
mkdir -p "$(dirname "${POLICY_FILE}")"
local snapshot="${CONFIG_GENERATION}.snapshot"
local peers_tmp="${PEERS_FILE}.tmp"
local policy_tmp="${POLICY_FILE}.tmp"
local peers_encoded policy_encoded generation
if ! kubectl get secret "${CLIENT_SECRET}" -n "${NAMESPACE}" \
-o "go-template={{ with index .data \"${CLIENT_SECRET_KEY}\" }}{{ . }}{{ end }}{{ \"\n\" }}{{ with index .data \"policy.conf\" }}{{ . }}{{ end }}{{ \"\n\" }}" \
> "${snapshot}" 2>/dev/null; then
echo "WARN: failed to read Secret ${NAMESPACE}/${CLIENT_SECRET}; keeping current peers" >&2
rm -f "${snapshot}"
write_empty_once
return 0
fi
peers_encoded="$(sed -n '1p' "${snapshot}")"
policy_encoded="$(sed -n '2p' "${snapshot}")"
rm -f "${snapshot}"
if [ -n "${peers_encoded}" ]; then
printf '%s' "${peers_encoded}" | base64 -d > "${peers_tmp}"
else
: > "${peers_tmp}"
fi
if [ -n "${policy_encoded}" ]; then
printf '%s' "${policy_encoded}" | base64 -d > "${policy_tmp}"
else
# Missing policy is fail-closed: no client-to-client pairs are allowed.
: > "${policy_tmp}"
fi
chmod 0600 "${peers_tmp}" "${policy_tmp}"
generation="$({
sha256sum "${peers_tmp}"
sha256sum "${policy_tmp}"
} | sha256sum | awk '{print $1}')"
if [ -f "${CONFIG_GENERATION}" ] \
&& [ "$(sed -n '1p' "${CONFIG_GENERATION}")" = "${generation}" ]; then
rm -f "${peers_tmp}" "${policy_tmp}"
return 0
fi
mv "${policy_tmp}" "${POLICY_FILE}"
mv "${peers_tmp}" "${PEERS_FILE}"
printf '%s\n' "${generation}" > "${CONFIG_GENERATION}.tmp"
mv "${CONFIG_GENERATION}.tmp" "${CONFIG_GENERATION}"
echo "Synced AmneziaWG peers and policy from Secret ${NAMESPACE}/${CLIENT_SECRET}"
}
if [ "${1:-}" = "once" ]; then
sync_once
exit 0
fi
while true; do
sleep 3600 &
wait "$!"
sync_once || true
sleep "${SYNC_INTERVAL}"
done
status-patch.sh: |
#!/usr/bin/env bash
set -euo pipefail
STATUS_FILE="/run/amnezia/reload-status"
PATCH_INTERVAL="${AMNEZIAWG_STATUS_PATCH_INTERVAL:-5}"
NAMESPACE="${POD_NAMESPACE:-$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)}"
: "${POD_NAME:?POD_NAME is required}"
last_file_hash=""
patch_status() {
local state="unknown"
local hash=""
local applied_at_ms=""
# The file is generated by run.sh and contains only shell assignments.
# shellcheck disable=SC1090
source "${STATUS_FILE}"
kubectl patch pod "${POD_NAME}" -n "${NAMESPACE}" --type merge -p "{\"metadata\":{\"annotations\":{\"amnezia-fellow.hexor.cy/client-secret-reload-status\":\"${state}\",\"amnezia-fellow.hexor.cy/client-secret-applied-at-ms\":\"${applied_at_ms}\",\"amnezia-fellow.hexor.cy/client-secret-applied-hash\":\"${hash}\"}}}"
}
while true; do
if [ -f "${STATUS_FILE}" ]; then
file_hash="$(sha256sum "${STATUS_FILE}" | awk '{print $1}')"
if [ "${file_hash}" != "${last_file_hash}" ]; then
patch_status || true
last_file_hash="${file_hash}"
fi
fi
sleep "${PATCH_INTERVAL}"
done
+161 -15
View File
@@ -6,8 +6,9 @@ metadata:
labels:
app: amneziawg
annotations:
reloader.stakater.com/auto: "true"
secret.reloader.stakater.com/reload: "amneziawg-server,amneziawg-clients"
reloader.stakater.com/auto: "false"
secret.reloader.stakater.com/reload: "amneziawg-server"
configmap.reloader.stakater.com/reload: "amneziawg-scripts,amneziawg-exporter-redis"
spec:
selector:
matchLabels:
@@ -27,6 +28,21 @@ spec:
tolerations:
- operator: Exists
initContainers:
- name: install-awg
image: amneziavpn/amneziawg-go:latest
imagePullPolicy: IfNotPresent
command:
- /bin/bash
- -lc
- |
set -euo pipefail
cp /usr/bin/awg /shared-bin/awg
cp /lib/ld-musl-x86_64.so.1 /shared-bin/ld-musl-x86_64.so.1
cp /lib/ld-musl-x86_64.so.1 /shared-bin/libc.musl-x86_64.so.1
chmod 0755 /shared-bin/awg /shared-bin/ld-musl-x86_64.so.1 /shared-bin/libc.musl-x86_64.so.1
volumeMounts:
- name: awg-bin
mountPath: /shared-bin
- name: register-endpoint
image: bitnami/kubectl:latest
imagePullPolicy: IfNotPresent
@@ -65,6 +81,26 @@ spec:
kubectl create secret generic amneziawg-endpoints -n "${NAMESPACE}" \
--from-literal="${NODE_NAME}=${VALUE}"
fi
- name: sync-client-secret
image: bitnami/kubectl:latest
imagePullPolicy: IfNotPresent
command:
- /bin/bash
- /scripts/client-secret-sync.sh
- once
resources:
requests:
memory: "32Mi"
cpu: "10m"
limits:
memory: "128Mi"
cpu: "100m"
volumeMounts:
- name: scripts
mountPath: /scripts
readOnly: true
- name: runtime-config
mountPath: /run/amnezia
containers:
- name: amneziawg
image: amneziavpn/amneziawg-go:latest
@@ -87,20 +123,29 @@ spec:
command:
- /bin/bash
- -lc
- awg show awg0 >/dev/null 2>&1
- /scripts/firewall-check.sh 5847 10.8.0.0/16
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
timeoutSeconds: 8
failureThreshold: 2
startupProbe:
exec:
command:
- /bin/bash
- -lc
- /scripts/firewall-check.sh 5847 10.8.0.0/16 || /scripts/firewall-up.sh 5847 10.8.0.0/16
periodSeconds: 5
timeoutSeconds: 15
failureThreshold: 12
livenessProbe:
exec:
command:
- /bin/bash
- -lc
- awg show awg0 >/dev/null 2>&1
- /scripts/firewall-check.sh 5847 10.8.0.0/16 || /scripts/firewall-up.sh 5847 10.8.0.0/16
initialDelaySeconds: 30
periodSeconds: 30
timeoutSeconds: 5
timeoutSeconds: 15
failureThreshold: 3
resources:
requests:
@@ -113,9 +158,6 @@ spec:
- name: server-config
mountPath: /etc/amnezia/server
readOnly: true
- name: client-config
mountPath: /etc/amnezia/clients
readOnly: true
- name: scripts
mountPath: /scripts
readOnly: true
@@ -123,6 +165,108 @@ spec:
mountPath: /run/amnezia
- name: dev-net-tun
mountPath: /dev/net/tun
- name: reload-status
image: bitnami/kubectl:latest
imagePullPolicy: IfNotPresent
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
command:
- /bin/bash
- /scripts/status-patch.sh
resources:
requests:
memory: "32Mi"
cpu: "10m"
limits:
memory: "128Mi"
cpu: "100m"
volumeMounts:
- name: scripts
mountPath: /scripts
readOnly: true
- name: runtime-config
mountPath: /run/amnezia
- name: client-secret-sync
image: bitnami/kubectl:latest
imagePullPolicy: IfNotPresent
command:
- /bin/bash
- /scripts/client-secret-sync.sh
resources:
requests:
memory: "32Mi"
cpu: "10m"
limits:
memory: "128Mi"
cpu: "100m"
volumeMounts:
- name: scripts
mountPath: /scripts
readOnly: true
- name: runtime-config
mountPath: /run/amnezia
- name: amneziawg-exporter-redis
image: redis:alpine
imagePullPolicy: IfNotPresent
command:
- redis-server
- /etc/redis/redis.conf
ports:
- name: redis
containerPort: 6379
protocol: TCP
resources:
requests:
memory: "32Mi"
cpu: "10m"
limits:
memory: "128Mi"
cpu: "100m"
volumeMounts:
- name: exporter-redis-config
mountPath: /etc/redis
readOnly: true
- name: exporter-redis-data
mountPath: /data
- name: amneziawg-exporter
image: amneziavpn/amneziawg-exporter:latest
imagePullPolicy: IfNotPresent
securityContext:
capabilities:
add:
- NET_ADMIN
env:
- name: AWG_EXPORTER_REDIS_HOST
value: "127.0.0.1"
- name: AWG_EXPORTER_REDIS_PORT
value: "6379"
ports:
- name: metrics
containerPort: 9351
protocol: TCP
resources:
requests:
memory: "64Mi"
cpu: "25m"
limits:
memory: "256Mi"
cpu: "200m"
volumeMounts:
- name: awg-bin
mountPath: /usr/bin/awg
subPath: awg
readOnly: true
- name: awg-bin
mountPath: /lib/ld-musl-x86_64.so.1
subPath: ld-musl-x86_64.so.1
readOnly: true
- name: awg-bin
mountPath: /lib/libc.musl-x86_64.so.1
subPath: libc.musl-x86_64.so.1
readOnly: true
volumes:
- name: server-config
secret:
@@ -131,17 +275,19 @@ spec:
items:
- key: awg0.conf
path: awg0.conf
- name: client-config
secret:
secretName: amneziawg-clients
optional: true
defaultMode: 0600
- name: scripts
configMap:
name: amneziawg-scripts
defaultMode: 0755
- name: runtime-config
emptyDir: {}
- name: awg-bin
emptyDir: {}
- name: exporter-redis-config
configMap:
name: amneziawg-exporter-redis
- name: exporter-redis-data
emptyDir: {}
- name: dev-net-tun
hostPath:
path: /dev/net/tun
@@ -0,0 +1,32 @@
---
apiVersion: v1
kind: ConfigMap
metadata:
name: amneziawg-exporter-redis
labels:
app: amneziawg
component: exporter
data:
redis.conf: |
bind 127.0.0.1
protected-mode yes
port 6379
tcp-backlog 511
timeout 0
tcp-keepalive 300
daemonize no
pidfile /run/redis.pid
loglevel warning
logfile ""
databases 16
always-show-logo no
set-proc-title no
save ""
appendonly no
stop-writes-on-bgsave-error no
rdbcompression yes
rdbchecksum yes
dir /data
rename-command CONFIG ""
rename-command SAVE ""
rename-command BGSAVE ""
+17
View File
@@ -0,0 +1,17 @@
---
apiVersion: v1
kind: Service
metadata:
name: amneziawg-exporter
labels:
app: amneziawg
component: exporter
spec:
type: ClusterIP
selector:
app: amneziawg
ports:
- name: metrics
protocol: TCP
port: 9351
targetPort: 9351
+29
View File
@@ -48,3 +48,32 @@ spec:
remoteRef:
key: 3092dc7c-41dd-461a-9f7a-377727f47e93
property: fields[1].value
---
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: amnezia-fellow
spec:
target:
name: amnezia-fellow
deletionPolicy: Delete
template:
engineVersion: v2
type: Opaque
data:
database-url: |-
postgresql://amnezia_fellow:{{ .amnezia_fellow | urlquery }}@psql.psql.svc:5432/amnezia_fellow
postgres-password: |-
{{ .amnezia_fellow }}
data:
- secretKey: amnezia_fellow
sourceRef:
storeRef:
name: vaultwarden-login
kind: ClusterSecretStore
remoteRef:
conversionStrategy: Default
decodingStrategy: None
metadataPolicy: None
key: 2a9deb39-ef22-433e-a1be-df1555625e22
property: fields[19].value
+72
View File
@@ -0,0 +1,72 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: amnezia-fellow
labels:
app: amnezia-fellow
annotations:
secret.reloader.stakater.com/reload: "amnezia-fellow"
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: amnezia-fellow
template:
metadata:
labels:
app: amnezia-fellow
spec:
serviceAccountName: amnezia-fellow
nodeSelector:
kubernetes.io/os: linux
kubernetes.io/hostname: cy.tail2fe2d.ts.net
containers:
- name: amnezia-fellow
image: ultradesu/amnezia-fellow:latest
imagePullPolicy: Always
ports:
- name: http
containerPort: 8000
protocol: TCP
env:
- name: AMNEZIA_FELLOW_DATABASE_URL
valueFrom:
secretKeyRef:
name: amnezia-fellow
key: database-url
- name: AMNEZIA_FELLOW_K8S_NAMESPACE
value: "amnezia"
- name: AMNEZIA_FELLOW_K8S_CLIENTS_SECRET
value: "amneziawg-clients"
- name: AMNEZIA_FELLOW_K8S_CLIENTS_SECRET_KEY
value: "peers.conf"
- name: AMNEZIA_FELLOW_K8S_SERVER_SECRET
value: "amneziawg-server"
- name: AMNEZIA_FELLOW_K8S_ENDPOINTS_SECRET
value: "amneziawg-endpoints"
- name: AMNEZIA_FELLOW_VPN_CLIENT_CIDR
value: "10.8.0.0/16"
- name: AMNEZIA_FELLOW_VPN_MTU
value: "1376"
readinessProbe:
tcpSocket:
port: http
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 3
livenessProbe:
tcpSocket:
port: http
initialDelaySeconds: 30
periodSeconds: 30
timeoutSeconds: 5
resources:
requests:
cpu: "50m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "512Mi"
+26
View File
@@ -0,0 +1,26 @@
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: amnezia-fellow-tls-ingress
annotations:
cert-manager.io/cluster-issuer: letsencrypt
traefik.ingress.kubernetes.io/router.middlewares: kube-system-https-redirect@kubernetescrd
acme.cert-manager.io/http01-edit-in-place: "true"
spec:
ingressClassName: traefik
rules:
- host: awg.hexor.cy
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: amnezia-fellow
port:
number: 8000
tls:
- secretName: amnezia-fellow-tls
hosts:
- awg.hexor.cy
+35
View File
@@ -0,0 +1,35 @@
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: amnezia-fellow
labels:
app: amnezia-fellow
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: amnezia-fellow
labels:
app: amnezia-fellow
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "create", "update", "patch"]
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: amnezia-fellow
labels:
app: amnezia-fellow
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: amnezia-fellow
subjects:
- kind: ServiceAccount
name: amnezia-fellow
+16
View File
@@ -0,0 +1,16 @@
---
apiVersion: v1
kind: Service
metadata:
name: amnezia-fellow
labels:
app: amnezia-fellow
spec:
type: ClusterIP
selector:
app: amnezia-fellow
ports:
- name: http
protocol: TCP
port: 8000
targetPort: 8000
+14
View File
@@ -0,0 +1,14 @@
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: amnezia-fellow-data
labels:
app: amnezia-fellow
spec:
accessModes:
- ReadWriteOnce
storageClassName: longhorn
resources:
requests:
storage: 3Gi
+8
View File
@@ -7,4 +7,12 @@ resources:
- external-secrets.yaml
- configmap-scripts.yaml
- rbac.yaml
- fellow-rbac.yaml
- fellow-storage.yaml
- fellow-service.yaml
- fellow-ingress.yaml
- fellow-deployment.yaml
- exporter-redis-configmap.yaml
- exporter-service.yaml
- servicemonitor.yaml
- daemonset.yaml
+3
View File
@@ -42,6 +42,9 @@ rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "create", "patch"]
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
+23
View File
@@ -0,0 +1,23 @@
---
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: amneziawg-exporter
labels:
app: amneziawg
component: exporter
release: prometheus
spec:
selector:
matchLabels:
app: amneziawg
component: exporter
endpoints:
- port: metrics
path: /metrics
interval: 30s
scrapeTimeout: 10s
honorLabels: true
namespaceSelector:
matchNames:
- amnezia
+20
View File
@@ -0,0 +1,20 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: doka2-lobby-list
namespace: argocd
spec:
project: apps
destination:
namespace: doka2-lobby-list
server: https://kubernetes.default.svc
source:
repoURL: ssh://git@gt.hexor.cy:30022/ab/homelab.git
targetRevision: HEAD
path: k8s/apps/doka2-lobby-list
syncPolicy:
automated:
selfHeal: true
prune: true
syncOptions:
- CreateNamespace=true
+54
View File
@@ -0,0 +1,54 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: doka2-lobby-list
labels:
app: doka2-lobby-list
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: doka2-lobby-list
template:
metadata:
labels:
app: doka2-lobby-list
spec:
nodeSelector:
kubernetes.io/os: linux
securityContext:
fsGroup: 1000
fsGroupChangePolicy: OnRootMismatch
volumes:
- name: creds
persistentVolumeClaim:
claimName: doka2-lobby-list-creds
containers:
- name: doka2-lobby-list
image: ultradesu/doka2-lobby-list:latest
imagePullPolicy: Always
ports:
- name: http
containerPort: 3000
protocol: TCP
env:
- name: ADMIN_PASSWORD
valueFrom:
secretKeyRef:
name: doka2-lobby-list-secrets
key: ADMIN_PASSWORD
- name: CREDS_DIR
value: /data
volumeMounts:
- name: creds
mountPath: /data
resources:
requests:
cpu: "50m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "512Mi"
@@ -0,0 +1,26 @@
---
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: doka2-lobby-list-secrets
spec:
target:
name: doka2-lobby-list-secrets
deletionPolicy: Delete
template:
type: Opaque
data:
ADMIN_PASSWORD: |-
{{ .admin_password | trim }}
data:
- secretKey: admin_password
sourceRef:
storeRef:
name: vaultwarden-login
kind: ClusterSecretStore
remoteRef:
conversionStrategy: Default
decodingStrategy: None
metadataPolicy: None
key: af4618dd-6431-4b20-b617-c00bae9ceff6
property: login.password
+26
View File
@@ -0,0 +1,26 @@
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: doka2-lobby-list-tls-ingress
annotations:
cert-manager.io/cluster-issuer: letsencrypt
traefik.ingress.kubernetes.io/router.middlewares: kube-system-https-redirect@kubernetescrd
acme.cert-manager.io/http01-edit-in-place: "true"
spec:
ingressClassName: traefik
rules:
- host: doka.hexor.cy
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: doka2-lobby-list
port:
number: 80
tls:
- secretName: doka2-lobby-list-tls
hosts:
- doka.hexor.cy
@@ -0,0 +1,10 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- app.yaml
- deployment.yaml
- external-secrets.yaml
- service.yaml
- ingress.yaml
- storage.yaml
+16
View File
@@ -0,0 +1,16 @@
---
apiVersion: v1
kind: Service
metadata:
name: doka2-lobby-list
labels:
app: doka2-lobby-list
spec:
type: ClusterIP
selector:
app: doka2-lobby-list
ports:
- port: 80
targetPort: http
protocol: TCP
name: http
+14
View File
@@ -0,0 +1,14 @@
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: doka2-lobby-list-creds
labels:
app: doka2-lobby-list
spec:
accessModes:
- ReadWriteOnce
storageClassName: longhorn
resources:
requests:
storage: 1Gi
+21
View File
@@ -0,0 +1,21 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: firefly-iii
namespace: argocd
spec:
project: apps
destination:
namespace: firefly-iii
server: https://kubernetes.default.svc
source:
repoURL: ssh://git@gt.hexor.cy:30022/ab/homelab.git
targetRevision: HEAD
path: k8s/apps/firefly-iii
syncPolicy:
automated:
selfHeal: true
prune: true
syncOptions:
- CreateNamespace=true
@@ -0,0 +1,53 @@
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: firefly-data-importer-auth
spec:
forwardAuth:
address: http://auth-proxy.auth-proxy.svc:80/auth
trustForwardHeader: true
authResponseHeaders:
- X-Auth-Request-User
- X-Auth-Request-Email
- X-Auth-Request-Groups
---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: firefly-data-importer
spec:
entryPoints:
- websecure
routes:
# OAuth callback is intentionally public. Data Importer validates both the
# state parameter and its browser session before exchanging the code.
- match: Host(`import.hexor.cy`) && Path(`/eb-callback`)
kind: Rule
priority: 200
services:
- name: firefly-data-importer
port: 80
# Everything else requires a valid auth-proxy session and group.
- match: Host(`import.hexor.cy`)
kind: Rule
priority: 100
middlewares:
- name: firefly-data-importer-auth
services:
- name: firefly-data-importer
port: 80
tls:
secretName: firefly-data-importer-tls
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: firefly-data-importer-tls
spec:
secretName: firefly-data-importer-tls
issuerRef:
name: letsencrypt
kind: ClusterIssuer
dnsNames:
- import.hexor.cy
+105
View File
@@ -0,0 +1,105 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: firefly-data-importer
labels:
app.kubernetes.io/name: firefly-data-importer
annotations:
reloader.stakater.com/auto: "true"
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app.kubernetes.io/name: firefly-data-importer
template:
metadata:
labels:
app.kubernetes.io/name: firefly-data-importer
spec:
nodeSelector:
kubernetes.io/hostname: ai.tail2fe2d.ts.net
tolerations:
- key: workload
operator: Equal
value: ai
effect: NoSchedule
containers:
- name: data-importer
image: fireflyiii/data-importer:version-2.3.4
imagePullPolicy: IfNotPresent
env:
- name: FIREFLY_III_URL
value: http://firefly-iii
- name: VANITY_URL
value: http://ff.lan
- name: TRUSTED_PROXIES
value: "**"
- name: EXPECT_SECURE_URL
value: "false"
- name: VERIFY_TLS_SECURITY
value: "true"
- name: TZ
value: Europe/London
- name: APP_ENV
value: production
- name: APP_DEBUG
value: "false"
- name: LOG_LEVEL
value: notice
- name: ENABLE_BANKING_APP_ID
valueFrom:
secretKeyRef:
name: firefly-data-importer-secrets
key: ENABLE_BANKING_APP_ID
- name: ENABLE_BANKING_PRIVATE_KEY
valueFrom:
secretKeyRef:
name: firefly-data-importer-secrets
key: ENABLE_BANKING_PRIVATE_KEY
ports:
- name: http
containerPort: 8080
protocol: TCP
readinessProbe:
tcpSocket:
port: http
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
tcpSocket:
port: http
initialDelaySeconds: 20
periodSeconds: 30
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
cpu: "1"
memory: 512Mi
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
securityContext:
seccompProfile:
type: RuntimeDefault
---
apiVersion: v1
kind: Service
metadata:
name: firefly-data-importer
labels:
app.kubernetes.io/name: firefly-data-importer
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: firefly-data-importer
ports:
- name: http
port: 80
targetPort: http
protocol: TCP
@@ -0,0 +1,85 @@
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: firefly-secrets
spec:
refreshInterval: 1h
target:
name: firefly-secrets
creationPolicy: Owner
deletionPolicy: Delete
template:
type: Opaque
data:
# Firefly and CloudNativePG deliberately share this generated Secret.
username: firefly
password: |-
{{ .db_password }}
DB_PASSWORD: |-
{{ .db_password }}
APP_KEY: |-
{{ .app_key }}
STATIC_CRON_TOKEN: |-
{{ .cron_token }}
data:
- secretKey: db_password
sourceRef:
storeRef:
name: vaultwarden-login
kind: ClusterSecretStore
remoteRef:
key: a1867c81-715c-47cd-978d-14ea5bcedea9
property: fields[0].value
- secretKey: app_key
sourceRef:
storeRef:
name: vaultwarden-login
kind: ClusterSecretStore
remoteRef:
key: a1867c81-715c-47cd-978d-14ea5bcedea9
property: fields[1].value
- secretKey: cron_token
sourceRef:
storeRef:
name: vaultwarden-login
kind: ClusterSecretStore
remoteRef:
key: a1867c81-715c-47cd-978d-14ea5bcedea9
property: fields[2].value
---
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: firefly-data-importer-secrets
spec:
refreshInterval: 1h
target:
name: firefly-data-importer-secrets
creationPolicy: Owner
deletionPolicy: Delete
template:
type: Opaque
data:
ENABLE_BANKING_APP_ID: |-
{{ .app_id }}
ENABLE_BANKING_PRIVATE_KEY: |-
-----BEGIN PRIVATE KEY-----
{{ .private_key | replace "-----BEGIN PRIVATE KEY-----" "" | replace "-----END PRIVATE KEY-----" "" | replace " " "" | trim }}
-----END PRIVATE KEY-----
data:
- secretKey: app_id
sourceRef:
storeRef:
name: vaultwarden-login
kind: ClusterSecretStore
remoteRef:
key: a1867c81-715c-47cd-978d-14ea5bcedea9
property: fields[3].value
- secretKey: private_key
sourceRef:
storeRef:
name: vaultwarden-login
kind: ClusterSecretStore
remoteRef:
key: a1867c81-715c-47cd-978d-14ea5bcedea9
property: fields[4].value
+101
View File
@@ -0,0 +1,101 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- app.yaml
- data-importer.yaml
- data-importer-ingress.yaml
- external-secrets.yaml
- postgres.yaml
- storage.yaml
- storage-prep.yaml
- traefik-ai.yaml
- network-policy.yaml
helmCharts:
- name: firefly-iii
repo: https://harish2k01.github.io/helm-charts
version: 0.1.2
releaseName: firefly-iii
namespace: firefly-iii
valuesFile: values.yaml
includeCRDs: true
patches:
# Chart 0.1.2 requires postgres.enabled=true. Remove its StatefulSet and
# make the generated database Service select the CloudNativePG instance.
- target:
group: apps
version: v1
kind: StatefulSet
name: firefly-iii-postgres
patch: |-
$patch: delete
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: firefly-iii-postgres
- target:
version: v1
kind: Service
name: firefly-iii-postgres
patch: |-
- op: replace
path: /spec/selector
value:
cnpg.io/cluster: firefly-postgres
# The upstream chart does not expose scheduling settings for its CronJob.
- target:
group: batch
version: v1
kind: CronJob
name: firefly-iii-cron
patch: |-
- op: add
path: /spec/concurrencyPolicy
value: Forbid
- op: add
path: /spec/jobTemplate/spec/backoffLimit
value: 1
- op: add
path: /spec/jobTemplate/spec/activeDeadlineSeconds
value: 600
- op: add
path: /spec/jobTemplate/spec/template/spec/nodeSelector
value:
kubernetes.io/hostname: ai.tail2fe2d.ts.net
- op: add
path: /spec/jobTemplate/spec/template/spec/tolerations
value:
- key: workload
operator: Equal
value: ai
effect: NoSchedule
- op: replace
path: /spec/jobTemplate/spec/template/spec/containers/0/command
value:
- /bin/sh
- -ec
- >-
curl --fail --silent --show-error
--retry 30 --retry-delay 10 --retry-all-errors
--connect-timeout 5 --max-time 30
"http://firefly-iii:80/api/v1/cron/${STATIC_CRON_TOKEN}"
# A second independent filter in addition to ingressClassName.
- target:
group: networking.k8s.io
version: v1
kind: Ingress
name: firefly-iii
patch: |-
- op: add
path: /metadata/labels/firefly.hexor.cy~1private-ai
value: "true"
# The shared Traefik has the same controller identifier and can discover
# IngressClass objects. The explicit annotation partitions this Ingress
# so only the instance configured with ingressclass=traefik-ai accepts it.
- op: add
path: /metadata/annotations/kubernetes.io~1ingress.class
value: traefik-ai
- op: remove
path: /spec/ingressClassName
+98
View File
@@ -0,0 +1,98 @@
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: firefly-ingress
spec:
podSelector:
matchLabels:
app.kubernetes.io/instance: firefly-iii
app.kubernetes.io/name: firefly-iii
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app.kubernetes.io/name: traefik-ai
# The chart's CronJob uses the same selector labels as the application.
- podSelector:
matchLabels:
app.kubernetes.io/instance: firefly-iii
app.kubernetes.io/name: firefly-iii
- podSelector:
matchLabels:
app.kubernetes.io/name: firefly-data-importer
# hostNetwork traffic is seen as node traffic rather than Pod traffic.
- ipBlock:
cidr: 192.168.1.117/32
- ipBlock:
cidr: 100.77.155.120/32
ports:
- protocol: TCP
port: 8080
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: firefly-postgres-ingress
spec:
podSelector:
matchLabels:
cnpg.io/cluster: firefly-postgres
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app.kubernetes.io/instance: firefly-iii
app.kubernetes.io/name: firefly-iii
- podSelector:
matchLabels:
cnpg.io/cluster: firefly-postgres
ports:
- protocol: TCP
port: 5432
# CloudNativePG operator health/status traffic to the instance manager.
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: psql
podSelector:
matchLabels:
app.kubernetes.io/name: cloudnative-pg
ports:
- protocol: TCP
port: 8000
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: firefly-data-importer-ingress
spec:
podSelector:
matchLabels:
app.kubernetes.io/name: firefly-data-importer
policyTypes:
- Ingress
ingress:
# Shared Traefik serves the public HTTPS importer.
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
app.kubernetes.io/name: traefik
ports:
- protocol: TCP
port: 8080
# Keep node traffic allowed for probes and local diagnostics.
- from:
- ipBlock:
cidr: 192.168.1.117/32
- ipBlock:
cidr: 100.77.155.120/32
ports:
- protocol: TCP
port: 8080
+49
View File
@@ -0,0 +1,49 @@
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: firefly-postgres
spec:
description: PostgreSQL for Firefly III
instances: 1
enableSuperuserAccess: false
primaryUpdateStrategy: unsupervised
bootstrap:
initdb:
database: firefly
owner: firefly
secret:
name: firefly-secrets
dataChecksums: true
storage:
size: 10Gi
storageClass: firefly-local
pvcTemplate:
accessModes:
- ReadWriteOnce
storageClassName: firefly-local
selector:
matchLabels:
app.kubernetes.io/name: firefly-postgres
resources:
requests:
storage: 10Gi
volumeMode: Filesystem
affinity:
enablePodAntiAffinity: false
nodeSelector:
kubernetes.io/hostname: ai.tail2fe2d.ts.net
tolerations:
- key: workload
operator: Equal
value: ai
effect: NoSchedule
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: "1"
memory: 1Gi
monitoring:
enablePodMonitor: true
+52
View File
@@ -0,0 +1,52 @@
apiVersion: batch/v1
kind: Job
metadata:
name: firefly-storage-prep
annotations:
argocd.argoproj.io/hook: PreSync
argocd.argoproj.io/hook-delete-policy: BeforeHookCreation,HookSucceeded
spec:
backoffLimit: 3
template:
metadata:
labels:
app.kubernetes.io/name: firefly-storage-prep
spec:
restartPolicy: OnFailure
nodeSelector:
kubernetes.io/hostname: ai.tail2fe2d.ts.net
tolerations:
- key: workload
operator: Equal
value: ai
effect: NoSchedule
containers:
- name: prepare
image: busybox:1.37.0
imagePullPolicy: IfNotPresent
command:
- /bin/sh
- -ec
- |
mkdir -p /host/k8s/firefly-iii/postgres
chown 26:26 /host/k8s/firefly-iii/postgres
chmod 0700 /host/k8s/firefly-iii/postgres
securityContext:
runAsUser: 0
runAsGroup: 0
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
add:
- CHOWN
- DAC_OVERRIDE
- FOWNER
volumeMounts:
- name: host-root
mountPath: /host/k8s/firefly-iii
volumes:
- name: host-root
hostPath:
path: /k8s/firefly-iii
type: DirectoryOrCreate
+72
View File
@@ -0,0 +1,72 @@
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: firefly-local
provisioner: kubernetes.io/no-provisioner
reclaimPolicy: Retain
volumeBindingMode: WaitForFirstConsumer
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: firefly-postgres
labels:
app.kubernetes.io/name: firefly-postgres
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: firefly-local
volumeMode: Filesystem
hostPath:
path: /k8s/firefly-iii/postgres
type: DirectoryOrCreate
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- ai.tail2fe2d.ts.net
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: firefly-uploads
labels:
app.kubernetes.io/name: firefly-uploads
spec:
capacity:
storage: 5Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: firefly-local
volumeMode: Filesystem
hostPath:
path: /k8s/firefly-iii/uploads
type: DirectoryOrCreate
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- ai.tail2fe2d.ts.net
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: firefly-uploads
spec:
accessModes:
- ReadWriteOnce
storageClassName: firefly-local
volumeName: firefly-uploads
resources:
requests:
storage: 5Gi
+175
View File
@@ -0,0 +1,175 @@
apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
name: traefik-ai
spec:
controller: traefik.io/ingress-controller
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: traefik-ai
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: traefik-ai
rules:
- apiGroups:
- ""
resources:
- services
- secrets
- endpoints
verbs:
- get
- list
- watch
- apiGroups:
- discovery.k8s.io
resources:
- endpointslices
verbs:
- get
- list
- watch
- apiGroups:
- networking.k8s.io
resources:
- ingresses
verbs:
- get
- list
- watch
- apiGroups:
- networking.k8s.io
resources:
- ingresses/status
verbs:
- update
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: traefik-ai
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: traefik-ai
subjects:
- kind: ServiceAccount
name: traefik-ai
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: firefly-traefik-ai
rules:
- apiGroups:
- ""
resources:
- nodes
verbs:
- get
- list
- watch
- apiGroups:
- networking.k8s.io
resources:
- ingressclasses
verbs:
- get
- list
- watch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: firefly-traefik-ai
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: firefly-traefik-ai
subjects:
- kind: ServiceAccount
name: traefik-ai
namespace: firefly-iii
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: traefik-ai
labels:
app.kubernetes.io/name: traefik-ai
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app.kubernetes.io/name: traefik-ai
template:
metadata:
labels:
app.kubernetes.io/name: traefik-ai
spec:
serviceAccountName: traefik-ai
hostNetwork: true
dnsPolicy: ClusterFirstWithHostNet
nodeSelector:
kubernetes.io/hostname: ai.tail2fe2d.ts.net
tolerations:
- key: workload
operator: Equal
value: ai
effect: NoSchedule
containers:
- name: traefik
image: rancher/mirrored-library-traefik:3.6.13
imagePullPolicy: IfNotPresent
args:
- --entrypoints.web.address=192.168.1.117:80
- --providers.kubernetesingress=true
- --providers.kubernetesingress.namespaces=firefly-iii
- --providers.kubernetesingress.ingressclass=traefik-ai
- --providers.kubernetesingress.labelselector=firefly.hexor.cy/private-ai=true
- --providers.kubernetescrd=false
- --api.dashboard=false
- --log.level=INFO
ports:
- name: web
containerPort: 80
hostPort: 80
hostIP: 192.168.1.117
protocol: TCP
readinessProbe:
tcpSocket:
host: 192.168.1.117
port: web
periodSeconds: 10
livenessProbe:
tcpSocket:
host: 192.168.1.117
port: web
periodSeconds: 30
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 500m
memory: 256Mi
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE
readOnlyRootFilesystem: true
runAsNonRoot: false
runAsUser: 0
runAsGroup: 0
securityContext:
seccompProfile:
type: RuntimeDefault
+68
View File
@@ -0,0 +1,68 @@
replicaCount: 1
image:
repository: fireflyiii/core
tag: version-6.6.1
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 80
targetPort: 8080
ingress:
enabled: true
className: traefik-ai
hosts:
- host: ff.lan
paths:
- path: /
pathType: Prefix
persistence:
upload:
enabled: true
existingClaim: firefly-uploads
# Must remain true because chart 0.1.2 validates it. Kustomize removes the
# generated StatefulSet and makes this Service select the CloudNativePG Pod.
postgres:
enabled: true
auth:
database: firefly
username: firefly
service:
port: 5432
cronjob:
enabled: true
schedule: "0 3 * * *"
timeZone: Europe/London
firefly:
env:
APP_ENV: production
APP_DEBUG: "false"
APP_URL: http://ff.lan
SITE_OWNER: owner@ff.lan
TZ: Europe/London
DEFAULT_LANGUAGE: en_US
TRUSTED_PROXIES: 10.42.0.0/16
COOKIE_SECURE: "false"
PGSQL_SSL_MODE: prefer
secrets:
existingSecret: firefly-secrets
nodeSelector:
kubernetes.io/hostname: ai.tail2fe2d.ts.net
tolerations:
- key: workload
operator: Equal
value: ai
effect: NoSchedule
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: "2"
memory: 1Gi
+5 -4
View File
@@ -41,18 +41,18 @@ spec:
- name: GITEA__service__REGISTER_MANUAL_CONFIRM
value: "true"
- name: GITEA__service__ENABLE_CAPTCHA
value: "false"
- name: GITEA__service__REQUIRE_CAPTCHA_FOR_LOGIN
value: "true"
- name: GITEA__service__REQUIRE_CAPTCHA_FOR_LOGIN
value: "false"
- name: GITEA__service__REQUIRE_EXTERNAL_REGISTRATION_CAPTCHA
value: "true"
- name: GITEA__service__CAPTCHA_TYPE
value: "hcaptcha"
value: "cfturnstile"
- name: GITEA__webhook__ALLOWED_HOST_LIST
value: "*"
envFrom:
- secretRef:
name: gitea-recapcha-creds
name: gitea-runner-act-runner-secrets
ports:
- name: http
containerPort: 3000
@@ -79,6 +79,7 @@ spec:
labels:
app: gitea-runner
spec:
serviceAccountName: gitea-runner
dnsConfig:
options:
- name: ndots
+10 -25
View File
@@ -13,6 +13,10 @@ spec:
data:
token: |-
{{ .password }}
GITEA__service__CF_TURNSTILE_SITEKEY: |-
{{ .CF_TURNSTILE_SITEKEY }}
GITEA__service__CF_TURNSTILE_SECRET: |-
{{ .CF_TURNSTILE_SECRET }}
data:
- secretKey: password
sourceRef:
@@ -22,38 +26,19 @@ spec:
remoteRef:
key: e475b5ab-ea3c-48a5-bb4c-a6bc552fc064
property: login.password
---
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: gitea-recapcha-creds
spec:
refreshInterval: 1m
target:
name: gitea-recapcha-creds
deletionPolicy: Delete
template:
type: Opaque
data:
GITEA__service__HCAPTCHA_SITEKEY: |-
{{ .HCAPTCHA_SITEKEY }}
GITEA__service__HCAPTCHA_SECRET: |-
{{ .HCAPTCHA_SECRET }}
data:
- secretKey: HCAPTCHA_SITEKEY
- secretKey: CF_TURNSTILE_SITEKEY
sourceRef:
storeRef:
name: vaultwarden-login
kind: ClusterSecretStore
remoteRef:
key: 89c8d8d2-6b53-42c5-805f-38a341ef163e
property: login.username
- secretKey: HCAPTCHA_SECRET
key: e475b5ab-ea3c-48a5-bb4c-a6bc552fc064
property: fields[0].value
- secretKey: CF_TURNSTILE_SECRET
sourceRef:
storeRef:
name: vaultwarden-login
kind: ClusterSecretStore
remoteRef:
key: 89c8d8d2-6b53-42c5-805f-38a341ef163e
property: login.password
key: e475b5ab-ea3c-48a5-bb4c-a6bc552fc064
property: fields[1].value
+3 -1
View File
@@ -5,6 +5,8 @@ resources:
- app.yaml
- external-secrets.yaml
- deployment.yaml
- runner-rbac.yaml
- runner-rebalance.yaml
- user-unban-cronjob.yaml
- service.yaml
- ingress.yaml
+52
View File
@@ -0,0 +1,52 @@
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: gitea-runner
namespace: gitea
---
apiVersion: v1
kind: Secret
metadata:
name: gitea-runner-kubernetes-token
namespace: gitea
annotations:
kubernetes.io/service-account.name: gitea-runner
type: kubernetes.io/service-account-token
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: auth-proxy-routes-manager
namespace: auth-proxy
rules:
- apiGroups:
- ""
resources:
- configmaps
resourceNames:
- auth-proxy-routes
verbs:
- get
- update
- patch
- apiGroups:
- ""
resources:
- configmaps
verbs:
- create
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: gitea-runner-auth-proxy-routes
namespace: auth-proxy
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: auth-proxy-routes-manager
subjects:
- kind: ServiceAccount
name: gitea-runner
namespace: gitea
+127
View File
@@ -0,0 +1,127 @@
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: gitea-runner-rebalancer
namespace: gitea
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: gitea-runner-rebalancer
namespace: gitea
rules:
- apiGroups:
- ""
resources:
- pods
verbs:
- get
- list
- delete
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: gitea-runner-rebalancer
namespace: gitea
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: gitea-runner-rebalancer
subjects:
- kind: ServiceAccount
name: gitea-runner-rebalancer
namespace: gitea
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: gitea-runner-rebalancer
rules:
- apiGroups:
- ""
resources:
- nodes
resourceNames:
- ai.tail2fe2d.ts.net
verbs:
- get
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: gitea-runner-rebalancer
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: gitea-runner-rebalancer
subjects:
- kind: ServiceAccount
name: gitea-runner-rebalancer
namespace: gitea
---
apiVersion: batch/v1
kind: CronJob
metadata:
name: gitea-runner-rebalancer
namespace: gitea
spec:
schedule: "*/2 * * * *"
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 1
failedJobsHistoryLimit: 3
jobTemplate:
spec:
ttlSecondsAfterFinished: 300
template:
metadata:
labels:
app: gitea-runner-rebalancer
spec:
serviceAccountName: gitea-runner-rebalancer
restartPolicy: Never
containers:
- name: rebalance
image: bitnami/kubectl:latest
imagePullPolicy: IfNotPresent
command:
- /bin/sh
- -ec
- |
target_node="ai.tail2fe2d.ts.net"
ready="$(
kubectl get node "${target_node}" \
-o jsonpath='{range .status.conditions[?(@.type=="Ready")]}{.status}{end}'
)"
if [ "${ready}" != "True" ]; then
echo "${target_node} is not Ready; keeping the runner on its current node"
exit 0
fi
runner_pod="$(
kubectl -n gitea get pods \
-l app=gitea-runner \
--field-selector=status.phase=Running \
-o jsonpath='{.items[0].metadata.name}'
)"
runner_node="$(
kubectl -n gitea get pods \
-l app=gitea-runner \
--field-selector=status.phase=Running \
-o jsonpath='{.items[0].spec.nodeName}'
)"
if [ -z "${runner_pod}" ] || [ -z "${runner_node}" ]; then
echo "No running Gitea runner found"
exit 0
fi
if [ "${runner_node}" = "${target_node}" ]; then
echo "${runner_pod} already runs on ${target_node}"
exit 0
fi
echo "Moving ${runner_pod} from ${runner_node} to preferred node ${target_node}"
kubectl -n gitea delete pod "${runner_pod}" --wait=false
+60
View File
@@ -0,0 +1,60 @@
---
apiVersion: batch/v1
kind: CronJob
metadata:
name: gitea-user-unban
labels:
app: gitea-user-unban
spec:
schedule: "*/10 * * * *"
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 3
jobTemplate:
spec:
template:
metadata:
labels:
app: gitea-user-unban
spec:
restartPolicy: OnFailure
nodeSelector:
kubernetes.io/hostname: master.tail2fe2d.ts.net
volumes:
- name: storage
hostPath:
path: /k8s/gitea
type: Directory
containers:
- name: sqlite-unban
image: 'gitea/gitea:latest'
imagePullPolicy: IfNotPresent
resources:
requests:
memory: "32Mi"
cpu: "10m"
limits:
memory: "128Mi"
cpu: "100m"
command:
- /bin/sh
- -ec
- |
sqlite3 -cmd ".timeout 30000" /data/gitea/gitea.db "
UPDATE \"user\"
SET is_active = 1,
prohibit_login = 0,
updated_unix = unixepoch()
WHERE lower(email) = lower('ab@hexor.cy')
AND (is_active <> 1 OR prohibit_login <> 0);
SELECT printf(
'gitea user watchdog: id=%d login=%s email=%s is_active=%d prohibit_login=%d updated_unix=%d',
id, lower_name, email, is_active, prohibit_login, updated_unix
)
FROM \"user\"
WHERE lower(email) = lower('ab@hexor.cy');
"
volumeMounts:
- name: storage
mountPath: /data
+2
View File
@@ -17,6 +17,8 @@ spec:
labels:
app: teamspeak
spec:
nodeSelector:
kubernetes.io/hostname: music.tail2fe2d.ts.net
volumes:
- name: data
persistentVolumeClaim:
+5
View File
@@ -45,6 +45,11 @@ spec:
value: "info"
- name: WEB_PETTING_DEBUG
value: "false"
- name: WEB_PETTING_DATABASE_URL
valueFrom:
secretKeyRef:
name: web-petting-secrets
key: WEB_PETTING_DATABASE_URL
resources:
requests:
memory: "256Mi"
@@ -0,0 +1,26 @@
---
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: web-petting-secrets
spec:
target:
name: web-petting-secrets
deletionPolicy: Delete
template:
type: Opaque
data:
WEB_PETTING_DATABASE_URL: |-
postgresql://web_petting:{{ .web_petting }}@psql.psql.svc:5432/web_petting
data:
- secretKey: web_petting
sourceRef:
storeRef:
name: vaultwarden-login
kind: ClusterSecretStore
remoteRef:
conversionStrategy: Default
decodingStrategy: None
metadataPolicy: None
key: 2a9deb39-ef22-433e-a1be-df1555625e22
property: fields[20].value
+1
View File
@@ -4,6 +4,7 @@ kind: Kustomization
resources:
- app.yaml
- deployment.yaml
- external-secrets.yaml
- service.yaml
- ingress.yaml
- storage.yaml
+26
View File
@@ -0,0 +1,26 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: argocd-dex-server
namespace: argocd
spec:
template:
spec:
containers:
- name: dex-server
readinessProbe:
tcpSocket:
port: 5556
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 3
successThreshold: 1
failureThreshold: 3
livenessProbe:
tcpSocket:
port: 5556
initialDelaySeconds: 10
periodSeconds: 30
timeoutSeconds: 3
successThreshold: 1
failureThreshold: 3
+3 -1
View File
@@ -10,9 +10,11 @@ resources:
helmCharts:
- name: argo-cd
repo: https://argoproj.github.io/argo-helm
version: 9.4.10
version: 10.2.1
releaseName: argocd
namespace: argocd
valuesFile: values.yaml
includeCRDs: true
patches:
- path: dex-probes-patch.yaml
+1 -1
View File
@@ -6,7 +6,7 @@ metadata:
spec:
project: core
destination:
namespace: longhorn
namespace: longhorn-system
server: https://kubernetes.default.svc
source:
repoURL: ssh://git@gt.hexor.cy:30022/ab/homelab.git
+1 -1
View File
@@ -9,7 +9,7 @@ helmCharts:
repo: https://charts.longhorn.io
version: 1.12.0
releaseName: longhorn
namespace: longhorn
namespace: longhorn-system
valuesFile: values.yaml
includeCRDs: true
+47
View File
@@ -1,7 +1,54 @@
global:
tolerations:
- key: "workload"
operator: "Exists"
effect: "NoSchedule"
- key: "node.kubernetes.io/unreachable"
operator: "Exists"
effect: "NoSchedule"
- key: "node.kubernetes.io/unreachable"
operator: "Exists"
effect: "NoExecute"
longhornManager:
tolerations:
- key: "workload"
operator: "Exists"
effect: "NoSchedule"
- key: "node.kubernetes.io/unreachable"
operator: "Exists"
effect: "NoSchedule"
- key: "node.kubernetes.io/unreachable"
operator: "Exists"
effect: "NoExecute"
longhornDriver:
tolerations:
- key: "workload"
operator: "Exists"
effect: "NoSchedule"
- key: "node.kubernetes.io/unreachable"
operator: "Exists"
effect: "NoSchedule"
- key: "node.kubernetes.io/unreachable"
operator: "Exists"
effect: "NoExecute"
longhornUI:
replicas: 1
tolerations:
- key: "workload"
operator: "Exists"
effect: "NoSchedule"
- key: "node.kubernetes.io/unreachable"
operator: "Exists"
effect: "NoSchedule"
- key: "node.kubernetes.io/unreachable"
operator: "Exists"
effect: "NoExecute"
defaultSettings:
taintToleration: "workload=ai:NoSchedule; workload=desktop:NoSchedule; node.kubernetes.io/unreachable:NoSchedule; node.kubernetes.io/unreachable:NoExecute"
# Keep new instance-manager pods schedulable on nodes with high CPU requests.
guaranteedInstanceManagerCPU: '{"v1":"6","v2":"6"}'
+26
View File
@@ -140,6 +140,10 @@ spec:
{{ .furumi_dev }}
USER_keycloak: |-
{{ .keycloak }}
USER_amnezia_fellow: |-
{{ .amnezia_fellow }}
USER_web_petting: |-
{{ .web_petting }}
data:
- secretKey: authentik
sourceRef:
@@ -339,3 +343,25 @@ spec:
metadataPolicy: None
key: 2a9deb39-ef22-433e-a1be-df1555625e22
property: fields[18].value
- secretKey: amnezia_fellow
sourceRef:
storeRef:
name: vaultwarden-login
kind: ClusterSecretStore
remoteRef:
conversionStrategy: Default
decodingStrategy: None
metadataPolicy: None
key: 2a9deb39-ef22-433e-a1be-df1555625e22
property: fields[19].value
- secretKey: web_petting
sourceRef:
storeRef:
name: vaultwarden-login
kind: ClusterSecretStore
remoteRef:
conversionStrategy: Default
decodingStrategy: None
metadataPolicy: None
key: 2a9deb39-ef22-433e-a1be-df1555625e22
property: fields[20].value
+45
View File
@@ -0,0 +1,45 @@
---
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: auth-proxy
spec:
forwardAuth:
address: http://auth-proxy.auth-proxy.svc:80/auth
trustForwardHeader: true
authResponseHeaders:
- X-Auth-Request-User
- X-Auth-Request-Email
- X-Auth-Request-Groups
---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: prometheus
annotations:
cert-manager.io/cluster-issuer: letsencrypt
spec:
entryPoints:
- websecure
routes:
- match: Host(`prom.hexor.cy`)
kind: Rule
middlewares:
- name: auth-proxy
services:
- name: prometheus-kube-prometheus-prometheus
port: 9090
tls:
secretName: prometheus-tls
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: prometheus-tls
spec:
secretName: prometheus-tls
issuerRef:
name: letsencrypt
kind: ClusterIssuer
dnsNames:
- prom.hexor.cy
+1
View File
@@ -4,6 +4,7 @@ kind: Kustomization
resources:
- persistentVolume.yaml
- external-secrets.yaml
- ingress.yaml
- grafana-alerting-configmap.yaml
- alertmanager-config.yaml
- dashboards/telemt-dashboard-cm.yaml
+2 -3
View File
@@ -1,4 +1,3 @@
alertmanager:
config:
global:
@@ -25,7 +24,7 @@ alertmanager:
{{ end }}
ingress:
enabled: true
enabled: false
ingressClassName: traefik
annotations:
cert-manager.io/cluster-issuer: letsencrypt
@@ -46,7 +45,7 @@ alertmanager:
prometheus:
ingress:
enabled: true
enabled: false
ingressClassName: traefik
annotations:
cert-manager.io/cluster-issuer: letsencrypt
-44
View File
@@ -1,44 +0,0 @@
# This file is maintained automatically by "terraform init".
# Manual edits may be lost in future updates.
provider "registry.terraform.io/goauthentik/authentik" {
version = "2025.12.1"
constraints = ">= 2023.10.0, 2025.12.1"
hashes = [
"h1:p9AGeRqK50wTHEIp7z7O4MUP83cs+lt7wPajZ9m9TB8=",
"zh:0e856d3b13614bc32346a236a8e84ba55ecd17238c2008d4b3e71aa8cb49f515",
"zh:2dcc44cd499c18ebbc4f763eff97a7b725763c8ac8fbb5d69c935413ccdc4962",
"zh:434100fc75ec7cd6b64cc9497e8273e79325fa8d285e9fd9d341c1a67421643b",
"zh:483484f66d2e8ce6fa4bfd91e824ceebf07d10acb5df5f366397c55227c4ae91",
"zh:596743a6f1c77a6f103b06ef8d932fe8f2376793b92478853dc84571d17c429f",
"zh:5ed2d5eb7db13229baaf042c725d5c64b58ffdcc641370175e0a88900af94bf1",
"zh:8aecd4cf782c82bee01098f72fe4ffff83707516007b32a01c7fcb19a9260338",
"zh:928c05ecac309287ff7d73ed6e478350fe3003557658ae5dc2be817a4268dba7",
"zh:9b9fd36dfb3e75da8b4478485272505ae9a3c67b10db173e1d2d76cfe2b637b8",
"zh:ab7cd8c61ab67a045854e32f0be1940a92746770dbf3c17bbe923e0259c4f897",
"zh:bb1360ec19a4fc1095d0ef1b7b6c5c3c1a91daac7cd1957d43a4cdbb7356a2e3",
"zh:d2186f4063aa1a547b52a53745d472e43f5343bc1674f2bbb91421c61b0fab50",
"zh:d74bbb67a77951b18ffd7b2863954e70ac03450ad2023cc305c66a5ff25d8d18",
"zh:f5970569ea0a479bbfbf2d452f5962e1c9bd472b82756db822d0e951363daa25",
]
}
provider "registry.terraform.io/hashicorp/random" {
version = "3.8.1"
constraints = ">= 3.5.0"
hashes = [
"h1:u8AKlWVDTH5r9YLSeswoVEjiY72Rt4/ch7U+61ZDkiQ=",
"zh:08dd03b918c7b55713026037c5400c48af5b9f468f483463321bd18e17b907b4",
"zh:0eee654a5542dc1d41920bbf2419032d6f0d5625b03bd81339e5b33394a3e0ae",
"zh:229665ddf060aa0ed315597908483eee5b818a17d09b6417a0f52fd9405c4f57",
"zh:2469d2e48f28076254a2a3fc327f184914566d9e40c5780b8d96ebf7205f8bc0",
"zh:37d7eb334d9561f335e748280f5535a384a88675af9a9eac439d4cfd663bcb66",
"zh:741101426a2f2c52dee37122f0f4a2f2d6af6d852cb1db634480a86398fa3511",
"zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
"zh:a902473f08ef8df62cfe6116bd6c157070a93f66622384300de235a533e9d4a9",
"zh:b85c511a23e57a2147355932b3b6dce2a11e856b941165793a0c3d7578d94d05",
"zh:c5172226d18eaac95b1daac80172287b69d4ce32750c82ad77fa0768be4ea4b8",
"zh:dab4434dba34aad569b0bc243c2d3f3ff86dd7740def373f2a49816bd2ff819b",
"zh:f49fd62aa8c5525a5c17abd51e27ca5e213881d58882fd42fec4a545b53c9699",
]
}
-81
View File
@@ -1,81 +0,0 @@
# Authentik Terraform Configuration
Root Terraform configuration for managing Authentik SSO — applications (OAuth2/OIDC, Proxy, SAML), groups, outposts, flows, certificates, and property mappings.
State is stored in Terraform Cloud (organization `ultradesu`, workspace `Authentik`).
## Structure
```
.
├── main.tf # Resources: groups, outposts, policy bindings, module calls
├── variables.tf # Input variable definitions
├── outputs.tf # Outputs (app details, groups, flows, wiki data)
├── providers.tf # Authentik provider (goauthentik/authentik 2025.12.1)
├── state.tf # Terraform Cloud backend
├── terraform.tfvars # General settings: authentik_url, outposts, flows, tags
├── oauth2-apps.auto.tfvars # OAuth2/OIDC application definitions
├── proxy-apps.auto.tfvars # Proxy application definitions
├── groups.auto.tfvars # Group definitions
└── modules/
├── oauth-provider/ # OAuth2/OIDC provider + application
├── proxy-provider/ # Proxy provider + application
└── saml-provider/ # SAML provider + application
```
## Usage
```bash
# Set the API token
export TF_VAR_authentik_token="..."
terraform init
terraform plan
terraform apply
```
All `*.auto.tfvars` files are loaded automatically — no `-var-file` flags needed.
## Adding applications
OAuth2/OIDC — add to `oauth2-apps.auto.tfvars`:
```hcl
oauth_applications = {
"my-app" = {
name = "My App"
slug = "my-app"
group = "Tools"
redirect_uris = ["https://my-app.example.com/callback"]
create_group = true
access_groups = ["admins"]
}
}
```
Proxy — add to `proxy-apps.auto.tfvars`:
```hcl
proxy_applications = {
"my-proxy" = {
name = "My Proxy"
slug = "my-proxy"
group = "Tools"
external_host = "https://my-proxy.example.com"
internal_host = "http://my-service.namespace.svc:80"
outpost = "kubernetes-outpost"
create_group = true
access_groups = ["admins"]
}
}
```
## CI/CD
Managed via Gitea Actions (`.gitea/workflows/authentik-apps.yaml`). Runs `terraform apply` on push to `main` when files in `terraform/authentik/` change. Also generates a wiki page with the applications list.
## Requirements
- Terraform >= 1.0
- goauthentik/authentik provider 2025.12.1
- Authentik API token with admin permissions
-10
View File
@@ -1,10 +0,0 @@
groups = {
"admins" = {
name = "Administrators"
is_superuser = true
attributes = {
notes = "Managed by Terraform"
}
}
}
-361
View File
@@ -1,361 +0,0 @@
data "authentik_flow" "default_authorization_flow" {
slug = var.default_authorization_flow
}
data "authentik_flow" "default_authentication_flow" {
slug = var.default_authentication_flow
}
data "authentik_flow" "default_invalidation_flow" {
slug = var.default_invalidation_flow
}
# Root groups (without parent)
resource "authentik_group" "root_groups" {
for_each = {
for k, v in var.groups : k => v
if v.parent == null
}
name = each.value.name
is_superuser = each.value.is_superuser
attributes = jsonencode(each.value.attributes)
}
# Child groups (with parent)
resource "authentik_group" "child_groups" {
for_each = {
for k, v in var.groups : k => v
if v.parent != null
}
name = each.value.name
is_superuser = each.value.is_superuser
parents = authentik_group.root_groups[each.value.parent].id
attributes = jsonencode(each.value.attributes)
depends_on = [authentik_group.root_groups]
}
# Auto-created groups for proxy applications
resource "authentik_group" "proxy_app_groups" {
for_each = {
for k, v in var.proxy_applications : k => v
if v.create_group == true
}
name = "TF-${each.value.name} Users"
is_superuser = false
attributes = jsonencode({
notes = "Auto-created for ${each.value.name} application"
app_slug = each.value.slug
})
}
# Auto-created groups for OAuth applications
resource "authentik_group" "oauth_app_groups" {
for_each = {
for k, v in var.oauth_applications : k => v
if v.create_group == true
}
name = "TF-${each.value.name} Users"
is_superuser = false
attributes = jsonencode({
notes = "Auto-created for ${each.value.name} application"
app_slug = each.value.slug
})
}
resource "authentik_certificate_key_pair" "certificates" {
for_each = var.certificates
name = each.value.name
certificate_data = each.value.certificate_data
key_data = each.value.key_data
}
data "authentik_service_connection_kubernetes" "local_k8s" {
name = "Local Kubernetes Cluster"
}
resource "authentik_flow" "flows" {
for_each = var.flows
name = each.value.name
title = each.value.title
slug = each.value.slug
designation = each.value.designation
policy_engine_mode = each.value.policy_engine_mode
compatibility_mode = each.value.compatibility_mode
layout = each.value.layout
denied_action = each.value.denied_action
}
resource "authentik_property_mapping_provider_scope" "oidc_mappings" {
for_each = {
for k, v in var.property_mappings : k => v
if v.oidc_scope != null
}
name = each.value.name
scope_name = each.value.oidc_scope
expression = each.value.expression
}
resource "authentik_property_mapping_provider_saml" "saml_mappings" {
for_each = {
for k, v in var.property_mappings : k => v
if v.saml_name != null
}
name = each.value.name
saml_name = each.value.saml_name
expression = each.value.expression
}
module "oauth_applications" {
source = "./modules/oauth-provider"
for_each = var.oauth_applications
name = each.value.name
app_name = each.value.name
app_slug = each.value.slug
app_group = each.value.group
client_id = each.value.client_id
authorization_flow = try(authentik_flow.flows[each.value.authorization_flow].id, data.authentik_flow.default_authorization_flow.id)
invalidation_flow = data.authentik_flow.default_invalidation_flow.id
redirect_uris = each.value.redirect_uris
client_type = each.value.client_type
include_claims_in_id_token = each.value.include_claims_in_id_token
access_code_validity = each.value.access_code_validity
access_token_validity = each.value.access_token_validity
refresh_token_validity = each.value.refresh_token_validity
property_mappings = each.value.property_mappings
signing_key = each.value.signing_key
policy_engine_mode = each.value.policy_engine_mode
meta_description = each.value.meta_description
meta_launch_url = each.value.meta_launch_url
meta_icon = each.value.meta_icon
scope_mappings = each.value.scope_mappings
# Access control - only pass explicitly defined groups
access_groups = [
for group_key in each.value.access_groups :
try(
authentik_group.root_groups[group_key].id,
authentik_group.child_groups[group_key].id
)
]
}
module "proxy_applications" {
source = "./modules/proxy-provider"
for_each = var.proxy_applications
name = each.value.name
app_name = each.value.name
app_slug = each.value.slug
app_group = each.value.group
external_host = each.value.external_host
internal_host = each.value.internal_host
internal_host_ssl_validation = each.value.internal_host_ssl_validation
authorization_flow = try(authentik_flow.flows[each.value.authorization_flow].id, data.authentik_flow.default_authorization_flow.id)
invalidation_flow = data.authentik_flow.default_invalidation_flow.id
mode = each.value.mode
intercept_header_auth = each.value.intercept_header_auth
basic_auth_enabled = each.value.basic_auth_enabled
basic_auth_user_attribute = each.value.basic_auth_username_attribute
basic_auth_password_attribute = each.value.basic_auth_password_attribute
cookie_domain = each.value.cookie_domain
skip_path_regex = each.value.skip_path_regex
policy_engine_mode = each.value.policy_engine_mode
meta_description = each.value.meta_description
meta_launch_url = each.value.meta_launch_url
meta_icon = each.value.meta_icon
# Access control - only pass explicitly defined groups
access_groups = [
for group_key in each.value.access_groups :
try(
authentik_group.root_groups[group_key].id,
authentik_group.child_groups[group_key].id
)
]
}
# Binding auto-created groups to their applications
resource "authentik_policy_binding" "auto_group_bindings" {
for_each = {
for k, v in var.proxy_applications : k => v
if v.create_group == true
}
target = module.proxy_applications[each.key].application_uuid
group = authentik_group.proxy_app_groups[each.key].id
order = 100
depends_on = [
module.proxy_applications,
authentik_group.proxy_app_groups
]
}
# Binding auto-created groups to their OAuth applications
resource "authentik_policy_binding" "oauth_auto_group_bindings" {
for_each = {
for k, v in var.oauth_applications : k => v
if v.create_group == true
}
target = module.oauth_applications[each.key].application_uuid
group = authentik_group.oauth_app_groups[each.key].id
order = 100
depends_on = [
module.oauth_applications,
authentik_group.oauth_app_groups
]
}
module "saml_applications" {
source = "./modules/saml-provider"
for_each = var.saml_applications
name = each.value.name
app_name = each.value.name
app_slug = each.value.slug
app_group = each.value.group
authorization_flow = try(authentik_flow.flows[each.value.authorization_flow].id, data.authentik_flow.default_authorization_flow.id)
invalidation_flow = data.authentik_flow.default_invalidation_flow.id
acs_url = each.value.acs_url
issuer = each.value.issuer
audience = each.value.audience
sp_binding = each.value.sp_binding
signing_key = each.value.signing_key
property_mappings = [for pm in each.value.property_mappings : authentik_property_mapping_provider_saml.saml_mappings[pm].id]
name_id_mapping = each.value.name_id_mapping != null ? authentik_property_mapping_provider_saml.saml_mappings[each.value.name_id_mapping].id : null
assertion_valid_not_before = each.value.assertion_valid_not_before
assertion_valid_not_on_or_after = each.value.assertion_valid_not_on_or_after
session_valid_not_on_or_after = each.value.session_valid_not_on_or_after
policy_engine_mode = each.value.policy_engine_mode
meta_description = each.value.meta_description
meta_launch_url = each.value.meta_launch_url
meta_icon = each.value.meta_icon
}
locals {
oauth_outpost_assignments = {
for app_key, app in var.oauth_applications : app_key => app.outpost
if app.outpost != null
}
proxy_outpost_assignments = {
for app_key, app in var.proxy_applications : app_key => app.outpost
if app.outpost != null
}
outpost_providers = {
for outpost_key, outpost in var.outposts : outpost_key => concat(
[for app_key, app_outpost in local.oauth_outpost_assignments :
module.oauth_applications[app_key].provider_id if app_outpost == outpost_key],
[for app_key, app_outpost in local.proxy_outpost_assignments :
module.proxy_applications[app_key].provider_id if app_outpost == outpost_key]
)
}
}
resource "authentik_outpost" "outposts" {
for_each = {
for k, v in var.outposts : k => v
if length(lookup(local.outpost_providers, k, [])) > 0
}
name = each.value.name
type = "proxy"
protocol_providers = local.outpost_providers[each.key]
service_connection = data.authentik_service_connection_kubernetes.local_k8s.id
config = jsonencode({
log_level = "info"
docker_labels = null
authentik_host = var.authentik_url
docker_network = null
container_image = null
docker_map_ports = true
refresh_interval = "minutes=5"
kubernetes_replicas = 1
kubernetes_namespace = "authentik"
authentik_host_browser = ""
object_naming_template = "ak-outpost-%(name)s"
authentik_host_insecure = false
kubernetes_json_patches = {
deployment = [
{
op = "add"
path = "/spec/template/spec/containers/0/env/-"
value = {
name = "AUTHENTIK_POSTGRESQL__HOST"
value = "psql.psql.svc"
}
},
{
op = "add"
path = "/spec/template/spec/containers/0/env/-"
value = {
name = "AUTHENTIK_POSTGRESQL__PORT"
value = "5432"
}
},
{
op = "add"
path = "/spec/template/spec/containers/0/env/-"
value = {
name = "AUTHENTIK_POSTGRESQL__NAME"
value = "authentik"
}
},
{
op = "add"
path = "/spec/template/spec/containers/0/env/-"
value = {
name = "AUTHENTIK_POSTGRESQL__USER"
valueFrom = {
secretKeyRef = {
name = "authentik-creds"
key = "AUTHENTIK_POSTGRESQL__USER"
}
}
}
},
{
op = "add"
path = "/spec/template/spec/containers/0/env/-"
value = {
name = "AUTHENTIK_POSTGRESQL__PASSWORD"
valueFrom = {
secretKeyRef = {
name = "authentik-creds"
key = "AUTHENTIK_POSTGRESQL__PASSWORD"
}
}
}
}
]
}
kubernetes_service_type = "ClusterIP"
kubernetes_image_pull_secrets = []
kubernetes_ingress_class_name = null
kubernetes_disabled_components = []
kubernetes_ingress_annotations = {}
kubernetes_ingress_secret_name = "idm-tls"
})
depends_on = [
module.oauth_applications,
module.proxy_applications
]
}
@@ -1,103 +0,0 @@
terraform {
required_providers {
authentik = {
source = "goauthentik/authentik"
version = ">= 2023.10.0"
}
random = {
source = "hashicorp/random"
version = ">= 3.5.0"
}
}
}
# Get all available scope mappings
data "authentik_property_mapping_provider_scope" "all_scopes" {
managed_list = [
"goauthentik.io/providers/oauth2/scope-email",
"goauthentik.io/providers/oauth2/scope-openid",
"goauthentik.io/providers/oauth2/scope-profile"
]
}
# Filter scope mappings based on requested scopes
locals {
scope_name_mapping = {
"openid" = "goauthentik.io/providers/oauth2/scope-openid"
"profile" = "goauthentik.io/providers/oauth2/scope-profile"
"email" = "goauthentik.io/providers/oauth2/scope-email"
}
selected_scope_ids = [
for scope in var.scope_mappings :
data.authentik_property_mapping_provider_scope.all_scopes.ids[index(data.authentik_property_mapping_provider_scope.all_scopes.managed_list, local.scope_name_mapping[scope])]
if contains(keys(local.scope_name_mapping), scope)
]
}
resource "random_password" "client_secret" {
count = var.client_secret == null ? 1 : 0
length = 40
special = true
}
resource "authentik_provider_oauth2" "provider" {
name = var.name
client_id = var.client_id != null ? var.client_id : random_id.client_id[0].hex
client_secret = var.client_secret != null ? var.client_secret : random_password.client_secret[0].result
client_type = var.client_type
authorization_flow = var.authorization_flow
invalidation_flow = var.invalidation_flow
include_claims_in_id_token = var.include_claims_in_id_token
access_code_validity = var.access_code_validity
access_token_validity = var.access_token_validity
refresh_token_validity = var.refresh_token_validity
signing_key = var.signing_key
allowed_redirect_uris = [
for uri in var.redirect_uris : {
matching_mode = "strict"
url = uri
}
]
property_mappings = length(var.property_mappings) > 0 ? var.property_mappings : local.selected_scope_ids
}
resource "random_id" "client_id" {
count = var.client_id == null ? 1 : 0
byte_length = 20
}
resource "authentik_application" "app" {
name = var.app_name
slug = var.app_slug
protocol_provider = authentik_provider_oauth2.provider.id
group = var.app_group
policy_engine_mode = var.policy_engine_mode
meta_description = var.meta_description
meta_launch_url = var.meta_launch_url
meta_icon = var.meta_icon
}
resource "authentik_policy_binding" "app_access" {
for_each = var.access_policies
target = authentik_application.app.id
policy = each.value.policy_id
order = each.value.order
enabled = lookup(each.value, "enabled", true)
timeout = lookup(each.value, "timeout", 30)
negate = lookup(each.value, "negate", false)
failure_result = lookup(each.value, "failure_result", true)
}
# Binding groups to the application
resource "authentik_policy_binding" "group_bindings" {
for_each = { for idx, group_id in var.access_groups : idx => group_id }
target = authentik_application.app.uuid
group = each.value
order = 10 + each.key
}
@@ -1,30 +0,0 @@
output "provider_id" {
description = "ID of the OAuth2 provider"
value = authentik_provider_oauth2.provider.id
}
output "application_id" {
description = "ID of the application"
value = authentik_application.app.id
}
output "application_uuid" {
description = "UUID of the application"
value = authentik_application.app.uuid
}
output "client_id" {
description = "OAuth2 Client ID"
value = authentik_provider_oauth2.provider.client_id
}
output "client_secret" {
description = "OAuth2 Client Secret"
value = authentik_provider_oauth2.provider.client_secret
sensitive = true
}
output "application_slug" {
description = "Application slug"
value = authentik_application.app.slug
}
@@ -1,150 +0,0 @@
variable "name" {
description = "Name of the OAuth2 provider"
type = string
}
variable "app_name" {
description = "Name of the application"
type = string
}
variable "app_slug" {
description = "Slug of the application"
type = string
}
variable "app_group" {
description = "Group for the application"
type = string
default = ""
}
variable "client_id" {
description = "OAuth2 Client ID"
type = string
default = null
}
variable "client_secret" {
description = "OAuth2 Client Secret"
type = string
default = null
sensitive = true
}
variable "client_type" {
description = "OAuth2 Client type (confidential or public)"
type = string
default = "confidential"
validation {
condition = contains(["confidential", "public"], var.client_type)
error_message = "Client type must be either 'confidential' or 'public'."
}
}
variable "authorization_flow" {
description = "Authorization flow UUID"
type = string
}
variable "invalidation_flow" {
description = "Invalidation flow UUID"
type = string
}
variable "redirect_uris" {
description = "List of allowed redirect URIs"
type = list(string)
default = []
}
variable "access_code_validity" {
description = "Access code validity duration"
type = string
default = "minutes=1"
}
variable "access_token_validity" {
description = "Access token validity duration"
type = string
default = "minutes=5"
}
variable "refresh_token_validity" {
description = "Refresh token validity duration"
type = string
default = "days=30"
}
variable "include_claims_in_id_token" {
description = "Include claims in ID token"
type = bool
default = true
}
variable "signing_key" {
description = "Signing key UUID"
type = string
default = null
}
variable "property_mappings" {
description = "List of property mapping UUIDs"
type = list(string)
default = []
}
variable "policy_engine_mode" {
description = "Policy engine mode"
type = string
default = "all"
validation {
condition = contains(["all", "any"], var.policy_engine_mode)
error_message = "Policy engine mode must be either 'all' or 'any'."
}
}
variable "meta_description" {
description = "Application meta description"
type = string
default = ""
}
variable "meta_launch_url" {
description = "Application launch URL"
type = string
default = ""
}
variable "meta_icon" {
description = "Application icon URL"
type = string
default = ""
}
variable "access_policies" {
description = "Access policies for the application"
type = map(object({
policy_id = string
order = number
enabled = optional(bool, true)
timeout = optional(number, 30)
negate = optional(bool, false)
failure_result = optional(bool, true)
}))
default = {}
}
variable "access_groups" {
description = "List of group IDs that have access to the application"
type = list(string)
default = []
}
variable "scope_mappings" {
description = "List of scope mappings for the OAuth provider"
type = list(string)
default = ["openid", "profile", "email"]
}
@@ -1,58 +0,0 @@
terraform {
required_providers {
authentik = {
source = "goauthentik/authentik"
version = ">= 2023.10.0"
}
}
}
resource "authentik_provider_proxy" "provider" {
name = var.name
external_host = var.external_host
internal_host = var.internal_host
internal_host_ssl_validation = var.internal_host_ssl_validation
authorization_flow = var.authorization_flow
invalidation_flow = var.invalidation_flow
mode = var.mode
cookie_domain = var.cookie_domain
skip_path_regex = var.skip_path_regex
intercept_header_auth = var.intercept_header_auth
basic_auth_enabled = var.basic_auth_enabled
basic_auth_password_attribute = var.basic_auth_password_attribute
property_mappings = var.property_mappings
}
resource "authentik_application" "app" {
name = var.app_name
slug = var.app_slug
protocol_provider = authentik_provider_proxy.provider.id
group = var.app_group
policy_engine_mode = var.policy_engine_mode
meta_description = var.meta_description
meta_launch_url = var.meta_launch_url
meta_icon = var.meta_icon
}
resource "authentik_policy_binding" "app_access" {
for_each = var.access_policies
target = authentik_application.app.id
policy = each.value.policy_id
order = each.value.order
enabled = lookup(each.value, "enabled", true)
timeout = lookup(each.value, "timeout", 30)
negate = lookup(each.value, "negate", false)
failure_result = lookup(each.value, "failure_result", true)
}
# Binding groups to the application
resource "authentik_policy_binding" "group_bindings" {
for_each = { for idx, group_id in var.access_groups : idx => group_id }
target = authentik_application.app.uuid
group = each.value
order = 10 + each.key
}
@@ -1,35 +0,0 @@
output "provider_id" {
description = "ID of the Proxy provider"
value = authentik_provider_proxy.provider.id
}
output "application_id" {
description = "ID of the application"
value = authentik_application.app.id
}
output "application_uuid" {
description = "UUID of the application"
value = authentik_application.app.uuid
}
output "application_slug" {
description = "Application slug"
value = authentik_application.app.slug
}
output "launch_url" {
description = "Application launch URL"
value = authentik_application.app.meta_launch_url
}
output "external_host" {
description = "External host URL"
value = authentik_provider_proxy.provider.external_host
}
output "internal_host" {
description = "Internal host URL"
value = authentik_provider_proxy.provider.internal_host
}
@@ -1,151 +0,0 @@
variable "name" {
description = "Name of the Proxy provider"
type = string
}
variable "app_name" {
description = "Name of the application"
type = string
}
variable "app_slug" {
description = "Slug of the application"
type = string
}
variable "app_group" {
description = "Group for the application"
type = string
default = ""
}
variable "external_host" {
description = "External hostname for the proxy"
type = string
}
variable "internal_host" {
description = "Internal hostname for the proxy"
type = string
default = ""
}
variable "internal_host_ssl_validation" {
description = "Enable SSL validation for internal host"
type = bool
default = true
}
variable "authorization_flow" {
description = "Authorization flow UUID"
type = string
}
variable "invalidation_flow" {
description = "Invalidation flow UUID"
type = string
}
variable "mode" {
description = "Proxy mode (proxy, forward_single, forward_domain)"
type = string
default = "proxy"
validation {
condition = contains(["proxy", "forward_single", "forward_domain"], var.mode)
error_message = "Mode must be one of: proxy, forward_single, forward_domain."
}
}
variable "cookie_domain" {
description = "Cookie domain for the proxy"
type = string
default = ""
}
variable "skip_path_regex" {
description = "Regular expression for paths to skip authentication"
type = string
default = ""
}
variable "intercept_header_auth" {
description = "Intercept header authentication"
type = bool
default = false
}
variable "basic_auth_enabled" {
description = "Enable basic authentication"
type = bool
default = false
}
variable "basic_auth_password_attribute" {
description = "Attribute for basic auth password"
type = string
default = ""
}
variable "basic_auth_user_attribute" {
description = "Attribute for basic auth username"
type = string
default = ""
}
variable "property_mappings" {
description = "List of property mapping UUIDs"
type = list(string)
default = []
}
variable "policy_engine_mode" {
description = "Policy engine mode"
type = string
default = "all"
validation {
condition = contains(["all", "any"], var.policy_engine_mode)
error_message = "Policy engine mode must be either 'all' or 'any'."
}
}
variable "meta_description" {
description = "Application meta description"
type = string
default = ""
}
variable "meta_launch_url" {
description = "Application launch URL"
type = string
default = ""
}
variable "meta_icon" {
description = "Application icon URL"
type = string
default = ""
}
variable "access_policies" {
description = "Access policies for the application"
type = map(object({
policy_id = string
order = number
enabled = optional(bool, true)
timeout = optional(number, 30)
negate = optional(bool, false)
failure_result = optional(bool, true)
}))
default = {}
}
variable "access_groups" {
description = "List of group IDs that have access to the application"
type = list(string)
default = []
}
@@ -1,53 +0,0 @@
terraform {
required_providers {
authentik = {
source = "goauthentik/authentik"
version = ">= 2023.10.0"
}
}
}
data "authentik_certificate_key_pair" "default" {
name = "authentik Self-signed Certificate"
}
resource "authentik_provider_saml" "provider" {
name = var.name
authorization_flow = var.authorization_flow
invalidation_flow = var.invalidation_flow
acs_url = var.acs_url
issuer = var.issuer
audience = var.audience
sp_binding = var.sp_binding
signing_kp = var.signing_key != null ? var.signing_key : data.authentik_certificate_key_pair.default.id
property_mappings = var.property_mappings
name_id_mapping = var.name_id_mapping
assertion_valid_not_before = var.assertion_valid_not_before
assertion_valid_not_on_or_after = var.assertion_valid_not_on_or_after
session_valid_not_on_or_after = var.session_valid_not_on_or_after
}
resource "authentik_application" "app" {
name = var.app_name
slug = var.app_slug
protocol_provider = authentik_provider_saml.provider.id
group = var.app_group
policy_engine_mode = var.policy_engine_mode
meta_description = var.meta_description
meta_launch_url = var.meta_launch_url
meta_icon = var.meta_icon
}
resource "authentik_policy_binding" "app_access" {
for_each = var.access_policies
target = authentik_application.app.id
policy = each.value.policy_id
order = each.value.order
enabled = lookup(each.value, "enabled", true)
timeout = lookup(each.value, "timeout", 30)
negate = lookup(each.value, "negate", false)
failure_result = lookup(each.value, "failure_result", true)
}
@@ -1,24 +0,0 @@
output "provider_id" {
description = "ID of the SAML provider"
value = authentik_provider_saml.provider.id
}
output "application_id" {
description = "ID of the application"
value = authentik_application.app.id
}
output "provider_name" {
description = "Name of the SAML provider"
value = authentik_provider_saml.provider.name
}
output "acs_url" {
description = "Assertion Consumer Service URL"
value = authentik_provider_saml.provider.acs_url
}
output "issuer" {
description = "SAML Issuer"
value = authentik_provider_saml.provider.issuer
}
@@ -1,124 +0,0 @@
variable "name" {
description = "Name of the SAML provider"
type = string
}
variable "app_name" {
description = "Name of the application"
type = string
}
variable "app_slug" {
description = "Slug of the application"
type = string
}
variable "app_group" {
description = "Group of the application"
type = string
default = ""
}
variable "authorization_flow" {
description = "Authorization flow ID"
type = string
}
variable "invalidation_flow" {
description = "Invalidation flow ID"
type = string
}
variable "acs_url" {
description = "Assertion Consumer Service URL"
type = string
}
variable "issuer" {
description = "SAML Issuer"
type = string
}
variable "audience" {
description = "SAML Audience"
type = string
}
variable "sp_binding" {
description = "Service Provider binding (post or redirect)"
type = string
default = "post"
}
variable "signing_key" {
description = "Certificate key pair ID for signing"
type = string
default = null
}
variable "property_mappings" {
description = "List of property mapping IDs"
type = list(string)
default = []
}
variable "name_id_mapping" {
description = "Property mapping ID for NameID"
type = string
default = null
}
variable "assertion_valid_not_before" {
description = "Assertion valid not before"
type = string
default = "minutes=-5"
}
variable "assertion_valid_not_on_or_after" {
description = "Assertion valid not on or after"
type = string
default = "minutes=5"
}
variable "session_valid_not_on_or_after" {
description = "Session valid not on or after"
type = string
default = "minutes=86400"
}
variable "policy_engine_mode" {
description = "Policy engine mode"
type = string
default = "all"
}
variable "meta_description" {
description = "Application description"
type = string
default = ""
}
variable "meta_launch_url" {
description = "Application launch URL"
type = string
default = ""
}
variable "meta_icon" {
description = "Application icon URL"
type = string
default = ""
}
variable "access_policies" {
description = "Access policies for the application"
type = map(object({
policy_id = string
order = number
enabled = optional(bool, true)
timeout = optional(number, 30)
negate = optional(bool, false)
failure_result = optional(bool, true)
}))
default = {}
}
-232
View File
@@ -1,232 +0,0 @@
oauth_applications = {
"paperless" = {
name = "Paperless-NGX"
slug = "paperless"
group = "Tools"
meta_description = "Document management system"
meta_icon = "https://img.icons8.com/fluency/48/documents.png"
redirect_uris = ["https://docs.hexor.cy/accounts/oidc/authentik/login/callback/"]
client_type = "confidential"
include_claims_in_id_token = true
access_code_validity = "minutes=1"
access_token_validity = "minutes=5"
refresh_token_validity = "days=30"
scope_mappings = ["openid", "profile", "email"]
create_group = true
access_groups = ["admins"]
}
"gitea" = {
name = "Gitea"
slug = "gitea"
group = "Tools"
meta_description = "Git repository hosting"
meta_icon = "https://img.icons8.com/?size=100&id=20906&format=png&color=000000"
redirect_uris = ["https://gt.hexor.cy/user/oauth2/Authentik/callback"]
client_type = "confidential"
include_claims_in_id_token = true
access_code_validity = "minutes=1"
access_token_validity = "minutes=10"
refresh_token_validity = "days=30"
scope_mappings = ["openid", "profile", "email"]
access_groups = ["admins"]
}
"jellyfin" = {
name = "Jellyfin"
slug = "jellyfin"
group = "Media and Storage"
meta_description = "Media streaming server"
meta_icon = "https://img.icons8.com/plasticine/100/jellyfin.png"
redirect_uris = [
"https://jf.hexor.cy/sso/OID/r/authentik",
"https://jf.hexor.cy/sso/OID/redirect/authentik"
]
client_type = "confidential"
include_claims_in_id_token = true
access_code_validity = "minutes=1"
access_token_validity = "minutes=10"
refresh_token_validity = "days=30"
scope_mappings = ["openid", "profile", "email"]
access_groups = ["admins"]
}
"argocd" = {
name = "ArgoCD"
slug = "argocd"
group = "Core"
meta_description = "GitOps deployment tool"
meta_icon = "https://img.icons8.com/color-glass/48/octopus.png"
redirect_uris = ["https://ag.hexor.cy/auth/callback"]
client_type = "confidential"
include_claims_in_id_token = true
access_code_validity = "minutes=1"
access_token_validity = "minutes=5"
refresh_token_validity = "days=30"
scope_mappings = ["openid", "profile", "email"]
signing_key = "1b1b5bec-034a-4d96-871a-133f11322360"
access_groups = ["admins"]
}
"grafana" = {
name = "Grafana"
slug = "grafana"
group = "Core"
meta_description = "Monitoring and observability"
meta_icon = "https://img.icons8.com/fluency/48/grafana.png"
redirect_uris = ["https://gf.hexor.cy/login/generic_oauth"]
client_type = "confidential"
include_claims_in_id_token = true
access_code_validity = "minutes=1"
access_token_validity = "minutes=5"
refresh_token_validity = "days=30"
scope_mappings = ["openid", "profile", "email"]
access_groups = ["admins"]
}
"immich" = {
name = "Immich"
slug = "immich"
group = "Media and Storage"
meta_description = "Photo and video management"
meta_icon = "https://img.icons8.com/fluency/48/photos.png"
redirect_uris = [
"https://photos.hexor.cy/auth/login",
"https://photos.hexor.cy/user-settings",
"app.immich:///oauth-callback",
"http://photos.homenet:30283/auth/login",
"http://photos.homenet:30283/user-settings"
]
client_type = "confidential"
include_claims_in_id_token = true
access_code_validity = "minutes=1"
access_token_validity = "minutes=5"
refresh_token_validity = "days=30"
scope_mappings = ["openid", "profile", "email"]
signing_key = "1b1b5bec-034a-4d96-871a-133f11322360"
access_groups = ["admins"]
create_group = true
}
"pgadmin" = {
name = "Postgres WEB Admin"
slug = "pgadmin"
group = "Core"
meta_description = "PostgreSQL WEB administration"
meta_icon = "https://img.icons8.com/?size=100&id=JRnxU7ZWP4mi&format=png&color=000000"
redirect_uris = ["https://pg.hexor.cy/oauth2/authorize"]
client_type = "confidential"
include_claims_in_id_token = true
access_code_validity = "minutes=1"
access_token_validity = "minutes=5"
refresh_token_validity = "days=30"
scope_mappings = ["openid", "profile", "email"]
access_groups = ["admins"]
signing_key = "1b1b5bec-034a-4d96-871a-133f11322360"
}
"home-assistant-lms" = {
name = "Home Assistant LMS"
slug = "home-assistant-lms"
group = "Internal"
meta_description = "Home Assistant Limassol"
meta_icon = "https://img.icons8.com/stickers/100/smart-home-automation.png"
redirect_uris = [
"http://ha-lms:8123/auth/oidc/callback",
"http://ha-lms.homenet:8123/auth/oidc/callback",
]
meta_launch_url = "http://ha-lms:8123/auth/oidc/welcome"
client_type = "confidential"
include_claims_in_id_token = true
access_code_validity = "minutes=1"
access_token_validity = "minutes=5"
refresh_token_validity = "days=30"
scope_mappings = ["openid", "profile", "email"]
access_groups = ["admins"]
create_group = true
signing_key = "1b1b5bec-034a-4d96-871a-133f11322360"
}
"home-assistant-london" = {
name = "Home Assistant London"
slug = "home-assistant-london"
group = "Internal"
meta_description = "Home Assistant London"
meta_icon = "https://img.icons8.com/stickers/100/smart-home-automation.png"
redirect_uris = [
"http://ha-london:8123/auth/oidc/callback",
"http://ha-london.tail2fe2d.ts.net:8123/auth/oidc/callback",
]
meta_launch_url = "http://ha-london:8123/auth/oidc/welcome"
client_type = "confidential"
include_claims_in_id_token = true
access_code_validity = "minutes=1"
access_token_validity = "minutes=5"
refresh_token_validity = "days=30"
scope_mappings = ["openid", "profile", "email"]
access_groups = ["admins"]
create_group = true
signing_key = "1b1b5bec-034a-4d96-871a-133f11322360"
}
"openwebui" = {
name = "OpenWeb UI"
slug = "openwebui"
group = "Tools"
meta_description = "OpenWeb UI"
meta_icon = "https://ollama.com/public/ollama.png"
redirect_uris = [
"https://ai.hexor.cy/oauth/oidc/callback",
]
meta_launch_url = "https://ai.hexor.cy"
client_type = "confidential"
include_claims_in_id_token = true
access_code_validity = "minutes=1"
access_token_validity = "minutes=5"
refresh_token_validity = "days=30"
scope_mappings = ["openid", "profile", "email"]
access_groups = ["admins"]
create_group = true
signing_key = "1b1b5bec-034a-4d96-871a-133f11322360"
}
"matrix" = {
name = "Matrix Chat"
slug = "matrix"
group = "Tools"
meta_description = "Matrix Chat"
meta_icon = "https://img.icons8.com/ios/100/40C057/matrix-logo.png"
redirect_uris = [
"https://auth.matrix.hexor.cy/upstream/callback/001KKV4EKY7KG98W2M9T806K6A",
]
meta_launch_url = "https://chat.matrix.hexor.cy"
client_type = "confidential"
include_claims_in_id_token = true
access_code_validity = "minutes=1"
access_token_validity = "minutes=5"
refresh_token_validity = "days=30"
scope_mappings = ["openid", "profile", "email"]
access_groups = []
create_group = false
signing_key = "1b1b5bec-034a-4d96-871a-133f11322360"
}
"furumi-ng-web" = {
name = "Furumi Web Player"
slug = "furumi-ng-web"
group = "Tools"
meta_description = "Furumi Web Player"
meta_icon = "https://img.icons8.com/pulsar-color/48/music.png"
redirect_uris = [
"https://music.hexor.cy/auth/callback",
]
meta_launch_url = "https://music.hexor.cy"
client_type = "confidential"
include_claims_in_id_token = true
access_code_validity = "minutes=1"
access_token_validity = "minutes=5"
refresh_token_validity = "days=30"
scope_mappings = ["openid", "profile", "email"]
access_groups = []
create_group = true
signing_key = "1b1b5bec-034a-4d96-871a-133f11322360"
}
}
-124
View File
@@ -1,124 +0,0 @@
output "oauth_applications" {
description = "OAuth2/OpenID applications details"
value = {
for k, v in module.oauth_applications : k => {
application_id = v.application_id
application_uuid = v.application_uuid
client_id = v.client_id
client_secret = v.client_secret
slug = v.application_slug
}
}
sensitive = true
}
output "proxy_applications" {
description = "Proxy applications details"
value = {
for k, v in module.proxy_applications : k => {
application_id = v.application_id
application_uuid = v.application_uuid
external_host = v.external_host
internal_host = v.internal_host
slug = v.application_slug
}
}
}
output "outposts" {
description = "Outposts details"
value = {
for k, v in authentik_outpost.outposts : k => {
id = v.id
name = v.name
type = v.type
}
}
}
output "groups" {
description = "Groups details"
value = merge(
{
for k, v in authentik_group.root_groups : k => {
id = v.id
name = v.name
}
},
{
for k, v in authentik_group.child_groups : k => {
id = v.id
name = v.name
}
},
{
for k, v in authentik_group.proxy_app_groups : k => {
id = v.id
name = v.name
auto_created = true
type = "proxy"
}
},
{
for k, v in authentik_group.oauth_app_groups : k => {
id = v.id
name = v.name
auto_created = true
type = "oauth"
}
}
)
}
output "flows" {
description = "Custom flows details"
value = {
for k, v in authentik_flow.flows : k => {
id = v.id
slug = v.slug
name = v.name
}
}
}
output "certificates" {
description = "Certificates details"
value = {
for k, v in authentik_certificate_key_pair.certificates : k => {
id = v.id
name = v.name
fingerprint_sha256 = v.fingerprint_sha256
fingerprint_sha1 = v.fingerprint_sha1
}
}
}
# Output for applications table generation
output "applications_for_wiki" {
description = "Applications data formatted for wiki table generation"
value = {
proxy_apps = {
for k, v in var.proxy_applications : k => {
name = v.name
type = "Proxy"
url = v.external_host
internal_url = v.internal_host
group = v.group
description = v.meta_description
icon = v.meta_icon
slug = v.slug
}
}
oauth_apps = {
for k, v in var.oauth_applications : k => {
name = v.name
type = "OAuth2/OpenID"
url = length(v.redirect_uris) > 0 ? "https://${split("/", replace(v.redirect_uris[0], "https://", ""))[0]}" : ""
group = v.group
description = v.meta_description
icon = v.meta_icon
slug = v.slug
}
}
}
}
-13
View File
@@ -1,13 +0,0 @@
terraform {
required_providers {
authentik = {
source = "goauthentik/authentik"
version = "2025.12.1"
}
}
}
provider "authentik" {
url = var.authentik_url
token = var.authentik_token
}
-193
View File
@@ -1,193 +0,0 @@
proxy_applications = {
"k8s-dashboard" = {
name = "K8S dashboard"
slug = "k8s-dashboard-ns"
group = "Core"
external_host = "https://k8s.hexor.cy"
internal_host = "http://kubernetes-dashboard.kubernetes-dashboard.svc"
internal_host_ssl_validation = false
meta_description = "K8S dashboard chart"
mode = "proxy"
outpost = "kubernetes-outpost"
meta_icon = "https://img.icons8.com/color/48/kubernetes.png"
create_group = true
access_groups = ["admins"]
}
"filemanager" = {
name = "FM filemanager"
slug = "fm-filemanager"
group = "Core"
external_host = "https://fm.hexor.cy"
internal_host = "http://fb-filemanager-filebrowser.syncthing.svc"
internal_host_ssl_validation = false
meta_description = "K8S dashboard chart"
mode = "proxy"
outpost = "kubernetes-outpost"
meta_icon = "https://img.icons8.com/external-anggara-flat-anggara-putra/32/external-folder-basic-user-interface-anggara-flat-anggara-putra.png"
create_group = true
access_groups = ["admins"]
}
"prometheus" = {
name = "Prometheus"
slug = "prometheus"
group = "Core"
external_host = "https://prom.hexor.cy"
internal_host = "http://prometheus-kube-prometheus-prometheus.prometheus.svc:9090"
meta_description = ""
meta_icon = "https://img.icons8.com/fluency/48/prometheus-app.png"
mode = "proxy"
outpost = "kubernetes-outpost"
internal_host_ssl_validation = false
create_group = true
access_groups = ["admins"]
}
"mtproxy-links" = {
name = "mtproxy-links"
slug = "mtproxy-links"
group = "Core"
external_host = "https://proxy.hexor.cy"
internal_host = "http://secret-reader.mtproxy.svc:80"
internal_host_ssl_validation = false
meta_description = ""
skip_path_regex = <<-EOT
/webhook
EOT
meta_icon = "https://img.icons8.com/ios-filled/50/password.png"
mode = "proxy"
outpost = "kubernetes-outpost"
create_group = true
access_groups = ["admins"]
}
# Tools applications
"qbittorrent" = {
name = "qBittorent"
slug = "qbittorent"
group = "Tools"
external_host = "https://qbt.hexor.cy"
internal_host = "http://qbittorrent.jellyfin.svc"
internal_host_ssl_validation = false
meta_description = ""
meta_icon = "https://img.icons8.com/nolan/64/qbittorrent--v2.png"
mode = "proxy"
outpost = "kubernetes-outpost"
create_group = true
access_groups = ["admins"]
}
# Media and Storage applications
"kopia" = {
name = "Kopia"
slug = "kopia"
group = "Media and Storage"
external_host = "https://backup.hexor.cy"
internal_host = "http://100.72.135.2:51515"
internal_host_ssl_validation = false
meta_description = ""
meta_icon = "https://img.icons8.com/external-flaticons-lineal-color-flat-icons/64/external-backup-productivity-flaticons-lineal-color-flat-icons.png"
mode = "proxy"
outpost = "kubernetes-outpost"
create_group = true
access_groups = ["admins"]
}
"syncthing-router" = {
name = "Syncthing"
slug = "syncthing"
group = "Media and Storage"
external_host = "https://ss.hexor.cy"
internal_host = "http://syncthing-router.syncthing.svc:80"
internal_host_ssl_validation = false
meta_description = ""
meta_icon = "https://img.icons8.com/?size=100&id=Id4NcEcXcYzF&format=png&color=000000"
mode = "proxy"
outpost = "kubernetes-outpost"
create_group = true
access_groups = ["admins"]
}
"truenas" = {
name = "TrueNAS"
slug = "truenas-proxy"
group = "Media and Storage"
external_host = "https://nas.hexor.cy"
internal_host = "http://10.0.5.107:81"
internal_host_ssl_validation = false
meta_description = ""
meta_icon = "https://img.icons8.com/dusk/64/nas.png"
mode = "proxy"
outpost = "kubernetes-outpost"
create_group = true
access_groups = ["admins"]
}
"khm" = {
name = "KHM"
slug = "khm"
group = "Media and Storage"
external_host = "https://khm.hexor.cy"
internal_host = "http://khm.khm.svc:8080"
internal_host_ssl_validation = false
meta_description = ""
meta_icon = "https://img.icons8.com/liquid-glass/48/key.png"
mode = "proxy"
outpost = "kubernetes-outpost"
access_groups = ["admins", "khm"]
create_group = true
access_groups = ["admins"]
}
"minecraft" = {
name = "Minecraft"
slug = "minecraft"
group = "Media and Storage"
external_host = "https://minecraft.hexor.cy"
internal_host = "http://minecraft-dynmap.minecraft.svc"
internal_host_ssl_validation = false
meta_description = ""
meta_icon = "https://img.icons8.com/color/48/minecraft-grass-cube.png"
mode = "proxy"
outpost = "kubernetes-outpost"
skip_path_regex = <<-EOT
/clients
EOT
}
"pasarguard" = {
name = "PasarGuard"
slug = "pasarguard"
group = "Tools"
external_host = "https://ps.hexor.cy"
internal_host = "https://pasarguard.pasarguard.svc:80"
internal_host_ssl_validation = false
meta_description = ""
skip_path_regex = <<-EOT
/
/sub/
/dashboard/
/api/
EOT
meta_icon = "https://img.icons8.com/?size=100&id=fqAD3lAB6zTe&format=png&color=000000"
mode = "proxy"
outpost = "kubernetes-outpost"
create_group = true
access_groups = ["admins"]
}
"ollama-public" = {
name = "Ollama Public"
slug = "ollama-public"
group = "AI"
external_host = "https://ollama.hexor.cy"
internal_host = "http://ollama.ollama.svc:11434"
internal_host_ssl_validation = false
meta_description = ""
meta_icon = "https://img.icons8.com/external-icongeek26-outline-icongeek26/64/external-llama-animal-head-icongeek26-outline-icongeek26.png"
mode = "proxy"
outpost = "kubernetes-outpost"
intercept_header_auth = true
create_group = true
access_groups = ["admins"]
}
}
-8
View File
@@ -1,8 +0,0 @@
terraform {
cloud {
organization = "ultradesu"
workspaces {
name = "Authentik"
}
}
}
-28
View File
@@ -1,28 +0,0 @@
authentik_url = "https://idm.hexor.cy"
flows = {
}
tags = {
environment = "production"
managed_by = "terraform"
project = "homelab"
}
outposts = {
"kubernetes-outpost" = {
name = "authentik Embedded Outpost"
type = "proxy"
service_connection = "k8s-cluster"
config = {
authentik_host = "https://idm.hexor.cy"
authentik_host_insecure = false
log_level = "info"
error_reporting = true
#container_image = null
}
}
}
-174
View File
@@ -1,174 +0,0 @@
variable "oauth_applications" {
description = "Map of OAuth2/OpenID applications"
type = map(object({
name = string
slug = string
group = optional(string, "")
policy_engine_mode = optional(string, "any")
meta_description = optional(string, "")
meta_launch_url = optional(string, "")
meta_icon = optional(string, "")
redirect_uris = list(string)
client_type = optional(string, "confidential")
client_id = optional(string, null)
include_claims_in_id_token = optional(bool, true)
access_code_validity = optional(string, "minutes=1")
access_token_validity = optional(string, "minutes=5")
refresh_token_validity = optional(string, "days=30")
property_mappings = optional(list(string), [])
authorization_flow = optional(string, null)
signing_key = optional(string, null)
outpost = optional(string, null)
create_group = optional(bool, false)
access_groups = optional(list(string), [])
scope_mappings = optional(list(string), ["openid", "profile", "email"])
}))
default = {}
}
variable "proxy_applications" {
description = "Map of Proxy applications"
type = map(object({
name = string
slug = string
group = optional(string, "")
policy_engine_mode = optional(string, "any")
meta_description = optional(string, "")
meta_launch_url = optional(string, "")
meta_icon = optional(string, "")
external_host = string
internal_host = optional(string, "")
internal_host_ssl_validation = optional(bool, true)
mode = optional(string, "proxy")
intercept_header_auth = optional(bool, false)
basic_auth_enabled = optional(bool, false)
basic_auth_username_attribute = optional(string, "")
basic_auth_password_attribute = optional(string, "")
cookie_domain = optional(string, "")
authorization_flow = optional(string, null)
skip_path_regex = optional(string, "")
outpost = optional(string, null)
create_group = optional(bool, false)
access_groups = optional(list(string), [])
}))
default = {}
}
variable "saml_applications" {
description = "Map of SAML applications"
type = map(object({
name = string
slug = string
group = optional(string, "")
policy_engine_mode = optional(string, "any")
meta_description = optional(string, "")
meta_launch_url = optional(string, "")
meta_icon = optional(string, "")
acs_url = string
issuer = string
audience = string
sp_binding = optional(string, "post")
signing_key = optional(string, null)
property_mappings = optional(list(string), [])
name_id_mapping = optional(string, null)
assertion_valid_not_before = optional(string, "minutes=-5")
assertion_valid_not_on_or_after = optional(string, "minutes=5")
session_valid_not_on_or_after = optional(string, "minutes=86400")
authorization_flow = optional(string, null)
}))
default = {}
}
variable "outposts" {
description = "Map of Outposts (only proxy type supported)"
type = map(object({
name = string
config = optional(map(any), {})
}))
default = {}
}
variable "flows" {
description = "Map of authentication flows"
type = map(object({
name = string
title = string
slug = string
designation = string
policy_engine_mode = optional(string, "any")
compatibility_mode = optional(bool, false)
layout = optional(string, "stacked")
denied_action = optional(string, "message_continue")
}))
default = {}
}
variable "groups" {
description = "Map of user groups"
type = map(object({
name = string
is_superuser = optional(bool, false)
parent = optional(string, null)
attributes = optional(map(any), {})
}))
default = {}
}
variable "certificates" {
description = "Map of certificates for HTTPS"
type = map(object({
name = string
certificate_data = string
key_data = string
managed = optional(string, null)
}))
default = {}
}
variable "property_mappings" {
description = "Custom property mappings for SAML/OAuth"
type = map(object({
name = string
expression = string
saml_name = optional(string, null)
oidc_scope = optional(string, null)
}))
default = {}
}
variable "default_authorization_flow" {
description = "Default authorization flow slug"
type = string
default = "default-provider-authorization-implicit-consent"
}
variable "default_authentication_flow" {
description = "Default authentication flow slug"
type = string
default = "default-authentication-flow"
}
variable "default_invalidation_flow" {
description = "Default invalidation flow slug"
type = string
default = "default-provider-invalidation-flow"
}
variable "tags" {
description = "Tags to apply to all resources"
type = map(string)
default = {}
}
variable "authentik_url" {
description = "Authentik URL"
type = string
default = "https://idm.hexor.cy"
}
variable "authentik_token" {
description = "Authentik API token (set via TF_VAR_authentik_token env var)"
type = string
sensitive = true
}
+14 -1
View File
@@ -16,6 +16,14 @@ proxy_applications = {
domain = "pass.hexor.cy"
allowed_groups = ["hexor-admin", "app-pass"]
}
Prometheus = {
domain = "prom.hexor.cy"
allowed_groups = ["hexor-admin"]
}
FireflyImporter = {
domain = "import.hexor.cy"
allowed_groups = ["hexor-admin"]
}
}
oauth2_applications = {
@@ -66,7 +74,7 @@ oauth2_applications = {
post_logout_redirect_uris = ["https://docs.hexor.cy/*"]
}
Immich = {
redirect_uris = [
redirect_uris = [
"https://photos.hexor.cy/auth/login",
"https://photos.hexor.cy/user-settings",
"app.immich:///oauth-callback",
@@ -91,4 +99,9 @@ oauth2_applications = {
web_origins = ["https://auth.matrix.hexor.cy"]
post_logout_redirect_uris = ["https://auth.matrix.hexor.cy/*"]
}
Amnezia-Fellow = {
redirect_uris = ["https://awg.hexor.cy/auth/oidc/callback"]
web_origins = ["https://awg.hexor.cy"]
post_logout_redirect_uris = ["https://awg.hexor.cy/*"]
}
}