1
0
mirror of git://f0xx.org/ac/ac-deploy synced 2026-08-12 18:12:19 +03:00

Monitor: Grafana auth proxy session fix and nginx login bypass.

Use ac_crash_sess shared session, allow password reset without auth_request,
update grafana.ini template and alert rules.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Anton Afanasyeu
2026-08-07 22:16:03 +02:00
parent 9307e3d47c
commit 12545813ab
6 changed files with 84 additions and 39 deletions

View File

@@ -32,7 +32,7 @@
| `/etc/prometheus/rules/node_alerts.yml` | HW/OS alert rules (10 rules) | | `/etc/prometheus/rules/node_alerts.yml` | HW/OS alert rules (10 rules) |
| `/etc/prometheus/rules/service_alerts.yml` | Service/HTTP/DB alert rules (10 rules) | | `/etc/prometheus/rules/service_alerts.yml` | Service/HTTP/DB alert rules (10 rules) |
| `/etc/alertmanager/alertmanager.yml` | Alert routing (email + Telegram stub) | | `/etc/alertmanager/alertmanager.yml` | Alert routing (email + Telegram stub) |
| `/etc/grafana/grafana.ini` | Grafana config (subpath, auth proxy) | | `/etc/grafana.ini` | Grafana config (subpath, auth proxy) — Alpine OpenRC path |
| `/var/lib/grafana/provisioning/` | Auto-provisioned datasource + dashboards dir | | `/var/lib/grafana/provisioning/` | Auto-provisioned datasource + dashboards dir |
| `/etc/conf.d/grafana` | Grafana runtime overrides (bind address, provisioning path) | | `/etc/conf.d/grafana` | Grafana runtime overrides (bind address, provisioning path) |
@@ -80,7 +80,7 @@ done
|--------|---------|----------| |--------|---------|----------|
| CPU | >85% for 10m | >95% for 5m | | CPU | >85% for 10m | >95% for 5m |
| RAM free | <15% for 5m | <5% for 2m | | RAM free | <15% for 5m | <5% for 2m |
| Disk free | <20% for 15m | <10% for 10m | | Disk free | <7% for 15m | <5% for 10m |
| Load (per vCPU) | >1.5× for 5m | >3.0× for 5m | | Load (per vCPU) | >1.5× for 5m | >3.0× for 5m |
| HTTP probe | >5s for 5m | down >3m | | HTTP probe | >5s for 5m | down >3m |
| SSL cert | <14 days | <7 days | | SSL cert | <14 days | <7 days |

View File

@@ -1,52 +1,35 @@
<?php <?php
declare(strict_types=1);
/** /**
* Grafana auth_request endpoint — nginx calls this to validate PHP session * Grafana / Alertmanager / broadcast auth_request endpoint.
* before proxying to cast04:3000 (Grafana).
* *
* If session is valid: HTTP 200 + X-WEBAUTH-USER header (username) * nginx `internal;` blocks direct browser access. Returns 200 + X-WEBAUTH-USER when
* If not authenticated: HTTP 401 (nginx redirects to /app/androidcast_project/login) * the shared Android Cast PHP session (ac_crash_sess) is valid, 401 otherwise.
* *
* Deploy to: /var/www/localhost/htdocs/apps/app/androidcast_project/api/grafana-auth-check.php * Deploy: /var/www/ac/composed/backend/public/api/grafana-auth-check.php
*/ */
// Only allow calls from nginx itself (127.0.0.1) — block direct browser access require_once dirname(__DIR__, 4) . '/platform/shared_session.php';
if (!in_array($_SERVER['REMOTE_ADDR'] ?? '', ['127.0.0.1', '::1'], true)) {
http_response_code(403);
exit;
}
// Session handling — must match the main app's session settings platform_start_session('ac_crash_sess', '/app/androidcast_project');
if (session_status() === PHP_SESSION_NONE) {
session_set_cookie_params([
'path' => '/app/androidcast_project/',
'secure' => isset($_SERVER['HTTPS']),
'httponly' => true,
'samesite' => 'Lax',
]);
session_start();
}
// Check if user is logged in — Auth.php stores array in $_SESSION['user']
// with at least { 'id' => int, 'username' => string, ... }
$user = $_SESSION['user'] ?? null; $user = $_SESSION['user'] ?? null;
if (empty($user) || empty($user['id'])) { if (empty($user['id'])) {
http_response_code(401); http_response_code(401);
exit; exit;
} }
// Require full login (not just 2FA pending) if (!empty($user['pending_2fa'])) {
if (isset($user['pending_2fa']) && $user['pending_2fa']) {
http_response_code(401); http_response_code(401);
exit; exit;
} }
$username = (string) ($user['username'] ?? $user['email'] ?? 'user_' . $user['id']); $username = (string) ($user['username'] ?? $user['email'] ?? 'user_' . $user['id']);
$grafanaUser = preg_replace('/[^a-zA-Z0-9._@-]/', '_', $username);
// Sanitize — Grafana username must be a valid identifier
$grafana_user = preg_replace('/[^a-zA-Z0-9._@-]/', '_', $username);
http_response_code(200); http_response_code(200);
header('X-WEBAUTH-USER: ' . $grafana_user); header('X-WEBAUTH-USER: ' . $grafanaUser);
header('Content-Type: text/plain'); header('Content-Type: text/plain');
echo 'ok'; echo 'ok';

View File

@@ -53,6 +53,7 @@ enabled = true
header_name = X-WEBAUTH-USER header_name = X-WEBAUTH-USER
header_property = username header_property = username
auto_sign_up = true auto_sign_up = true
enable_login_token = true
# Trust X-WEBAUTH-USER only from cluster BE nginx (cast0103): # Trust X-WEBAUTH-USER only from cluster BE nginx (cast0103):
whitelist = 10.7.16.236,10.7.16.237,10.7.16.238 whitelist = 10.7.16.236,10.7.16.237,10.7.16.238
# Sync roles from header (optional, set X-WEBAUTH-ROLE in nginx if needed): # Sync roles from header (optional, set X-WEBAUTH-ROLE in nginx if needed):
@@ -66,7 +67,13 @@ enabled = false
enabled = true enabled = true
[smtp] [smtp]
enabled = false # Grafana own SMTP not needed; alertmanager handles mail enabled = true
host = smtp.gmail.com:587
user = @MSMTP_USER@
password = @MSMTP_PASS@
from_address = @MSMTP_FROM@
from_name = Android Cast Monitoring
startTLS_policy = MandatoryStartTLS
[log] [log]
mode = file mode = file

View File

@@ -61,24 +61,24 @@ groups:
- alert: LowDiskWarning - alert: LowDiskWarning
expr: > expr: >
(node_filesystem_avail_bytes{fstype!~"tmpfs|devtmpfs|overlay"} / (node_filesystem_avail_bytes{fstype!~"tmpfs|devtmpfs|overlay"} /
node_filesystem_size_bytes{fstype!~"tmpfs|devtmpfs|overlay"}) * 100 < 20 node_filesystem_size_bytes{fstype!~"tmpfs|devtmpfs|overlay"}) * 100 < 7
for: 15m for: 15m
labels: labels:
severity: warning severity: warning
annotations: annotations:
summary: "Low disk on {{ $labels.instance }} mountpoint {{ $labels.mountpoint }}" summary: "Low disk on {{ $labels.instance }} mountpoint {{ $labels.mountpoint }}"
description: "Free disk is {{ $value | printf \"%.1f\" }}% on {{ $labels.instance }}:{{ $labels.mountpoint }} (threshold: 20%)." description: "Free disk is {{ $value | printf \"%.1f\" }}% on {{ $labels.instance }}:{{ $labels.mountpoint }} (threshold: 7%)."
- alert: LowDiskCritical - alert: LowDiskCritical
expr: > expr: >
(node_filesystem_avail_bytes{fstype!~"tmpfs|devtmpfs|overlay"} / (node_filesystem_avail_bytes{fstype!~"tmpfs|devtmpfs|overlay"} /
node_filesystem_size_bytes{fstype!~"tmpfs|devtmpfs|overlay"}) * 100 < 10 node_filesystem_size_bytes{fstype!~"tmpfs|devtmpfs|overlay"}) * 100 < 5
for: 10m for: 10m
labels: labels:
severity: critical severity: critical
annotations: annotations:
summary: "Critical disk on {{ $labels.instance }}:{{ $labels.mountpoint }}" summary: "Critical disk on {{ $labels.instance }}:{{ $labels.mountpoint }}"
description: "Free disk is {{ $value | printf \"%.1f\" }}% (threshold: 10%) on {{ $labels.instance }}:{{ $labels.mountpoint }}." description: "Free disk is {{ $value | printf \"%.1f\" }}% (threshold: 5%) on {{ $labels.instance }}:{{ $labels.mountpoint }}."
# ── load ──────────────────────────────────────────────────────────────── # ── load ────────────────────────────────────────────────────────────────
- alert: HighLoadWarning - alert: HighLoadWarning

View File

@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
/**
* Grafana / Alertmanager / broadcast auth_request endpoint.
*
* nginx `internal;` blocks direct browser access. Returns 200 + X-WEBAUTH-USER when
* the shared Android Cast PHP session (ac_crash_sess) is valid, 401 otherwise.
*
* Deploy: /var/www/ac/composed/backend/public/api/grafana-auth-check.php
*/
require_once dirname(__DIR__, 4) . '/platform/shared_session.php';
platform_start_session('ac_crash_sess', '/app/androidcast_project');
$user = $_SESSION['user'] ?? null;
if (empty($user['id'])) {
http_response_code(401);
exit;
}
if (!empty($user['pending_2fa'])) {
http_response_code(401);
exit;
}
$username = (string) ($user['username'] ?? $user['email'] ?? 'user_' . $user['id']);
$grafanaUser = preg_replace('/[^a-zA-Z0-9._@-]/', '_', $username);
http_response_code(200);
header('X-WEBAUTH-USER: ' . $grafanaUser);
header('Content-Type: text/plain');
echo 'ok';

View File

@@ -193,6 +193,14 @@ server {
alias /var/www/ac/workspace/ac-be-builder/public/assets/; alias /var/www/ac/workspace/ac-be-builder/public/assets/;
} }
location ~ ^/app/androidcast_project/build/api/(.+\.php)$ {
include fastcgi_params;
fastcgi_pass 127.0.0.1:9000;
fastcgi_param SCRIPT_FILENAME /var/www/ac/workspace/ac-be-builder/public/api/$1;
fastcgi_param SCRIPT_NAME /app/androidcast_project/build/api/$1;
fastcgi_param REQUEST_URI $request_uri;
}
location ^~ /app/androidcast_project/build/ { location ^~ /app/androidcast_project/build/ {
include fastcgi_params; include fastcgi_params;
fastcgi_pass 127.0.0.1:9000; fastcgi_pass 127.0.0.1:9000;
@@ -222,16 +230,28 @@ server {
location = /app/androidcast_project/monitor { location = /app/androidcast_project/monitor {
return 301 $public_scheme://$http_host/app/androidcast_project/monitor/; return 301 $public_scheme://$http_host/app/androidcast_project/monitor/;
} }
# Login + password reset must work without PHP session (Grafana local auth).
location ~ ^/app/androidcast_project/monitor/(login|user/password|api/user/password) {
proxy_pass http://10.7.16.239:3000;
proxy_redirect off;
proxy_set_header Host $http_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 $public_scheme;
proxy_http_version 1.1;
}
location /app/androidcast_project/monitor/ { location /app/androidcast_project/monitor/ {
auth_request /app/androidcast_project/api/grafana-auth-check.php; auth_request /app/androidcast_project/api/grafana-auth-check.php;
auth_request_set $grafana_user $upstream_http_x_webauth_user; auth_request_set $grafana_user $upstream_http_x_webauth_user;
error_page 401 = @monitor_login_redirect; error_page 401 = @monitor_login_redirect;
proxy_pass http://10.7.16.239:3000/; proxy_pass http://10.7.16.239:3000;
proxy_redirect off;
proxy_set_header Host $http_host; proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Proto $public_scheme;
proxy_set_header X-WEBAUTH-USER $grafana_user; proxy_set_header X-WEBAUTH-USER $grafana_user;
proxy_set_header Authorization "";
proxy_http_version 1.1; proxy_http_version 1.1;
proxy_read_timeout 300; proxy_read_timeout 300;
proxy_send_timeout 300; proxy_send_timeout 300;