From b3c17a9d5349f7ade3f85954b5137c4f17fe31ab Mon Sep 17 00:00:00 2001 From: Mitchell R Date: Mon, 11 May 2026 08:57:55 +0200 Subject: [PATCH] fix(deploy): gate proxied runtime routes --- CLAUDE.md | 2 +- deploy/README.md | 6 ++ deploy/angie/betterframe.conf | 24 +++++- deploy/angie/betterframe.docker.conf | 114 +++++++++++++++++++++++++++ deploy/docker/docker-compose.yml | 4 +- 5 files changed, 145 insertions(+), 5 deletions(-) create mode 100644 deploy/angie/betterframe.docker.conf diff --git a/CLAUDE.md b/CLAUDE.md index e0663da..3f4882a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -235,7 +235,7 @@ Everything else is a shared module (plain TS, no BSB lifecycle). 5. **Rust kiosk polish** — multi-camera compositor, H264/H265 auto-detect, web cells via WebKit 6. **Node-RED bridge** — outbound HTTP forwarder + inbound callbacks 7. **Display power relay** — kiosk handles CEC first, then monitor DPMS fallback (`wlr-randr`, then `xset`). Server/admin should keep using generic wake/standby commands, not CEC-only naming. -8. **Angie config** + systemd units + Dockerfile +8. **Angie config** + systemd units + Dockerfile — ✅ baseline native + Docker deployment files exist; Angie now uses auth_request for admin/kiosk-gated Node-RED routes, and Docker uses container upstreams. 9. **Auth-check endpoints** — ✅ admin session/API-key, kiosk key, and API-key checks added for proxy `auth_request` ## conventions (additions discovered while building) diff --git a/deploy/README.md b/deploy/README.md index a420061..338cd3c 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -18,6 +18,7 @@ sudo chown betterframe:betterframe /var/lib/betterframe /var/log/betterframe sudo git clone https://github.com/BetterCorp/BetterFrame.git /opt/betterframe cd /opt/betterframe sudo -u betterframe npm install +sudo -u betterframe npm run build sudo cp sec-config.yaml /opt/betterframe/server/sec-config.yaml # Install systemd unit @@ -59,6 +60,9 @@ sudo cp deploy/angie/betterframe.conf /etc/angie/conf.d/ sudo systemctl reload angie ``` +The Angie config gates `/nrdp/*` with the admin session/API-key auth-check +endpoint and `/in/kiosk/*` with the kiosk Bearer-key auth-check endpoint. + ## Docker ```bash @@ -66,6 +70,8 @@ docker compose -f deploy/docker/docker-compose.yml up -d ``` Kiosk still runs natively on the Pi (needs Wayland/HDMI), not in Docker. +The Compose stack uses `deploy/angie/betterframe.docker.conf` because service +names, not `127.0.0.1`, are the correct upstreams inside the Docker network. Access: `http:///setup` for first-run. diff --git a/deploy/angie/betterframe.conf b/deploy/angie/betterframe.conf index b0b8d14..a26c29c 100644 --- a/deploy/angie/betterframe.conf +++ b/deploy/angie/betterframe.conf @@ -78,7 +78,7 @@ server { # ---- Node-RED dashboard (admin-only) ---- location /nrdp/ { - # auth_request /api/admin/_check; # enable when auth-check endpoint ready + auth_request /api/admin/_check; rewrite ^/nrdp/(.*) /$1 break; proxy_pass http://betterframe_nodered; proxy_set_header Host $host; @@ -96,11 +96,31 @@ server { # ---- Node-RED HTTP-in (kiosk-gated) ---- location /in/kiosk/ { - # Bearer kiosk-key validated by Node-RED flow + auth_request /api/kiosk/_check; rewrite ^/in/kiosk/(.*) /kiosk/$1 break; proxy_pass http://betterframe_nodered; } + # ---- Proxy auth subrequests ---- + location = /api/admin/_check { + internal; + proxy_pass http://betterframe_admin; + proxy_pass_request_body off; + proxy_set_header Content-Length ""; + proxy_set_header Cookie $http_cookie; + proxy_set_header Authorization $http_authorization; + proxy_set_header X-Real-IP $remote_addr; + } + + location = /api/kiosk/_check { + internal; + proxy_pass http://betterframe_api; + proxy_pass_request_body off; + proxy_set_header Content-Length ""; + proxy_set_header Authorization $http_authorization; + proxy_set_header X-Real-IP $remote_addr; + } + # ---- Health/readiness/version (public) ---- location ~ ^/(healthz|readyz|version)$ { proxy_pass http://betterframe_admin; diff --git a/deploy/angie/betterframe.docker.conf b/deploy/angie/betterframe.docker.conf new file mode 100644 index 0000000..886ee9b --- /dev/null +++ b/deploy/angie/betterframe.docker.conf @@ -0,0 +1,114 @@ +# BetterFrame Docker nginx config. +# +# Same routes as betterframe.conf, but upstreams use compose service names +# instead of localhost. + +upstream betterframe_admin { server server:18080; keepalive 16; } +upstream betterframe_api { server server:18081; keepalive 16; } +upstream betterframe_ws { server server:18082; } +upstream betterframe_nodered { server nodered:1880; keepalive 8; } + +limit_req_zone $binary_remote_addr zone=bf_public:10m rate=30r/s; + +server { + listen 80; + server_name _; + + client_max_body_size 16M; + + location /admin/ { + proxy_pass http://betterframe_admin; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_http_version 1.1; + proxy_set_header Connection ""; + } + + location = /admin { return 301 /admin/; } + location /setup { proxy_pass http://betterframe_admin; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } + location /auth/ { proxy_pass http://betterframe_admin; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } + location /static/ { proxy_pass http://betterframe_admin; } + + location /api/kiosk/ { + proxy_pass http://betterframe_api; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_http_version 1.1; + proxy_set_header Connection ""; + } + + location /api/pair/ { + limit_req zone=bf_public burst=10 nodelay; + proxy_pass http://betterframe_api; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + } + + location /api/admin/ { + proxy_pass http://betterframe_admin; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + } + + location /ws/kiosk { + proxy_pass http://betterframe_ws; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_read_timeout 86400s; + proxy_send_timeout 86400s; + } + + location /nrdp/ { + auth_request /api/admin/_check; + rewrite ^/nrdp/(.*) /$1 break; + proxy_pass http://betterframe_nodered; + proxy_set_header Host $host; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + } + + location /in/public/ { + limit_req zone=bf_public burst=20 nodelay; + rewrite ^/in/public/(.*) /public/$1 break; + proxy_pass http://betterframe_nodered; + } + + location /in/kiosk/ { + auth_request /api/kiosk/_check; + rewrite ^/in/kiosk/(.*) /kiosk/$1 break; + proxy_pass http://betterframe_nodered; + } + + location = /api/admin/_check { + internal; + proxy_pass http://betterframe_admin; + proxy_pass_request_body off; + proxy_set_header Content-Length ""; + proxy_set_header Cookie $http_cookie; + proxy_set_header Authorization $http_authorization; + proxy_set_header X-Real-IP $remote_addr; + } + + location = /api/kiosk/_check { + internal; + proxy_pass http://betterframe_api; + proxy_pass_request_body off; + proxy_set_header Content-Length ""; + proxy_set_header Authorization $http_authorization; + proxy_set_header X-Real-IP $remote_addr; + } + + location ~ ^/(healthz|readyz|version)$ { + proxy_pass http://betterframe_admin; + } + + location = / { + proxy_pass http://betterframe_admin; + } +} diff --git a/deploy/docker/docker-compose.yml b/deploy/docker/docker-compose.yml index 28bc226..32e47ac 100644 --- a/deploy/docker/docker-compose.yml +++ b/deploy/docker/docker-compose.yml @@ -21,7 +21,7 @@ services: restart: unless-stopped volumes: - betterframe-data:/var/lib/betterframe - - ../../sec-config.yaml:/app/sec-config.yaml:ro + - ../../sec-config.yaml:/app/server/sec-config.yaml:ro expose: - "18080" - "18081" @@ -39,7 +39,7 @@ services: ports: - "80:80" volumes: - - ../angie/betterframe.conf:/etc/nginx/conf.d/default.conf:ro + - ../angie/betterframe.docker.conf:/etc/nginx/conf.d/default.conf:ro networks: - betterframe