feat: add optional basic auth for metrics endpoint

This commit is contained in:
Julian Tölle 2020-12-06 03:03:33 +01:00
parent a6097204c7
commit 879c6a62e2
7 changed files with 92 additions and 3 deletions

View file

@ -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.
- `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

View file

@ -64,6 +64,21 @@ spec:
{{- if .Values.prometheus.enabled }}
- name: PROMETHEUS_ENABLED
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 }}
securityContext:

View file

@ -8,3 +8,8 @@ type: Opaque
data:
spotify-client-secret: {{ .Values.spotify.clientSecret | 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 }}

View file

@ -89,3 +89,8 @@ sentry:
prometheus:
enabled: false
basicAuth:
enabled: false
username: ""
password: ""

View file

@ -45,6 +45,21 @@ import { ConfigModule as NestConfigModule } from "@nestjs/config";
// Prometheus for Metrics (Optional)
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(),
}
),
}),
}),
],

View 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();
}
}

View file

@ -6,6 +6,8 @@ import {
Module,
NestModule,
} 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
// injected services and upstream module does not support dynamic configuration
@ -13,8 +15,12 @@ import {
// https://github.com/digikare/nestjs-prom/issues/27
const promEnabled = process.env.PROMETHEUS_ENABLED === "true";
const METRIC_PATH = "/api/metrics";
@Module({})
export class MetricsModule implements NestModule {
constructor(private readonly config: ConfigService) {}
static forRoot(): DynamicModule {
const module = {
imports: [],
@ -22,7 +28,7 @@ export class MetricsModule implements NestModule {
};
if (promEnabled) {
const promOptions = {
metricPath: "/api/metrics",
metricPath: METRIC_PATH,
withDefaultsMetrics: true,
withDefaultController: true,
};
@ -42,10 +48,14 @@ export class MetricsModule implements NestModule {
}
configure(consumer: MiddlewareConsumer) {
if (promEnabled) {
if (this.config.get<boolean>("PROMETHEUS_ENABLED")) {
// We register the Middleware ourselves to avoid tracking
// 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);
}
}
}
}