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/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 |
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
@@ -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 (cast01–03):
|
# Trust X-WEBAUTH-USER only from cluster BE nginx (cast01–03):
|
||||||
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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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/;
|
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;
|
||||||
|
|||||||
Reference in New Issue
Block a user