Skip to content

Commit f0aa7ee

Browse files
kamilkisielan1ru4l
authored andcommitted
/auth-otel
1 parent d720e47 commit f0aa7ee

File tree

6 files changed

+129
-57
lines changed

6 files changed

+129
-57
lines changed

docker/configs/otel-collector/Dockerfile

Lines changed: 0 additions & 31 deletions
This file was deleted.

docker/docker-compose.dev.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,9 @@ services:
183183
condition: service_healthy
184184
build:
185185
context: ./configs/otel-collector
186-
dockerfile: Dockerfile
186+
dockerfile: ../otel-collector.dockerfile
187+
environment:
188+
HIVE_OTEL_AUTH_ENDPOINT: 'http://host.docker.internal:3001/otel-auth'
187189
volumes:
188190
- ./configs/otel-collector/builder-config.yaml:/builder-config.yaml
189191
- ./configs/otel-collector/config.yaml:/etc/otel-config.yaml

docker/docker.hcl

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -379,9 +379,7 @@ target "apollo-router" {
379379

380380
target "otel-collector" {
381381
inherits = ["otel-collector-base", get_target()]
382-
contexts = {
383-
config = "${PWD}/docker/configs/otel-collector"
384-
}
382+
context = "${PWD}/docker/configs/otel-collector"
385383
args = {
386384
IMAGE_TITLE = "graphql-hive/otel-collector"
387385
IMAGE_DESCRIPTION = "OTEL Collector for GraphQL Hive."

docker/otel-collector.dockerfile

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
FROM scratch AS config
2+
3+
COPY builder-config.yaml .
4+
15
FROM golang:1.23.7-bookworm AS builder
26

37
ARG OTEL_VERSION=0.122.0
@@ -10,7 +14,7 @@ RUN go install go.opentelemetry.io/collector/cmd/builder@v${OTEL_VERSION}
1014
COPY --from=config builder-config.yaml .
1115

1216
# Build the custom collector
13-
RUN builder --config=/build/builder-config.yaml
17+
RUN CGO_ENABLED=0 builder --config=/build/builder-config.yaml
1418

1519
# Stage 2: Final Image
1620
FROM alpine:3.14

packages/services/server/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -593,7 +593,7 @@ export async function main() {
593593
return;
594594
});
595595

596-
createOtelAuthEndpoint(server);
596+
createOtelAuthEndpoint({ server, authN, redis, pgPool: storage.pool, tracing: !!tracing });
597597

598598
if (env.cdn.providers.api !== null) {
599599
const s3 = {
Lines changed: 119 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,127 @@
11
import type { FastifyInstance } from 'fastify';
2+
import type Redis from 'ioredis';
3+
import type { DatabasePool } from 'slonik';
4+
import type { AuthN } from '@hive/api/modules/auth/lib/authz';
5+
import { PrometheusConfig } from '@hive/api/modules/shared/providers/prometheus-config';
6+
import { TargetsByIdCache } from '@hive/api/modules/target/providers/targets-by-id-cache';
7+
import { TargetsBySlugCache } from '@hive/api/modules/target/providers/targets-by-slug-cache';
8+
import { isUUID } from '@hive/api/shared/is-uuid';
29

3-
export function createOtelAuthEndpoint(server: FastifyInstance) {
4-
server.get('/otel-auth', (req, res) => {
5-
const authHeader = req.headers.authorization;
10+
export function createOtelAuthEndpoint(args: {
11+
server: FastifyInstance;
12+
tracing: boolean;
13+
authN: AuthN;
14+
redis: Redis;
15+
pgPool: DatabasePool;
16+
}) {
17+
const prometheusConfig = new PrometheusConfig(args.tracing);
18+
const targetsByIdCache = new TargetsByIdCache(args.redis, args.pgPool, prometheusConfig);
19+
const targetsBySlugCache = new TargetsBySlugCache(args.redis, args.pgPool, prometheusConfig);
20+
21+
args.server.get('/otel-auth', async (req, reply) => {
622
const targetRefHeader = req.headers['x-hive-target-ref'];
7-
req.log.info('request! ' + authHeader);
8-
9-
if (authHeader === 'Bearer 123') {
10-
if (targetRefHeader !== 'target-1') {
11-
// No access to target-1
12-
res.status(403).send({
13-
message: 'Forbidden access to the target',
14-
});
15-
return;
16-
}
17-
18-
res.status(200).send({
19-
message: 'Authenticated',
20-
targetId: targetRefHeader,
23+
24+
const targetRefRaw = Array.isArray(targetRefHeader) ? targetRefHeader[0] : targetRefHeader;
25+
26+
if (typeof targetRefRaw !== 'string' || targetRefRaw.trim().length === 0) {
27+
await reply.status(400).send({
28+
message: `Missing required header: 'X-Hive-Target-Ref'. Please provide a valid target reference in the request headers.`,
29+
});
30+
return;
31+
}
32+
33+
const targetRefParseResult = parseTargetRef(targetRefRaw);
34+
35+
if (!targetRefParseResult.ok) {
36+
await reply.status(400).send({
37+
message: targetRefParseResult.error,
38+
});
39+
return;
40+
}
41+
42+
const targetRef = targetRefParseResult.data;
43+
44+
const session = await args.authN.authenticate({ req, reply });
45+
46+
const target = await (targetRef.kind === 'id'
47+
? targetsByIdCache.get(targetRef.targetId)
48+
: targetsBySlugCache.get(targetRef));
49+
50+
if (!target) {
51+
await reply.status(404).send({
52+
message: `The specified target does not exist. Verify the target reference and try again.`,
2153
});
22-
} else {
23-
res.status(401).send({
24-
message: 'Unauthorized',
54+
return;
55+
}
56+
57+
const canReportUsage = await session.canPerformAction({
58+
organizationId: target.orgId,
59+
action: 'usage:report', // TODO: define a new action for tracing
60+
params: {
61+
organizationId: target.orgId,
62+
projectId: target.projectId,
63+
targetId: target.id,
64+
},
65+
});
66+
67+
if (!canReportUsage) {
68+
await reply.status(403).send({
69+
message: `You do not have permission to send traces for this target.`,
2570
});
71+
return;
2672
}
73+
74+
await reply.status(200).send({
75+
message: 'Authenticated',
76+
targetId: target.id,
77+
});
78+
return;
2779
});
2880
}
81+
82+
// TODO: https://github.com/open-telemetry/opentelemetry-collector/blob/ae0b83b94cc4d4cd90a73a2f390d23c25f848aec/config/confighttp/confighttp.go#L551C4-L551C84
83+
// swallows the error and returns 401 Unauthorized to the OTel SDK.
84+
const invalidTaretRefError =
85+
'Invalid slug or ID provided for target reference. ' +
86+
'Must match target slug "$organization_slug/$project_slug/$target_slug" (e.g. "the-guild/graphql-hive/staging") ' +
87+
'or UUID (e.g. c8164307-0b42-473e-a8c5-2860bb4beff6).';
88+
89+
function parseTargetRef(targetRef: string) {
90+
if (targetRef.includes('/')) {
91+
const parts = targetRef.split('/');
92+
93+
if (parts.length !== 3) {
94+
return {
95+
ok: false,
96+
error: invalidTaretRefError,
97+
} as const;
98+
}
99+
100+
const [organizationSlug, projectSlug, targetSlug] = parts;
101+
102+
return {
103+
ok: true,
104+
data: {
105+
kind: 'slugs',
106+
organizationSlug,
107+
projectSlug,
108+
targetSlug,
109+
},
110+
} as const;
111+
}
112+
113+
if (!isUUID(targetRef)) {
114+
return {
115+
ok: false,
116+
error: invalidTaretRefError,
117+
} as const;
118+
}
119+
120+
return {
121+
ok: true,
122+
data: {
123+
kind: 'id',
124+
targetId: targetRef,
125+
},
126+
} as const;
127+
}

0 commit comments

Comments
 (0)