Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10f31e7437 |
@@ -42,7 +42,10 @@ ArgoCD homelab project
|
|||||||
|
|
||||||
| Application | Status |
|
| Application | Status |
|
||||||
| :--- | :---: |
|
| :--- | :---: |
|
||||||
|
| **amnezia** | [](https://ag.hexor.cy/applications/argocd/amnezia) |
|
||||||
| **comfyui** | [](https://ag.hexor.cy/applications/argocd/comfyui) |
|
| **comfyui** | [](https://ag.hexor.cy/applications/argocd/comfyui) |
|
||||||
|
| **doka2-lobby-list** | [](https://ag.hexor.cy/applications/argocd/doka2-lobby-list) |
|
||||||
|
| **firefly-iii** | [](https://ag.hexor.cy/applications/argocd/firefly-iii) |
|
||||||
| **furumi** | [](https://ag.hexor.cy/applications/argocd/furumi) |
|
| **furumi** | [](https://ag.hexor.cy/applications/argocd/furumi) |
|
||||||
| **gitea** | [](https://ag.hexor.cy/applications/argocd/gitea) |
|
| **gitea** | [](https://ag.hexor.cy/applications/argocd/gitea) |
|
||||||
| **greece-notifier** | [](https://ag.hexor.cy/applications/argocd/greece-notifier) |
|
| **greece-notifier** | [](https://ag.hexor.cy/applications/argocd/greece-notifier) |
|
||||||
@@ -53,6 +56,7 @@ ArgoCD homelab project
|
|||||||
| **k8s-secrets** | [](https://ag.hexor.cy/applications/argocd/k8s-secrets) |
|
| **k8s-secrets** | [](https://ag.hexor.cy/applications/argocd/k8s-secrets) |
|
||||||
| **khm** | [](https://ag.hexor.cy/applications/argocd/khm) |
|
| **khm** | [](https://ag.hexor.cy/applications/argocd/khm) |
|
||||||
| **lidarr** | [](https://ag.hexor.cy/applications/argocd/lidarr) |
|
| **lidarr** | [](https://ag.hexor.cy/applications/argocd/lidarr) |
|
||||||
|
| **llamacpp** | [](https://ag.hexor.cy/applications/argocd/llamacpp) |
|
||||||
| **matrix** | [](https://ag.hexor.cy/applications/argocd/matrix) |
|
| **matrix** | [](https://ag.hexor.cy/applications/argocd/matrix) |
|
||||||
| **mtproxy** | [](https://ag.hexor.cy/applications/argocd/mtproxy) |
|
| **mtproxy** | [](https://ag.hexor.cy/applications/argocd/mtproxy) |
|
||||||
| **n8n** | [](https://ag.hexor.cy/applications/argocd/n8n) |
|
| **n8n** | [](https://ag.hexor.cy/applications/argocd/n8n) |
|
||||||
|
|||||||
@@ -10,10 +10,7 @@ data:
|
|||||||
|
|
||||||
PORT="${1:-5847}"
|
PORT="${1:-5847}"
|
||||||
VPN_CIDR="${2:-10.8.0.0/16}"
|
VPN_CIDR="${2:-10.8.0.0/16}"
|
||||||
POLICY_FILE="${3:-/run/amnezia/policy/policy.conf}"
|
|
||||||
FORWARD_CHAIN="AMNEZIAWG-FORWARD"
|
FORWARD_CHAIN="AMNEZIAWG-FORWARD"
|
||||||
POLICY_CHAIN_A="AMNEZIAWG-POLICY-A"
|
|
||||||
POLICY_CHAIN_B="AMNEZIAWG-POLICY-B"
|
|
||||||
IPTABLES=(iptables -w 30)
|
IPTABLES=(iptables -w 30)
|
||||||
|
|
||||||
external_interface() {
|
external_interface() {
|
||||||
@@ -52,84 +49,10 @@ data:
|
|||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
ensure_chain() {
|
reset_chain() {
|
||||||
"${IPTABLES[@]}" -N "$1" >/dev/null 2>&1 || true
|
local chain="$1"
|
||||||
}
|
"${IPTABLES[@]}" -N "${chain}" >/dev/null 2>&1 || true
|
||||||
|
"${IPTABLES[@]}" -F "${chain}"
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
EXT_IF="$(external_interface || true)"
|
EXT_IF="$(external_interface || true)"
|
||||||
@@ -142,7 +65,6 @@ data:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
sysctl -w net.ipv4.ip_forward=1
|
sysctl -w net.ipv4.ip_forward=1
|
||||||
sysctl -w net.ipv4.conf.awg0.send_redirects=0
|
|
||||||
|
|
||||||
# Remove rules written by older revisions. In particular, the unqualified
|
# Remove rules written by older revisions. In particular, the unqualified
|
||||||
# tailscale UDP DROP also blocks Flannel VXLAN (UDP/8472).
|
# tailscale UDP DROP also blocks Flannel VXLAN (UDP/8472).
|
||||||
@@ -157,21 +79,11 @@ data:
|
|||||||
"${IPTABLES[@]}" -F AMNEZIAWG-INPUT >/dev/null 2>&1 || true
|
"${IPTABLES[@]}" -F AMNEZIAWG-INPUT >/dev/null 2>&1 || true
|
||||||
"${IPTABLES[@]}" -X AMNEZIAWG-INPUT >/dev/null 2>&1 || true
|
"${IPTABLES[@]}" -X AMNEZIAWG-INPUT >/dev/null 2>&1 || true
|
||||||
|
|
||||||
ensure_chain "${FORWARD_CHAIN}"
|
reset_chain "${FORWARD_CHAIN}"
|
||||||
ensure_chain "${POLICY_CHAIN_A}"
|
"${IPTABLES[@]}" -A "${FORWARD_CHAIN}" -i awg0 -m comment --comment amneziawg-forward-in -j ACCEPT
|
||||||
ensure_chain "${POLICY_CHAIN_B}"
|
"${IPTABLES[@]}" -A "${FORWARD_CHAIN}" -o awg0 -m comment --comment amneziawg-forward-out -j ACCEPT
|
||||||
if ! "${IPTABLES[@]}" -S "${FORWARD_CHAIN}" 2>/dev/null | grep -q -- '-j RETURN'; then
|
"${IPTABLES[@]}" -A "${FORWARD_CHAIN}" -j RETURN
|
||||||
"${IPTABLES[@]}" -A "${FORWARD_CHAIN}" -j RETURN
|
ensure_rule -A FORWARD -m comment --comment amneziawg-forward-jump -j "${FORWARD_CHAIN}"
|
||||||
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
|
ensure_rule -A -t nat POSTROUTING -s "${VPN_CIDR}" -o "${EXT_IF}" -m comment --comment amneziawg-masquerade -j MASQUERADE
|
||||||
|
|
||||||
@@ -184,8 +96,6 @@ data:
|
|||||||
PORT="${1:-5847}"
|
PORT="${1:-5847}"
|
||||||
VPN_CIDR="${2:-10.8.0.0/16}"
|
VPN_CIDR="${2:-10.8.0.0/16}"
|
||||||
FORWARD_CHAIN="AMNEZIAWG-FORWARD"
|
FORWARD_CHAIN="AMNEZIAWG-FORWARD"
|
||||||
POLICY_CHAIN_A="AMNEZIAWG-POLICY-A"
|
|
||||||
POLICY_CHAIN_B="AMNEZIAWG-POLICY-B"
|
|
||||||
IPTABLES=(iptables -w 30)
|
IPTABLES=(iptables -w 30)
|
||||||
|
|
||||||
external_interface() {
|
external_interface() {
|
||||||
@@ -229,10 +139,6 @@ data:
|
|||||||
"${IPTABLES[@]}" -F AMNEZIAWG-INPUT >/dev/null 2>&1 || true
|
"${IPTABLES[@]}" -F AMNEZIAWG-INPUT >/dev/null 2>&1 || true
|
||||||
"${IPTABLES[@]}" -X 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 "${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
|
"${IPTABLES[@]}" -X "${FORWARD_CHAIN}" >/dev/null 2>&1 || true
|
||||||
|
|
||||||
firewall-check.sh: |
|
firewall-check.sh: |
|
||||||
@@ -270,26 +176,11 @@ data:
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
FIRST_FORWARD_RULE="$("${IPTABLES[@]}" -S FORWARD | grep '^-A FORWARD ' | sed -n '1p')"
|
"${IPTABLES[@]}" -C FORWARD -m comment --comment amneziawg-forward-jump -j "${FORWARD_CHAIN}"
|
||||||
[[ "${FIRST_FORWARD_RULE}" == *"--comment amneziawg-forward-jump"* ]]
|
"${IPTABLES[@]}" -C "${FORWARD_CHAIN}" -i awg0 -m comment --comment amneziawg-forward-in -j ACCEPT
|
||||||
[[ "${FIRST_FORWARD_RULE}" == *"-j ${FORWARD_CHAIN}"* ]]
|
"${IPTABLES[@]}" -C "${FORWARD_CHAIN}" -o awg0 -m comment --comment amneziawg-forward-out -j ACCEPT
|
||||||
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
|
"${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
|
awg show awg0 >/dev/null 2>&1
|
||||||
|
|
||||||
run.sh: |
|
run.sh: |
|
||||||
@@ -298,8 +189,6 @@ data:
|
|||||||
|
|
||||||
SERVER_CONFIG="/etc/amnezia/server/awg0.conf"
|
SERVER_CONFIG="/etc/amnezia/server/awg0.conf"
|
||||||
CLIENTS_DIR="${AMNEZIAWG_CLIENTS_DIR:-/run/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"
|
RUNTIME_CONFIG="/run/amnezia/awg0.conf"
|
||||||
SYNC_CONFIG="/run/amnezia/awg0.sync.conf"
|
SYNC_CONFIG="/run/amnezia/awg0.sync.conf"
|
||||||
STATUS_FILE="/run/amnezia/reload-status"
|
STATUS_FILE="/run/amnezia/reload-status"
|
||||||
@@ -334,12 +223,13 @@ data:
|
|||||||
chmod 0600 "${RUNTIME_CONFIG}"
|
chmod 0600 "${RUNTIME_CONFIG}"
|
||||||
}
|
}
|
||||||
|
|
||||||
runtime_config_hash() {
|
client_config_hash() {
|
||||||
if [ -f "${CONFIG_GENERATION}" ]; then
|
{
|
||||||
sha256sum "${CONFIG_GENERATION}" | awk '{print $1}'
|
for client_config in "${CLIENTS_DIR}"/*; do
|
||||||
else
|
[ -f "${client_config}" ] || continue
|
||||||
printf 'missing\n'
|
sha256sum "${client_config}"
|
||||||
fi
|
done
|
||||||
|
} | sha256sum | awk '{print $1}'
|
||||||
}
|
}
|
||||||
|
|
||||||
write_reload_status() {
|
write_reload_status() {
|
||||||
@@ -360,14 +250,10 @@ data:
|
|||||||
}
|
}
|
||||||
|
|
||||||
apply_live_config() {
|
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
|
render_config
|
||||||
awg-quick strip "${RUNTIME_CONFIG}" > "${SYNC_CONFIG}"
|
awg-quick strip "${RUNTIME_CONFIG}" > "${SYNC_CONFIG}"
|
||||||
chmod 0600 "${SYNC_CONFIG}"
|
chmod 0600 "${SYNC_CONFIG}"
|
||||||
awg syncconf awg0 "${SYNC_CONFIG}"
|
awg syncconf awg0 "${SYNC_CONFIG}"
|
||||||
/scripts/firewall-up.sh 5847 10.8.0.0/16 "${POLICY_FILE}"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
watch_client_config() {
|
watch_client_config() {
|
||||||
@@ -377,7 +263,7 @@ data:
|
|||||||
wait "$!" || return 0
|
wait "$!" || return 0
|
||||||
|
|
||||||
local current_hash
|
local current_hash
|
||||||
current_hash="$(runtime_config_hash)"
|
current_hash="$(client_config_hash)"
|
||||||
if [ "${current_hash}" = "${last_hash}" ]; then
|
if [ "${current_hash}" = "${last_hash}" ]; then
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
@@ -397,7 +283,7 @@ data:
|
|||||||
trap cleanup EXIT
|
trap cleanup EXIT
|
||||||
trap 'exit 0' TERM INT
|
trap 'exit 0' TERM INT
|
||||||
|
|
||||||
initial_hash="$(runtime_config_hash)"
|
initial_hash="$(client_config_hash)"
|
||||||
render_config
|
render_config
|
||||||
cleanup
|
cleanup
|
||||||
awg-quick up "${RUNTIME_CONFIG}"
|
awg-quick up "${RUNTIME_CONFIG}"
|
||||||
@@ -413,8 +299,6 @@ data:
|
|||||||
CLIENT_SECRET_KEY="${AMNEZIAWG_CLIENT_SECRET_KEY:-peers.conf}"
|
CLIENT_SECRET_KEY="${AMNEZIAWG_CLIENT_SECRET_KEY:-peers.conf}"
|
||||||
CLIENTS_DIR="${AMNEZIAWG_CLIENTS_DIR:-/run/amnezia/clients}"
|
CLIENTS_DIR="${AMNEZIAWG_CLIENTS_DIR:-/run/amnezia/clients}"
|
||||||
PEERS_FILE="${CLIENTS_DIR}/peers.conf"
|
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}"
|
SYNC_INTERVAL="${AMNEZIAWG_CLIENT_SECRET_SYNC_INTERVAL:-5}"
|
||||||
NAMESPACE="${POD_NAMESPACE:-$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)}"
|
NAMESPACE="${POD_NAMESPACE:-$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)}"
|
||||||
|
|
||||||
@@ -424,66 +308,33 @@ data:
|
|||||||
: > "${PEERS_FILE}"
|
: > "${PEERS_FILE}"
|
||||||
chmod 0600 "${PEERS_FILE}"
|
chmod 0600 "${PEERS_FILE}"
|
||||||
fi
|
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() {
|
sync_once() {
|
||||||
mkdir -p "${CLIENTS_DIR}"
|
mkdir -p "${CLIENTS_DIR}"
|
||||||
mkdir -p "$(dirname "${POLICY_FILE}")"
|
local tmp_file="${PEERS_FILE}.tmp"
|
||||||
local snapshot="${CONFIG_GENERATION}.snapshot"
|
local encoded=""
|
||||||
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}" \
|
if ! encoded="$(kubectl get secret "${CLIENT_SECRET}" -n "${NAMESPACE}" -o "go-template={{ index .data \"${CLIENT_SECRET_KEY}\" }}" 2>/dev/null)"; then
|
||||||
-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
|
echo "WARN: failed to read Secret ${NAMESPACE}/${CLIENT_SECRET}; keeping current peers" >&2
|
||||||
rm -f "${snapshot}"
|
|
||||||
write_empty_once
|
write_empty_once
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
peers_encoded="$(sed -n '1p' "${snapshot}")"
|
if [ -n "${encoded}" ]; then
|
||||||
policy_encoded="$(sed -n '2p' "${snapshot}")"
|
printf '%s' "${encoded}" | base64 -d > "${tmp_file}"
|
||||||
rm -f "${snapshot}"
|
|
||||||
|
|
||||||
if [ -n "${peers_encoded}" ]; then
|
|
||||||
printf '%s' "${peers_encoded}" | base64 -d > "${peers_tmp}"
|
|
||||||
else
|
else
|
||||||
: > "${peers_tmp}"
|
: > "${tmp_file}"
|
||||||
fi
|
fi
|
||||||
if [ -n "${policy_encoded}" ]; then
|
chmod 0600 "${tmp_file}"
|
||||||
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="$({
|
if [ -f "${PEERS_FILE}" ] && cmp -s "${tmp_file}" "${PEERS_FILE}"; then
|
||||||
sha256sum "${peers_tmp}"
|
rm -f "${tmp_file}"
|
||||||
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
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
mv "${policy_tmp}" "${POLICY_FILE}"
|
mv "${tmp_file}" "${PEERS_FILE}"
|
||||||
mv "${peers_tmp}" "${PEERS_FILE}"
|
echo "Synced AmneziaWG client peers from Secret ${NAMESPACE}/${CLIENT_SECRET}:${CLIENT_SECRET_KEY}"
|
||||||
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
|
if [ "${1:-}" = "once" ]; then
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
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
|
|
||||||
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
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
|
|
||||||
@@ -46,40 +46,4 @@ spec:
|
|||||||
remoteRef:
|
remoteRef:
|
||||||
key: a1867c81-715c-47cd-978d-14ea5bcedea9
|
key: a1867c81-715c-47cd-978d-14ea5bcedea9
|
||||||
property: fields[2].value
|
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
|
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ kind: Kustomization
|
|||||||
|
|
||||||
resources:
|
resources:
|
||||||
- app.yaml
|
- app.yaml
|
||||||
- data-importer.yaml
|
|
||||||
- data-importer-ingress.yaml
|
|
||||||
- external-secrets.yaml
|
- external-secrets.yaml
|
||||||
- postgres.yaml
|
- postgres.yaml
|
||||||
- storage.yaml
|
- storage.yaml
|
||||||
@@ -23,7 +21,7 @@ helmCharts:
|
|||||||
|
|
||||||
patches:
|
patches:
|
||||||
# Chart 0.1.2 requires postgres.enabled=true. Remove its StatefulSet and
|
# Chart 0.1.2 requires postgres.enabled=true. Remove its StatefulSet and
|
||||||
# make the generated database Service select the CloudNativePG instance.
|
# redirect the generated database Service to the CloudNativePG primary.
|
||||||
- target:
|
- target:
|
||||||
group: apps
|
group: apps
|
||||||
version: v1
|
version: v1
|
||||||
@@ -40,10 +38,14 @@ patches:
|
|||||||
kind: Service
|
kind: Service
|
||||||
name: firefly-iii-postgres
|
name: firefly-iii-postgres
|
||||||
patch: |-
|
patch: |-
|
||||||
- op: replace
|
- op: remove
|
||||||
path: /spec/selector
|
path: /spec/selector
|
||||||
value:
|
- op: replace
|
||||||
cnpg.io/cluster: firefly-postgres
|
path: /spec/type
|
||||||
|
value: ExternalName
|
||||||
|
- op: add
|
||||||
|
path: /spec/externalName
|
||||||
|
value: firefly-postgres-rw.firefly-iii.svc.cluster.local
|
||||||
# The upstream chart does not expose scheduling settings for its CronJob.
|
# The upstream chart does not expose scheduling settings for its CronJob.
|
||||||
- target:
|
- target:
|
||||||
group: batch
|
group: batch
|
||||||
@@ -51,15 +53,6 @@ patches:
|
|||||||
kind: CronJob
|
kind: CronJob
|
||||||
name: firefly-iii-cron
|
name: firefly-iii-cron
|
||||||
patch: |-
|
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
|
- op: add
|
||||||
path: /spec/jobTemplate/spec/template/spec/nodeSelector
|
path: /spec/jobTemplate/spec/template/spec/nodeSelector
|
||||||
value:
|
value:
|
||||||
@@ -71,16 +64,6 @@ patches:
|
|||||||
operator: Equal
|
operator: Equal
|
||||||
value: ai
|
value: ai
|
||||||
effect: NoSchedule
|
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.
|
# A second independent filter in addition to ingressClassName.
|
||||||
- target:
|
- target:
|
||||||
group: networking.k8s.io
|
group: networking.k8s.io
|
||||||
@@ -91,11 +74,3 @@ patches:
|
|||||||
- op: add
|
- op: add
|
||||||
path: /metadata/labels/firefly.hexor.cy~1private-ai
|
path: /metadata/labels/firefly.hexor.cy~1private-ai
|
||||||
value: "true"
|
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
|
|
||||||
|
|||||||
@@ -19,14 +19,6 @@ spec:
|
|||||||
matchLabels:
|
matchLabels:
|
||||||
app.kubernetes.io/instance: firefly-iii
|
app.kubernetes.io/instance: firefly-iii
|
||||||
app.kubernetes.io/name: 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:
|
ports:
|
||||||
- protocol: TCP
|
- protocol: TCP
|
||||||
port: 8080
|
port: 8080
|
||||||
@@ -64,35 +56,4 @@ spec:
|
|||||||
ports:
|
ports:
|
||||||
- protocol: TCP
|
- protocol: TCP
|
||||||
port: 8000
|
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
|
|
||||||
|
|||||||
@@ -114,8 +114,6 @@ spec:
|
|||||||
app.kubernetes.io/name: traefik-ai
|
app.kubernetes.io/name: traefik-ai
|
||||||
spec:
|
spec:
|
||||||
serviceAccountName: traefik-ai
|
serviceAccountName: traefik-ai
|
||||||
hostNetwork: true
|
|
||||||
dnsPolicy: ClusterFirstWithHostNet
|
|
||||||
nodeSelector:
|
nodeSelector:
|
||||||
kubernetes.io/hostname: ai.tail2fe2d.ts.net
|
kubernetes.io/hostname: ai.tail2fe2d.ts.net
|
||||||
tolerations:
|
tolerations:
|
||||||
@@ -128,7 +126,7 @@ spec:
|
|||||||
image: rancher/mirrored-library-traefik:3.6.13
|
image: rancher/mirrored-library-traefik:3.6.13
|
||||||
imagePullPolicy: IfNotPresent
|
imagePullPolicy: IfNotPresent
|
||||||
args:
|
args:
|
||||||
- --entrypoints.web.address=192.168.1.117:80
|
- --entrypoints.web.address=:8000
|
||||||
- --providers.kubernetesingress=true
|
- --providers.kubernetesingress=true
|
||||||
- --providers.kubernetesingress.namespaces=firefly-iii
|
- --providers.kubernetesingress.namespaces=firefly-iii
|
||||||
- --providers.kubernetesingress.ingressclass=traefik-ai
|
- --providers.kubernetesingress.ingressclass=traefik-ai
|
||||||
@@ -138,18 +136,15 @@ spec:
|
|||||||
- --log.level=INFO
|
- --log.level=INFO
|
||||||
ports:
|
ports:
|
||||||
- name: web
|
- name: web
|
||||||
containerPort: 80
|
containerPort: 8000
|
||||||
hostPort: 80
|
hostPort: 80
|
||||||
hostIP: 192.168.1.117
|
|
||||||
protocol: TCP
|
protocol: TCP
|
||||||
readinessProbe:
|
readinessProbe:
|
||||||
tcpSocket:
|
tcpSocket:
|
||||||
host: 192.168.1.117
|
|
||||||
port: web
|
port: web
|
||||||
periodSeconds: 10
|
periodSeconds: 10
|
||||||
livenessProbe:
|
livenessProbe:
|
||||||
tcpSocket:
|
tcpSocket:
|
||||||
host: 192.168.1.117
|
|
||||||
port: web
|
port: web
|
||||||
periodSeconds: 30
|
periodSeconds: 30
|
||||||
resources:
|
resources:
|
||||||
@@ -164,12 +159,9 @@ spec:
|
|||||||
capabilities:
|
capabilities:
|
||||||
drop:
|
drop:
|
||||||
- ALL
|
- ALL
|
||||||
add:
|
|
||||||
- NET_BIND_SERVICE
|
|
||||||
readOnlyRootFilesystem: true
|
readOnlyRootFilesystem: true
|
||||||
runAsNonRoot: false
|
runAsNonRoot: true
|
||||||
runAsUser: 0
|
runAsUser: 65532
|
||||||
runAsGroup: 0
|
|
||||||
securityContext:
|
securityContext:
|
||||||
seccompProfile:
|
seccompProfile:
|
||||||
type: RuntimeDefault
|
type: RuntimeDefault
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ persistence:
|
|||||||
existingClaim: firefly-uploads
|
existingClaim: firefly-uploads
|
||||||
|
|
||||||
# Must remain true because chart 0.1.2 validates it. Kustomize removes the
|
# Must remain true because chart 0.1.2 validates it. Kustomize removes the
|
||||||
# generated StatefulSet and makes this Service select the CloudNativePG Pod.
|
# generated StatefulSet and redirects this Service to CloudNativePG.
|
||||||
postgres:
|
postgres:
|
||||||
enabled: true
|
enabled: true
|
||||||
auth:
|
auth:
|
||||||
@@ -66,3 +66,4 @@ firefly:
|
|||||||
limits:
|
limits:
|
||||||
cpu: "2"
|
cpu: "2"
|
||||||
memory: 1Gi
|
memory: 1Gi
|
||||||
|
|
||||||
|
|||||||
@@ -79,7 +79,6 @@ spec:
|
|||||||
labels:
|
labels:
|
||||||
app: gitea-runner
|
app: gitea-runner
|
||||||
spec:
|
spec:
|
||||||
serviceAccountName: gitea-runner
|
|
||||||
dnsConfig:
|
dnsConfig:
|
||||||
options:
|
options:
|
||||||
- name: ndots
|
- name: ndots
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ resources:
|
|||||||
- app.yaml
|
- app.yaml
|
||||||
- external-secrets.yaml
|
- external-secrets.yaml
|
||||||
- deployment.yaml
|
- deployment.yaml
|
||||||
- runner-rbac.yaml
|
|
||||||
- runner-rebalance.yaml
|
|
||||||
- user-unban-cronjob.yaml
|
- user-unban-cronjob.yaml
|
||||||
- service.yaml
|
- service.yaml
|
||||||
- ingress.yaml
|
- ingress.yaml
|
||||||
|
|||||||
@@ -1,52 +0,0 @@
|
|||||||
---
|
|
||||||
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
|
|
||||||
@@ -1,127 +0,0 @@
|
|||||||
---
|
|
||||||
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
|
|
||||||
@@ -10,7 +10,7 @@ resources:
|
|||||||
helmCharts:
|
helmCharts:
|
||||||
- name: argo-cd
|
- name: argo-cd
|
||||||
repo: https://argoproj.github.io/argo-helm
|
repo: https://argoproj.github.io/argo-helm
|
||||||
version: 10.2.1
|
version: 9.4.10
|
||||||
releaseName: argocd
|
releaseName: argocd
|
||||||
namespace: argocd
|
namespace: argocd
|
||||||
valuesFile: values.yaml
|
valuesFile: values.yaml
|
||||||
|
|||||||
Generated
+44
@@ -0,0 +1,44 @@
|
|||||||
|
# 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",
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
# 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
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
groups = {
|
||||||
|
"admins" = {
|
||||||
|
name = "Administrators"
|
||||||
|
is_superuser = true
|
||||||
|
attributes = {
|
||||||
|
notes = "Managed by Terraform"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,361 @@
|
|||||||
|
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
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
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"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,151 @@
|
|||||||
|
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 = []
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
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 = {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,232 @@
|
|||||||
|
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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
terraform {
|
||||||
|
required_providers {
|
||||||
|
authentik = {
|
||||||
|
source = "goauthentik/authentik"
|
||||||
|
version = "2025.12.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
provider "authentik" {
|
||||||
|
url = var.authentik_url
|
||||||
|
token = var.authentik_token
|
||||||
|
}
|
||||||
@@ -0,0 +1,193 @@
|
|||||||
|
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"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
terraform {
|
||||||
|
cloud {
|
||||||
|
organization = "ultradesu"
|
||||||
|
workspaces {
|
||||||
|
name = "Authentik"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,174 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@@ -20,10 +20,6 @@ proxy_applications = {
|
|||||||
domain = "prom.hexor.cy"
|
domain = "prom.hexor.cy"
|
||||||
allowed_groups = ["hexor-admin"]
|
allowed_groups = ["hexor-admin"]
|
||||||
}
|
}
|
||||||
FireflyImporter = {
|
|
||||||
domain = "import.hexor.cy"
|
|
||||||
allowed_groups = ["hexor-admin"]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
oauth2_applications = {
|
oauth2_applications = {
|
||||||
@@ -74,7 +70,7 @@ oauth2_applications = {
|
|||||||
post_logout_redirect_uris = ["https://docs.hexor.cy/*"]
|
post_logout_redirect_uris = ["https://docs.hexor.cy/*"]
|
||||||
}
|
}
|
||||||
Immich = {
|
Immich = {
|
||||||
redirect_uris = [
|
redirect_uris = [
|
||||||
"https://photos.hexor.cy/auth/login",
|
"https://photos.hexor.cy/auth/login",
|
||||||
"https://photos.hexor.cy/user-settings",
|
"https://photos.hexor.cy/user-settings",
|
||||||
"app.immich:///oauth-callback",
|
"app.immich:///oauth-callback",
|
||||||
|
|||||||
Reference in New Issue
Block a user