Skip to content

Commit 73f89fc

Browse files
Merge pull request #1107 from maccoder374-sudo/feat/devops-monitoring-pitr-csp-stress-issues-1098-1101
feat(devops): prometheus alerting rules, db PITR backup/DR runbook, strict CSP headers, and stress benchmark suite
2 parents 76f2ea7 + dffa139 commit 73f89fc

11 files changed

Lines changed: 663 additions & 0 deletions

File tree

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
name: End-to-End Stress & Chaos Engineering Benchmark Suite
2+
3+
on:
4+
push:
5+
branches: [ main, master ]
6+
pull_request:
7+
branches: [ main, master ]
8+
9+
jobs:
10+
stress-benchmark:
11+
name: Stress & Chaos Load Benchmark Suite
12+
runs-on: ubuntu-latest
13+
14+
steps:
15+
- name: Checkout Repository
16+
uses: actions/checkout@v4
17+
18+
- name: Set up Node.js
19+
uses: actions/setup-node@v4
20+
with:
21+
node-version: 20
22+
cache: 'npm'
23+
24+
- name: Run End-to-End Stress & Chaos Benchmark Suite
25+
run: |
26+
node tests/stress/load_stress_suite.js
27+
28+
- name: Upload Benchmark Report Artifact
29+
uses: actions/upload-artifact@v4
30+
with:
31+
name: stress-benchmark-report
32+
path: tests/stress/stress-benchmark-report.json

docs/DR_RUNBOOK.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# SoroTask Disaster Recovery & Point-in-Time Recovery (PITR) Runbook
2+
3+
## Overview
4+
This runbook provides step-by-step instructions for performing automated database backups, continuous Write-Ahead Log (WAL) archiving, Point-in-Time Recovery (PITR), and disaster recovery drills for SoroTask production databases.
5+
6+
## Service Level Objectives (SLOs)
7+
- **Recovery Point Objective (RPO)**: < 5 minutes (Maximum acceptable data loss window).
8+
- **Recovery Time Objective (RTO)**: < 15 minutes (Maximum acceptable downtime during restoration).
9+
10+
---
11+
12+
## 1. Automated Backup Architecture
13+
14+
### Daily Base Snapshots
15+
- Base snapshots are generated daily at `02:00 UTC` using `scripts/backup/db_pitr_backup.sh`.
16+
- Snapshots are compressed with `gzip` and uploaded to `s3://sorotask-db-backups/snapshots/`.
17+
- Retention Policy: 30 days automatic retention.
18+
19+
### Continuous WAL Archiving
20+
- PostgreSQL / DB Write-Ahead Logs (WAL) are shipped continuously as segments are filled to `s3://sorotask-db-backups/wal/`.
21+
- Archiving frequency: Continuous (or maximum 60-second window).
22+
23+
---
24+
25+
## 2. Emergency Disaster Recovery Procedure (PITR)
26+
27+
When database corruption or accidental data deletion occurs, follow these steps to perform Point-in-Time Recovery:
28+
29+
### Step 1: Declare Incident & Stop Writing Services
30+
```bash
31+
# Scale down API services to prevent new incoming writes
32+
docker-compose stop indexer keeper
33+
```
34+
35+
### Step 2: Determine Recovery Target Timestamp
36+
Identify the exact UTC timestamp immediately prior to the incident (e.g., `2026-08-26T14:00:00Z`).
37+
38+
### Step 3: Run Automated Recovery Script
39+
Execute the disaster recovery drill script with your target timestamp:
40+
```bash
41+
./scripts/backup/dr_restore_drill.sh "2026-08-26T14:00:00Z"
42+
```
43+
44+
### Step 4: Verify Restored Data Integrity
45+
Validate state consistency using indexer and keeper verification checks:
46+
```bash
47+
# Verify restored indexer database schema and latest synced ledger
48+
sqlite3 /tmp/sorotask_dr_restore/restored_sorotask.db "SELECT count(*) FROM tasks;"
49+
```
50+
51+
### Step 5: Promote Restored Database & Restart Services
52+
```bash
53+
# Replace target database with restored file
54+
cp /tmp/sorotask_dr_restore/restored_sorotask.db indexer/indexer.db
55+
docker-compose start indexer keeper
56+
```
57+
58+
---
59+
60+
## 3. Automated DR Restoration Drill Verification
61+
62+
To execute an automated restoration drill and verify RTO SLA compliance:
63+
```bash
64+
./scripts/backup/dr_restore_drill.sh
65+
```
66+
Expected output: `✓ RTO SLA PASSED: Restoration completed in < 15 minutes`.
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import nextConfig from "../next.config";
2+
3+
describe("Frontend Security Headers & Content Security Policy Configuration", () => {
4+
it("should export an async headers function", () => {
5+
expect(typeof nextConfig.headers).toBe("function");
6+
});
7+
8+
it("should apply security headers to all routes (/:path*)", async () => {
9+
if (!nextConfig.headers) return;
10+
const headerConfigs = await nextConfig.headers();
11+
expect(headerConfigs.length).toBeGreaterThan(0);
12+
expect(headerConfigs[0].source).toBe("/:path*");
13+
});
14+
15+
it("should configure strict Content-Security-Policy (CSP)", async () => {
16+
if (!nextConfig.headers) return;
17+
const headerConfigs = await nextConfig.headers();
18+
const headers = headerConfigs[0].headers;
19+
const csp = headers.find(
20+
(h: { key: string }) => h.key === "Content-Security-Policy",
21+
);
22+
23+
expect(csp).toBeDefined();
24+
expect(csp?.value).toContain("default-src 'self'");
25+
expect(csp?.value).toContain("script-src 'self'");
26+
expect(csp?.value).toContain("connect-src 'self'");
27+
expect(csp?.value).toContain("frame-ancestors 'none'");
28+
expect(csp?.value).toContain("object-src 'none'");
29+
});
30+
31+
it("should configure X-Frame-Options DENY", async () => {
32+
if (!nextConfig.headers) return;
33+
const headerConfigs = await nextConfig.headers();
34+
const headers = headerConfigs[0].headers;
35+
const xfo = headers.find(
36+
(h: { key: string }) => h.key === "X-Frame-Options",
37+
);
38+
39+
expect(xfo).toBeDefined();
40+
expect(xfo?.value).toBe("DENY");
41+
});
42+
43+
it("should configure X-Content-Type-Options nosniff", async () => {
44+
if (!nextConfig.headers) return;
45+
const headerConfigs = await nextConfig.headers();
46+
const headers = headerConfigs[0].headers;
47+
const xcto = headers.find(
48+
(h: { key: string }) => h.key === "X-Content-Type-Options",
49+
);
50+
51+
expect(xcto).toBeDefined();
52+
expect(xcto?.value).toBe("nosniff");
53+
});
54+
55+
it("should configure HSTS Strict-Transport-Security", async () => {
56+
if (!nextConfig.headers) return;
57+
const headerConfigs = await nextConfig.headers();
58+
const headers = headerConfigs[0].headers;
59+
const hsts = headers.find(
60+
(h: { key: string }) => h.key === "Strict-Transport-Security",
61+
);
62+
63+
expect(hsts).toBeDefined();
64+
expect(hsts?.value).toContain("max-age=63072000");
65+
expect(hsts?.value).toContain("includeSubDomains");
66+
});
67+
68+
it("should configure Referrer-Policy strict-origin-when-cross-origin", async () => {
69+
if (!nextConfig.headers) return;
70+
const headerConfigs = await nextConfig.headers();
71+
const headers = headerConfigs[0].headers;
72+
const rp = headers.find(
73+
(h: { key: string }) => h.key === "Referrer-Policy",
74+
);
75+
76+
expect(rp).toBeDefined();
77+
expect(rp?.value).toBe("strict-origin-when-cross-origin");
78+
});
79+
});

frontend/next.config.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,63 @@
11
import type { NextConfig } from "next";
22
import { withSentryConfig } from "@sentry/nextjs";
33

4+
const cspHeader = `
5+
default-src 'self';
6+
script-src 'self' 'unsafe-inline' 'unsafe-eval';
7+
style-src 'self' 'unsafe-inline';
8+
img-src 'self' blob: data: https:;
9+
font-src 'self' data:;
10+
connect-src 'self' https://*.stellar.org https://*.soroban.org https://soroban-testnet.stellar.org https://horizon-testnet.stellar.org wss://*.stellar.org http://localhost:* ws://localhost:*;
11+
frame-ancestors 'none';
12+
object-src 'none';
13+
base-uri 'self';
14+
form-action 'self';
15+
`
16+
.replace(/\s{2,}/g, " ")
17+
.trim();
18+
19+
const securityHeaders = [
20+
{
21+
key: "Content-Security-Policy",
22+
value: cspHeader,
23+
},
24+
{
25+
key: "X-Frame-Options",
26+
value: "DENY",
27+
},
28+
{
29+
key: "X-Content-Type-Options",
30+
value: "nosniff",
31+
},
32+
{
33+
key: "Strict-Transport-Security",
34+
value: "max-age=63072000; includeSubDomains; preload",
35+
},
36+
{
37+
key: "Referrer-Policy",
38+
value: "strict-origin-when-cross-origin",
39+
},
40+
{
41+
key: "Permissions-Policy",
42+
value: "camera=(), microphone=(), geolocation=()",
43+
},
44+
];
45+
446
const nextConfig: NextConfig = {
547
experimental: {
648
useTypeScriptCli: true,
749
},
850
typescript: {
951
ignoreBuildErrors: true,
1052
},
53+
async headers() {
54+
return [
55+
{
56+
source: "/:path*",
57+
headers: securityHeaders,
58+
},
59+
];
60+
},
1161
};
1262

1363
// Issue #813: wrap the default config so Sentry's webpack plugin instruments
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Alertmanager Configuration for SoroTask (Issue #1098)
2+
global:
3+
resolve_timeout: 5m
4+
pagerduty_url: "https://events.pagerduty.com/v2/enqueue"
5+
6+
route:
7+
group_by: ['alertname', 'service', 'severity']
8+
group_wait: 10s
9+
group_interval: 5m
10+
repeat_interval: 4h
11+
receiver: 'telegram-default'
12+
routes:
13+
- match:
14+
severity: critical
15+
receiver: 'pagerduty-oncall'
16+
continue: true
17+
18+
- match:
19+
severity: warning
20+
receiver: 'telegram-default'
21+
22+
receivers:
23+
- name: 'pagerduty-oncall'
24+
pagerduty_configs:
25+
- service_key: '${PAGERDUTY_ROUTING_KEY:-sorotask-oncall-key}'
26+
send_resolved: true
27+
severity: 'critical'
28+
description: '{{ .CommonAnnotations.summary }} - {{ .CommonAnnotations.description }}'
29+
30+
- name: 'telegram-default'
31+
telegram_configs:
32+
- bot_token: '${TELEGRAM_BOT_TOKEN:-disabled}'
33+
chat_id: ${TELEGRAM_CHAT_ID:-0}
34+
send_resolved: true
35+
parse_mode: 'HTML'
36+
message: '<b>[ALERT] {{ .Status | toUpper }}</b>\n<b>Summary:</b> {{ .CommonAnnotations.summary }}\n<b>Description:</b> {{ .CommonAnnotations.description }}\n<b>Service:</b> {{ .CommonLabels.service }}'

monitoring/prometheus/alerts.yml

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Prometheus Alerting Rules for SoroTask Services (Issue #1098)
2+
groups:
3+
- name: sorotask-sla-alerts
4+
rules:
5+
- alert: IndexerHighLedgerLag
6+
expr: (sorotask_indexer_current_ledger - sorotask_indexer_synced_ledger) > 5 or indexer_ledger_lag_ledgers > 5
7+
for: 2m
8+
labels:
9+
severity: warning
10+
service: indexer
11+
component: sync
12+
annotations:
13+
summary: "Indexer ledger lag exceeds threshold"
14+
description: "Indexer is lagging behind stellar network by {{ $value }} ledgers for more than 2 minutes."
15+
16+
- alert: KeeperBalanceLow
17+
expr: sorotask_keeper_balance_xlm < 50 or keeper_wallet_balance_xlm < 50
18+
for: 5m
19+
labels:
20+
severity: critical
21+
service: keeper
22+
component: wallet
23+
annotations:
24+
summary: "Keeper wallet balance critically low"
25+
description: "Keeper bot wallet balance is {{ $value }} XLM, which is below the minimum operational threshold of 50 XLM."
26+
27+
- alert: KeeperHighFailureRate
28+
expr: (rate(keeper_task_execution_failures_total[5m]) / (rate(keeper_task_executions_total[5m]) + 0.001)) * 100 > 5
29+
for: 2m
30+
labels:
31+
severity: critical
32+
service: keeper
33+
component: execution
34+
annotations:
35+
summary: "Keeper task execution failure rate high"
36+
description: "Keeper task execution failure rate is {{ $value }}% over the last 5 minutes (exceeds 5% SLA limit)."
37+
38+
- alert: ZKProverQueueHighLatency
39+
expr: zk_queue_wait_ms > 60000 or (histogram_quantile(0.95, sum(rate(zk_queue_wait_ms_bucket[5m])) by (le)) > 60000)
40+
for: 2m
41+
labels:
42+
severity: warning
43+
service: zk-proof-service
44+
component: prover-queue
45+
annotations:
46+
summary: "ZK prover queue latency high"
47+
description: "ZK prover queue wait time is {{ $value }}ms, exceeding the 60 second SLA limit."
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Prometheus Configuration for SoroTask
2+
global:
3+
scrape_interval: 15s
4+
evaluation_interval: 15s
5+
6+
rule_files:
7+
- "alerts.yml"
8+
9+
alerting:
10+
alertmanagers:
11+
- static_configs:
12+
- targets:
13+
- "alertmanager:9093"
14+
15+
scrape_configs:
16+
- job_name: "sorotask-keeper"
17+
static_configs:
18+
- targets: ["keeper:3000"]
19+
20+
- job_name: "sorotask-indexer"
21+
static_configs:
22+
- targets: ["indexer:4000"]
23+
24+
- job_name: "zk-proof-service"
25+
static_configs:
26+
- targets: ["zk-proof-service:5000"]

0 commit comments

Comments
 (0)