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:
@@ -32,7 +32,7 @@
|
||||
| `/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/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 |
|
||||
| `/etc/conf.d/grafana` | Grafana runtime overrides (bind address, provisioning path) |
|
||||
|
||||
@@ -80,7 +80,7 @@ done
|
||||
|--------|---------|----------|
|
||||
| CPU | >85% for 10m | >95% for 5m |
|
||||
| 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 |
|
||||
| HTTP probe | >5s for 5m | down >3m |
|
||||
| SSL cert | <14 days | <7 days |
|
||||
|
||||
@@ -1,52 +1,35 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Grafana auth_request endpoint — nginx calls this to validate PHP session
|
||||
* before proxying to cast04:3000 (Grafana).
|
||||
* Grafana / Alertmanager / broadcast auth_request endpoint.
|
||||
*
|
||||
* If session is valid: HTTP 200 + X-WEBAUTH-USER header (username)
|
||||
* If not authenticated: HTTP 401 (nginx redirects to /app/androidcast_project/login)
|
||||
* 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 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
|
||||
if (!in_array($_SERVER['REMOTE_ADDR'] ?? '', ['127.0.0.1', '::1'], true)) {
|
||||
http_response_code(403);
|
||||
exit;
|
||||
}
|
||||
require_once dirname(__DIR__, 4) . '/platform/shared_session.php';
|
||||
|
||||
// Session handling — must match the main app's session settings
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_set_cookie_params([
|
||||
'path' => '/app/androidcast_project/',
|
||||
'secure' => isset($_SERVER['HTTPS']),
|
||||
'httponly' => true,
|
||||
'samesite' => 'Lax',
|
||||
]);
|
||||
session_start();
|
||||
}
|
||||
platform_start_session('ac_crash_sess', '/app/androidcast_project');
|
||||
|
||||
// Check if user is logged in — Auth.php stores array in $_SESSION['user']
|
||||
// with at least { 'id' => int, 'username' => string, ... }
|
||||
$user = $_SESSION['user'] ?? null;
|
||||
|
||||
if (empty($user) || empty($user['id'])) {
|
||||
if (empty($user['id'])) {
|
||||
http_response_code(401);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Require full login (not just 2FA pending)
|
||||
if (isset($user['pending_2fa']) && $user['pending_2fa']) {
|
||||
if (!empty($user['pending_2fa'])) {
|
||||
http_response_code(401);
|
||||
exit;
|
||||
}
|
||||
|
||||
$username = (string) ($user['username'] ?? $user['email'] ?? 'user_' . $user['id']);
|
||||
|
||||
// Sanitize — Grafana username must be a valid identifier
|
||||
$grafana_user = preg_replace('/[^a-zA-Z0-9._@-]/', '_', $username);
|
||||
$grafanaUser = preg_replace('/[^a-zA-Z0-9._@-]/', '_', $username);
|
||||
|
||||
http_response_code(200);
|
||||
header('X-WEBAUTH-USER: ' . $grafana_user);
|
||||
header('X-WEBAUTH-USER: ' . $grafanaUser);
|
||||
header('Content-Type: text/plain');
|
||||
echo 'ok';
|
||||
|
||||
@@ -53,6 +53,7 @@ enabled = true
|
||||
header_name = X-WEBAUTH-USER
|
||||
header_property = username
|
||||
auto_sign_up = true
|
||||
enable_login_token = true
|
||||
# Trust X-WEBAUTH-USER only from cluster BE nginx (cast01–03):
|
||||
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):
|
||||
@@ -66,7 +67,13 @@ enabled = false
|
||||
enabled = true
|
||||
|
||||
[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]
|
||||
mode = file
|
||||
|
||||
@@ -61,24 +61,24 @@ groups:
|
||||
- alert: LowDiskWarning
|
||||
expr: >
|
||||
(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
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
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
|
||||
expr: >
|
||||
(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
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
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 ────────────────────────────────────────────────────────────────
|
||||
- alert: HighLoadWarning
|
||||
|
||||
@@ -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';
|
||||
@@ -193,6 +193,14 @@ server {
|
||||
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/ {
|
||||
include fastcgi_params;
|
||||
fastcgi_pass 127.0.0.1:9000;
|
||||
@@ -222,16 +230,28 @@ server {
|
||||
location = /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/ {
|
||||
auth_request /app/androidcast_project/api/grafana-auth-check.php;
|
||||
auth_request_set $grafana_user $upstream_http_x_webauth_user;
|
||||
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 X-Real-IP $remote_addr;
|
||||
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 Authorization "";
|
||||
proxy_http_version 1.1;
|
||||
proxy_read_timeout 300;
|
||||
proxy_send_timeout 300;
|
||||
|
||||
Reference in New Issue
Block a user