fix(platform): isolate authentik admin styling

This commit is contained in:
Codex
2026-05-25 01:08:50 +03:00
parent e9def6672c
commit c1ac8b1a10
16 changed files with 670 additions and 61 deletions
+2
View File
@@ -2,6 +2,8 @@
# This intentionally uses high ports and does not touch existing nodedc-demo.
AUTH_DOMAIN=auth.nas.nodedc
AUTH_ADMIN_DOMAIN=auth-admin.nas.nodedc
AUTH_ADMIN_LAN_HOST=172.22.0.222
LAUNCHER_DOMAIN=launcher.nas.nodedc
TASK_DOMAIN=task.nas.nodedc
NODEDC_PUBLIC_HTTP_PORT=18080
+17
View File
@@ -3,6 +3,9 @@
}
http://{$AUTH_DOMAIN} {
@auth_admin path /if/admin /if/admin/*
redir @auth_admin http://{$AUTH_ADMIN_DOMAIN:auth-admin.nas.nodedc}:{$NODEDC_PUBLIC_HTTP_PORT}{uri} 302
@auth_root path /
redir @auth_root http://{$LAUNCHER_DOMAIN}:{$NODEDC_PUBLIC_HTTP_PORT}/ 302
@@ -16,6 +19,17 @@ http://{$AUTH_DOMAIN} {
}
}
http://{$AUTH_ADMIN_DOMAIN:auth-admin.nas.nodedc}, http://{$AUTH_ADMIN_LAN_HOST:172.22.0.222} {
@admin_root path /
redir @admin_root /if/admin/ 302
reverse_proxy authentik-server:9000 {
header_up Host {http.request.host}
header_up X-Forwarded-Proto {scheme}
header_up X-Forwarded-For {remote_host}
}
}
http://{$LAUNCHER_DOMAIN} {
reverse_proxy launcher:5173 {
header_up Host {http.request.host}
@@ -35,6 +49,9 @@ http://{$TASK_DOMAIN} {
}
http://id.nodedc.ru {
@auth_admin path /if/admin /if/admin/*
respond @auth_admin 404
@auth_root path /
redir @auth_root https://hub.nodedc.ru/ 302
+64 -3
View File
@@ -19,6 +19,8 @@ https://ops.nodedc.ru -> Tasker / Operational Core
https://ops-agents.nodedc.ru -> Ops Agents Gateway / MCP
```
`id.nodedc.ru` is the user-facing OIDC/login host. Authentik Admin is intentionally not exposed through this public host; `/if/admin/*` returns `404` there.
В `Caddyfile.http` эти домены проксируются через локальный HTTP edge, но upstream получает `X-Forwarded-Proto: https` и `X-Forwarded-Port: 443`.
## Локальные домены для первичной проверки
@@ -27,6 +29,7 @@ https://ops-agents.nodedc.ru -> Ops Agents Gateway / MCP
```text
172.22.0.222 auth.nas.nodedc
172.22.0.222 auth-admin.nas.nodedc
172.22.0.222 launcher.nas.nodedc
172.22.0.222 task.nas.nodedc
```
@@ -35,15 +38,19 @@ https://ops-agents.nodedc.ru -> Ops Agents Gateway / MCP
```text
http://auth.nas.nodedc:18080
http://auth-admin.nas.nodedc:18080/if/admin/
http://172.22.0.222:18080/if/admin/
http://launcher.nas.nodedc:18080
http://task.nas.nodedc:18080
http://task.nas.nodedc:18090
```
`auth-admin.nas.nodedc` is the technical Authentik Admin entrypoint. `172.22.0.222:18080` is a local IP fallback for workstations without `auth-admin.nas.nodedc` DNS. Both keep Authentik access separate from the public `id.nodedc.ru` login entrypoint and do not load NODE.DC auth-flow CSS.
## Что входит
- `docker-compose.platform-http.yml` поднимает новый Authentik, Launcher и Caddy edge.
- `Caddyfile.http` маршрутизирует локальные `auth/launcher/task.nas.nodedc` и внешние `id/hub/ops.nodedc.ru`.
- `Caddyfile.http` маршрутизирует локальные `auth/auth-admin/launcher/task.nas.nodedc`, IP fallback `172.22.0.222` для Authentik Admin и внешние `id/hub/ops.nodedc.ru`.
- `deploy-current.sh` синхронизирует compose, Caddyfile и опционально Launcher source в NAS mount. Authentik templates синхронизируются только при явном `SYNC_AUTHENTIK_TEMPLATES=1`.
- `backup-current.sh` делает snapshot Launcher runtime/uploads/Auth templates/config и готовит команду `pg_dump` для Authentik Postgres.
- Tasker поднимается отдельным compose из `NODEDC_TASKMANAGER/plane-app/docker-compose.yaml` на порту `18090`.
@@ -134,7 +141,7 @@ TASKER_SYNC_SOURCE=1 ./infra/synology/deploy-current.sh
```bash
cd /volume1/docker/nodedc-platform/launcher/source
sudo /usr/local/bin/docker build -t nodedc/launcher:local .
sudo /usr/local/bin/docker build --no-cache -t nodedc/launcher:local .
cd /volume1/docker/nodedc-platform/platform
sudo /usr/local/bin/docker compose \
@@ -143,7 +150,61 @@ sudo /usr/local/bin/docker compose \
up -d --force-recreate --no-deps launcher
```
После такого deploy проверить `healthz`, запись в launcher storage/uploads и сценарий пользователя без аппрува: сохранение аватара не должно показывать экран `Заявка ожидает подтверждения`.
После такого deploy проверить `healthz`, запись в launcher storage/uploads и сценарий пользователя без аппрува: сохранение аватара не должно показывать экран `Заявка ожидает подтверждения`. Дополнительно проверить, что live bundle больше не содержит старый pending gate:
```bash
launcher_asset="$(
curl -k -sS --compressed -H 'Accept: text/html' https://hub.nodedc.ru/ \
| grep -aoE 'index-[A-Za-z0-9_-]+\.js' \
| head -n 1
)"
test -n "$launcher_asset"
if curl -k -sS --compressed "https://hub.nodedc.ru/assets/${launcher_asset}" \
| grep -aq 'Заявка ожидает подтверждения'; then
echo 'old pending gate still present'
exit 1
fi
echo 'launcher-pending-gate-ok'
```
## Authentik Admin и Brand CSS
NODE.DC auth-flow CSS must stay template-scoped. Do not store it in Authentik `Brand.branding_custom_css`: Authentik passes that CSS into Admin/User web component runtime and breaks native controls.
After syncing Authentik templates to NAS, recreate `reverse-proxy authentik-server authentik-worker launcher`, then clear existing global Brand CSS in the live DB:
```bash
cd /volume1/docker/nodedc-platform/platform
sudo bash clear-authentik-brand-css.sh
```
Verify:
```bash
id_admin_status="$(
curl -k -sS -o /dev/null -w '%{http_code}' https://id.nodedc.ru/if/admin/
)"
if [[ "$id_admin_status" != "404" ]]; then
echo "public id admin is not closed: status=${id_admin_status}"
exit 1
fi
echo 'public-id-admin-closed-ok'
auth_admin_page="$(
curl -k -fsS --compressed http://auth-admin.nas.nodedc:18080/if/admin/
)"
printf '%s' "$auth_admin_page" \
| grep -aE '<style data-id="brand-css"></style>|authentikBrand.branding_custom_css = ""'
auth_admin_flow="$(
curl -k -fsS --compressed http://auth-admin.nas.nodedc:18080/if/flow/default-authentication-flow/
)"
if printf '%s' "$auth_admin_flow" | grep -aq '<style data-id="nodedc-auth-login-css">'; then
echo 'admin host still has NODE.DC auth CSS'
exit 1
fi
echo 'auth-admin-css-ok'
```
## Backup текущего состояния
+113
View File
@@ -0,0 +1,113 @@
#!/usr/bin/env bash
set -euo pipefail
DOCKER_BIN="${DOCKER_BIN:-/usr/local/bin/docker}"
PLATFORM_DIR="${PLATFORM_DIR:-/volume1/docker/nodedc-platform/platform}"
LAUNCHER_SOURCE_DIR="${LAUNCHER_SOURCE_DIR:-/volume1/docker/nodedc-platform/launcher/source}"
ENV_FILE="${ENV_FILE:-${PLATFORM_DIR}/.env.synology}"
COMPOSE_FILE="${COMPOSE_FILE:-${PLATFORM_DIR}/docker-compose.platform-http.yml}"
if [[ ! -x "${DOCKER_BIN}" ]]; then
DOCKER_BIN="$(command -v docker)"
fi
cd "${PLATFORM_DIR}"
echo "== storage permissions =="
mkdir -p ../launcher/server-storage ../launcher/uploads
chown -R 1000:1000 ../launcher/server-storage ../launcher/uploads
chmod -R u+rwX,g+rwX ../launcher/server-storage ../launcher/uploads
echo "== launcher image build =="
cd "${LAUNCHER_SOURCE_DIR}"
"${DOCKER_BIN}" build --no-cache -t nodedc/launcher:local .
echo "== platform services recreate =="
cd "${PLATFORM_DIR}"
"${DOCKER_BIN}" compose \
--env-file "${ENV_FILE}" \
-f "${COMPOSE_FILE}" \
up -d --force-recreate --no-deps reverse-proxy authentik-server authentik-worker launcher
echo "== clear authentik global brand css =="
DOCKER_BIN="${DOCKER_BIN}" bash "${PLATFORM_DIR}/clear-authentik-brand-css.sh"
echo "== launcher storage check =="
"${DOCKER_BIN}" exec nodedc-platform-launcher-1 sh -lc \
'touch /app/server/storage/.write-test /app/server/storage/uploads/.write-test && rm /app/server/storage/.write-test /app/server/storage/uploads/.write-test && echo storage-ok'
echo "== launcher -> authentik api check =="
"${DOCKER_BIN}" exec nodedc-platform-launcher-1 sh -lc '
echo "$NODEDC_AUTHENTIK_BASE_URL"
getent hosts nodedc-platform-authentik-server
for attempt in 1 2 3 4 5 6 7 8 9 10; do
if wget -qSO- \
--header "Authorization: Bearer $NODEDC_AUTHENTIK_SERVICE_TOKEN" \
"$NODEDC_AUTHENTIK_BASE_URL/api/v3/core/groups/?search=nodedc_admin" \
>/tmp/authentik-api-check.json \
2>/tmp/authentik-api-check.headers; then
head -n 25 /tmp/authentik-api-check.headers
exit 0
fi
head -n 25 /tmp/authentik-api-check.headers || true
echo "authentik-api-not-ready attempt=$attempt"
sleep 10
done
exit 1
'
echo "== auth flow check =="
auth_flow=""
for attempt in 1 2 3 4 5 6 7 8 9 10; do
if auth_flow="$(
curl -k -fsS --compressed https://id.nodedc.ru/if/flow/default-authentication-flow/
)"; then
break
fi
echo "auth-flow-not-ready attempt=${attempt}"
sleep 10
done
printf '%s' "$auth_flow" \
| grep -aE 'syncFormFieldsBeforeSubmit|branding_custom_css = ""|Запросить доступ'
echo "== public admin closed check =="
id_admin_status="$(
curl -k -sS -o /dev/null -w '%{http_code}' https://id.nodedc.ru/if/admin/
)"
if [[ "$id_admin_status" != "404" ]]; then
echo "public id admin is not closed: status=${id_admin_status}"
exit 1
fi
echo "public-id-admin-closed-ok"
echo "== technical admin css check =="
auth_admin_page="$(
curl -k -fsS --compressed -H 'Host: auth-admin.nas.nodedc' http://127.0.0.1:18080/if/admin/
)"
printf '%s' "$auth_admin_page" \
| grep -aE '<style data-id="brand-css"></style>|authentikBrand.branding_custom_css = ""'
auth_admin_flow="$(
curl -k -fsS --compressed -H 'Host: auth-admin.nas.nodedc' http://127.0.0.1:18080/if/flow/default-authentication-flow/
)"
if printf '%s' "$auth_admin_flow" | grep -aq '<style data-id="nodedc-auth-login-css">'; then
echo "admin host still has NODE.DC auth CSS"
exit 1
fi
echo "auth-admin-css-ok"
echo "== launcher bundle check =="
launcher_asset="$(
curl -k -fsS --compressed -H 'Accept: text/html' https://hub.nodedc.ru/ \
| grep -aoE 'index-[A-Za-z0-9_-]+\.js' \
| head -n 1
)"
test -n "$launcher_asset"
if curl -k -fsS --compressed "https://hub.nodedc.ru/assets/${launcher_asset}" \
| grep -aq 'Заявка ожидает подтверждения'; then
echo "old pending gate still present"
exit 1
fi
echo "launcher-pending-gate-ok"
echo "runtime-apply-ok"
+20
View File
@@ -0,0 +1,20 @@
#!/usr/bin/env bash
set -euo pipefail
DOCKER_BIN="${DOCKER_BIN:-/usr/local/bin/docker}"
AUTHENTIK_CONTAINER="${AUTHENTIK_CONTAINER:-nodedc-platform-authentik-server-1}"
"${DOCKER_BIN}" exec "${AUTHENTIK_CONTAINER}" ak shell -c '
from authentik.brands.models import Brand
cleared = []
for brand in Brand.objects.all():
if brand.branding_custom_css:
brand.branding_custom_css = ""
brand.save(update_fields=["branding_custom_css"])
cleared.append(brand.domain)
print("cleared_brand_css=" + str(len(cleared)))
if cleared:
print("domains=" + ",".join(cleared))
'
+58 -2
View File
@@ -30,6 +30,9 @@ rsync -av \
rsync -av \
"${PLATFORM_REPO}/infra/synology/deploy-current.sh" \
"${PLATFORM_REPO}/infra/synology/backup-current.sh" \
"${PLATFORM_REPO}/infra/synology/apply-current-runtime.sh" \
"${PLATFORM_REPO}/infra/synology/verify-current-runtime.sh" \
"${PLATFORM_REPO}/infra/synology/clear-authentik-brand-css.sh" \
"${NAS_ROOT}/platform/"
if [[ "${SYNC_AUTHENTIK_TEMPLATES}" == "1" ]]; then
@@ -160,13 +163,23 @@ cat <<'EOF'
Synced files to NAS mount. Run on Synology to apply runtime changes:
cd /volume1/docker/nodedc-platform/platform
sudo bash apply-current-runtime.sh
If runtime was already applied and only verification is needed:
cd /volume1/docker/nodedc-platform/platform
sudo bash verify-current-runtime.sh
Manual equivalent:
cd /volume1/docker/nodedc-platform/platform
sudo mkdir -p ../launcher/server-storage ../launcher/uploads
sudo chown -R 1000:1000 ../launcher/server-storage ../launcher/uploads
sudo chmod -R u+rwX,g+rwX ../launcher/server-storage ../launcher/uploads
cd /volume1/docker/nodedc-platform/launcher/source
sudo /usr/local/bin/docker build -t nodedc/launcher:local .
sudo /usr/local/bin/docker build --no-cache -t nodedc/launcher:local .
cd /volume1/docker/nodedc-platform/platform
sudo /usr/local/bin/docker compose \
@@ -181,6 +194,12 @@ sudo /usr/local/bin/docker compose \
-f /volume1/docker/nodedc-platform/platform/docker-compose.platform-http.yml \
up -d --force-recreate --no-deps reverse-proxy authentik-server authentik-worker launcher
After Authentik template rollout, clear global Brand custom CSS from the live DB.
NODE.DC login CSS must be template-scoped, not stored in Brand.branding_custom_css:
cd /volume1/docker/nodedc-platform/platform
sudo bash clear-authentik-brand-css.sh
Verify:
sudo /usr/local/bin/docker exec nodedc-platform-launcher-1 sh -lc \
@@ -201,7 +220,44 @@ sudo /usr/local/bin/docker exec nodedc-platform-launcher-1 sh -lc '
'
curl -k -sS --compressed https://id.nodedc.ru/if/flow/default-authentication-flow/ \
| grep -aE 'hub.nodedc.ru|launcher.local|getLauncherBaseUrl|Запросить доступ'
| grep -aE 'syncFormFieldsBeforeSubmit|branding_custom_css = ""|hub.nodedc.ru|launcher.local|getLauncherBaseUrl|Запросить доступ'
id_admin_status="$(
curl -k -sS -o /dev/null -w '%{http_code}' https://id.nodedc.ru/if/admin/
)"
if [[ "$id_admin_status" != "404" ]]; then
echo "public id admin is not closed: status=${id_admin_status}"
exit 1
fi
echo 'public-id-admin-closed-ok'
auth_admin_page="$(
curl -k -fsS --compressed http://auth-admin.nas.nodedc:18080/if/admin/
)"
printf '%s' "$auth_admin_page" \
| grep -aE '<style data-id="brand-css"></style>|authentikBrand.branding_custom_css = ""'
auth_admin_flow="$(
curl -k -fsS --compressed http://auth-admin.nas.nodedc:18080/if/flow/default-authentication-flow/
)"
if printf '%s' "$auth_admin_flow" | grep -aq '<style data-id="nodedc-auth-login-css">'; then
echo 'admin host still has NODE.DC auth CSS'
exit 1
fi
echo 'auth-admin-css-ok'
launcher_asset="$(
curl -k -sS --compressed -H 'Accept: text/html' https://hub.nodedc.ru/ \
| grep -aoE 'index-[A-Za-z0-9_-]+\.js' \
| head -n 1
)"
test -n "$launcher_asset"
if curl -k -sS --compressed "https://hub.nodedc.ru/assets/${launcher_asset}" \
| grep -aq 'Заявка ожидает подтверждения'; then
echo 'old pending gate still present'
exit 1
fi
echo 'launcher-pending-gate-ok'
Optional Tasker apply after TASKER_REPO sync:
+108
View File
@@ -0,0 +1,108 @@
#!/usr/bin/env bash
set -euo pipefail
DOCKER_BIN="${DOCKER_BIN:-/usr/local/bin/docker}"
if [[ ! -x "${DOCKER_BIN}" ]]; then
DOCKER_BIN="$(command -v docker)"
fi
fetch_with_retry() {
local url="$1"
shift
local output=""
local attempt
for attempt in 1 2 3 4 5 6 7 8 9 10; do
if output="$(curl -k -fsS --compressed "$@" "$url")"; then
printf '%s' "$output"
return 0
fi
echo "not-ready url=${url} attempt=${attempt}" >&2
sleep 10
done
return 1
}
echo "== containers =="
"${DOCKER_BIN}" ps --filter 'name=nodedc-platform' --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'
echo "== launcher storage check =="
"${DOCKER_BIN}" exec nodedc-platform-launcher-1 sh -lc \
'touch /app/server/storage/.write-test /app/server/storage/uploads/.write-test && rm /app/server/storage/.write-test /app/server/storage/uploads/.write-test && echo storage-ok'
echo "== launcher -> authentik api check =="
"${DOCKER_BIN}" exec nodedc-platform-launcher-1 sh -lc '
echo "$NODEDC_AUTHENTIK_BASE_URL"
getent hosts nodedc-platform-authentik-server
for attempt in 1 2 3 4 5 6 7 8 9 10; do
if wget -qSO- \
--header "Authorization: Bearer $NODEDC_AUTHENTIK_SERVICE_TOKEN" \
"$NODEDC_AUTHENTIK_BASE_URL/api/v3/core/groups/?search=nodedc_admin" \
>/tmp/authentik-api-check.json \
2>/tmp/authentik-api-check.headers; then
head -n 25 /tmp/authentik-api-check.headers
exit 0
fi
head -n 25 /tmp/authentik-api-check.headers || true
echo "authentik-api-not-ready attempt=$attempt"
sleep 10
done
exit 1
'
echo "== auth flow check =="
auth_flow="$(fetch_with_retry https://id.nodedc.ru/if/flow/default-authentication-flow/)"
printf '%s' "$auth_flow" \
| grep -aE 'syncFormFieldsBeforeSubmit|branding_custom_css = ""|Запросить доступ'
echo "== public admin closed check =="
id_admin_status="$(
curl -k -sS -o /dev/null -w '%{http_code}' https://id.nodedc.ru/if/admin/
)"
if [[ "$id_admin_status" != "404" ]]; then
echo "public id admin is not closed: status=${id_admin_status}"
exit 1
fi
echo "public-id-admin-closed-ok"
echo "== technical admin css check =="
auth_admin_page="$(
fetch_with_retry http://127.0.0.1:18080/if/admin/ -H 'Host: auth-admin.nas.nodedc'
)"
printf '%s' "$auth_admin_page" \
| grep -aE '<style data-id="brand-css"></style>|authentikBrand.branding_custom_css = ""'
auth_admin_flow="$(
fetch_with_retry http://127.0.0.1:18080/if/flow/default-authentication-flow/ -H 'Host: auth-admin.nas.nodedc'
)"
if printf '%s' "$auth_admin_flow" | grep -aq '<style data-id="nodedc-auth-login-css">'; then
echo "admin host still has NODE.DC auth CSS"
exit 1
fi
echo "auth-admin-css-ok"
echo "== technical admin ip fallback check =="
ip_admin_page="$(
fetch_with_retry http://172.22.0.222:18080/if/admin/
)"
printf '%s' "$ip_admin_page" \
| grep -aE '<style data-id="brand-css"></style>|authentikBrand.branding_custom_css = ""'
echo "== launcher bundle check =="
launcher_html="$(fetch_with_retry https://hub.nodedc.ru/ -H 'Accept: text/html')"
launcher_asset="$(
printf '%s' "$launcher_html" \
| grep -aoE 'index-[A-Za-z0-9_-]+\.js' \
| head -n 1
)"
test -n "$launcher_asset"
echo "launcher_asset=${launcher_asset}"
if fetch_with_retry "https://hub.nodedc.ru/assets/${launcher_asset}" \
| grep -aq 'Заявка ожидает подтверждения'; then
echo "old pending gate still present"
exit 1
fi
echo "launcher-pending-gate-ok"
echo "runtime-verify-ok"