mirror of
https://github.com/apricote/Listory.git
synced 2026-01-13 21:21:02 +00:00
feat: add optional basic auth for metrics endpoint
This commit is contained in:
parent
a6097204c7
commit
879c6a62e2
7 changed files with 92 additions and 3 deletions
|
|
@ -55,6 +55,9 @@ You can use Prometheus to track various metrics about your Listory deployment.
|
||||||
The metrics will be exposed on the `/api/metrics` endpoint. Make sure that this endpoint is not publicly available in your deployment.
|
The metrics will be exposed on the `/api/metrics` endpoint. Make sure that this endpoint is not publicly available in your deployment.
|
||||||
|
|
||||||
- `PROMETHEUS_ENABLED`: **false**, Set to `true` to enable Prometheus Metrics.
|
- `PROMETHEUS_ENABLED`: **false**, Set to `true` to enable Prometheus Metrics.
|
||||||
|
- `PROMETHEUS_BASIC_AUTH`: **false**, Set to `true` to require basic auth to access the metrics endpoint.
|
||||||
|
- `PROMETHEUS_BASIC_AUTH_USERNAME`: _Required_, if `PROMETHEUS_BASIC_AUTH` is `true`.
|
||||||
|
- `PROMETHEUS_BASIC_AUTH_PASSWORD`: _Required_, if `PROMETHEUS_BASIC_AUTH` is `true`.
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,21 @@ spec:
|
||||||
{{- if .Values.prometheus.enabled }}
|
{{- if .Values.prometheus.enabled }}
|
||||||
- name: PROMETHEUS_ENABLED
|
- name: PROMETHEUS_ENABLED
|
||||||
value: "true"
|
value: "true"
|
||||||
|
|
||||||
|
{{- if .Values.prometheus.basicAuth.enabled }}
|
||||||
|
- name: PROMETHEUS_BASIC_AUTH
|
||||||
|
value: "true"
|
||||||
|
- name: PROMETHEUS_BASIC_AUTH_USERNAME
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: {{ include "listory.fullname" . }}
|
||||||
|
key: prometheus-basic-auth-username
|
||||||
|
- name: PROMETHEUS_BASIC_AUTH_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: {{ include "listory.fullname" . }}
|
||||||
|
key: prometheus-basic-auth-password
|
||||||
|
{{- end }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
||||||
securityContext:
|
securityContext:
|
||||||
|
|
|
||||||
|
|
@ -8,3 +8,8 @@ type: Opaque
|
||||||
data:
|
data:
|
||||||
spotify-client-secret: {{ .Values.spotify.clientSecret | b64enc | quote }}
|
spotify-client-secret: {{ .Values.spotify.clientSecret | b64enc | quote }}
|
||||||
jwt-secret: {{ .Values.auth.jwtSecret | b64enc | quote }}
|
jwt-secret: {{ .Values.auth.jwtSecret | b64enc | quote }}
|
||||||
|
|
||||||
|
{{- if .Values.prometheus.basicAuth.enabled }}
|
||||||
|
prometheus-basic-auth-username: {{ .Values.prometheus.basicAuth.username | b64enc | quote }}
|
||||||
|
prometheus-basic-auth-password: {{ .Values.prometheus.basicAuth.password | b64enc | quote }}
|
||||||
|
{{- end }}
|
||||||
|
|
@ -89,3 +89,8 @@ sentry:
|
||||||
|
|
||||||
prometheus:
|
prometheus:
|
||||||
enabled: false
|
enabled: false
|
||||||
|
|
||||||
|
basicAuth:
|
||||||
|
enabled: false
|
||||||
|
username: ""
|
||||||
|
password: ""
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,21 @@ import { ConfigModule as NestConfigModule } from "@nestjs/config";
|
||||||
|
|
||||||
// Prometheus for Metrics (Optional)
|
// Prometheus for Metrics (Optional)
|
||||||
PROMETHEUS_ENABLED: Joi.boolean().default(false),
|
PROMETHEUS_ENABLED: Joi.boolean().default(false),
|
||||||
|
PROMETHEUS_BASIC_AUTH: Joi.boolean().default(false),
|
||||||
|
PROMETHEUS_BASIC_AUTH_USERNAME: Joi.string().when(
|
||||||
|
"PROMETHEUS_BASIC_AUTH",
|
||||||
|
{
|
||||||
|
is: Joi.valid(true),
|
||||||
|
then: Joi.required(),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
PROMETHEUS_BASIC_AUTH_PASSWORD: Joi.string().when(
|
||||||
|
"PROMETHEUS_BASIC_AUTH",
|
||||||
|
{
|
||||||
|
is: Joi.valid(true),
|
||||||
|
then: Joi.required(),
|
||||||
|
}
|
||||||
|
),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
|
|
||||||
36
src/metrics/metrics-auth.middleware.ts
Normal file
36
src/metrics/metrics-auth.middleware.ts
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
import {
|
||||||
|
Injectable,
|
||||||
|
NestMiddleware,
|
||||||
|
UnauthorizedException,
|
||||||
|
} from "@nestjs/common";
|
||||||
|
import { ConfigService } from "@nestjs/config";
|
||||||
|
import { IncomingMessage } from "http";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class MetricsAuthMiddleware implements NestMiddleware {
|
||||||
|
private readonly expectedHeaderValue: string;
|
||||||
|
|
||||||
|
constructor(config: ConfigService) {
|
||||||
|
const username = config.get<string>("PROMETHEUS_BASIC_AUTH_USERNAME");
|
||||||
|
const password = config.get<string>("PROMETHEUS_BASIC_AUTH_PASSWORD");
|
||||||
|
|
||||||
|
this.expectedHeaderValue = MetricsAuthMiddleware.buildHeaderValue(
|
||||||
|
username,
|
||||||
|
password
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static buildHeaderValue(username: string, password: string): string {
|
||||||
|
return `Basic ${Buffer.from(`${username}:${password}`).toString("base64")}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
use(req: IncomingMessage, res: any, next: () => void) {
|
||||||
|
const header = req.headers?.authorization;
|
||||||
|
|
||||||
|
if (header !== this.expectedHeaderValue) {
|
||||||
|
throw new UnauthorizedException("MetricsBasicAuthNotMatching");
|
||||||
|
}
|
||||||
|
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,6 +6,8 @@ import {
|
||||||
Module,
|
Module,
|
||||||
NestModule,
|
NestModule,
|
||||||
} from "@nestjs/common";
|
} from "@nestjs/common";
|
||||||
|
import { ConfigService } from "@nestjs/config";
|
||||||
|
import { MetricsAuthMiddleware } from "./metrics-auth.middleware";
|
||||||
|
|
||||||
// Dirty hack because we can not conditionally import modules based on
|
// Dirty hack because we can not conditionally import modules based on
|
||||||
// injected services and upstream module does not support dynamic configuration
|
// injected services and upstream module does not support dynamic configuration
|
||||||
|
|
@ -13,8 +15,12 @@ import {
|
||||||
// https://github.com/digikare/nestjs-prom/issues/27
|
// https://github.com/digikare/nestjs-prom/issues/27
|
||||||
const promEnabled = process.env.PROMETHEUS_ENABLED === "true";
|
const promEnabled = process.env.PROMETHEUS_ENABLED === "true";
|
||||||
|
|
||||||
|
const METRIC_PATH = "/api/metrics";
|
||||||
|
|
||||||
@Module({})
|
@Module({})
|
||||||
export class MetricsModule implements NestModule {
|
export class MetricsModule implements NestModule {
|
||||||
|
constructor(private readonly config: ConfigService) {}
|
||||||
|
|
||||||
static forRoot(): DynamicModule {
|
static forRoot(): DynamicModule {
|
||||||
const module = {
|
const module = {
|
||||||
imports: [],
|
imports: [],
|
||||||
|
|
@ -22,7 +28,7 @@ export class MetricsModule implements NestModule {
|
||||||
};
|
};
|
||||||
if (promEnabled) {
|
if (promEnabled) {
|
||||||
const promOptions = {
|
const promOptions = {
|
||||||
metricPath: "/api/metrics",
|
metricPath: METRIC_PATH,
|
||||||
withDefaultsMetrics: true,
|
withDefaultsMetrics: true,
|
||||||
withDefaultController: true,
|
withDefaultController: true,
|
||||||
};
|
};
|
||||||
|
|
@ -42,10 +48,14 @@ export class MetricsModule implements NestModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
configure(consumer: MiddlewareConsumer) {
|
configure(consumer: MiddlewareConsumer) {
|
||||||
if (promEnabled) {
|
if (this.config.get<boolean>("PROMETHEUS_ENABLED")) {
|
||||||
// We register the Middleware ourselves to avoid tracking
|
// We register the Middleware ourselves to avoid tracking
|
||||||
// latency for static files served for the frontend.
|
// latency for static files served for the frontend.
|
||||||
consumer.apply(InboundMiddleware).forRoutes("/api");
|
consumer.apply(InboundMiddleware).exclude(METRIC_PATH).forRoutes("/api");
|
||||||
|
|
||||||
|
if (this.config.get<boolean>("PROMETHEUS_BASIC_AUTH")) {
|
||||||
|
consumer.apply(MetricsAuthMiddleware).forRoutes(METRIC_PATH);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue