Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
55899f0
feat(relay): add resource identity auth for relays
saifsmailbox98 May 18, 2026
25ff699
feat(networking-ui): split view overhaul for gateways, pools, and relays
saifsmailbox98 May 18, 2026
06a82e6
Revert "feat(networking-ui): split view overhaul for gateways, pools,…
saifsmailbox98 May 18, 2026
6e05559
feat(relay): add connected gateways section, fix create modal and hea…
saifsmailbox98 May 18, 2026
5b4aff5
fix(relay): host editing, security hardening, audit log, and review f…
saifsmailbox98 May 18, 2026
566696d
merge: resolve conflicts with main (heartbeatTTL, remove lastHealthCh…
saifsmailbox98 May 18, 2026
9697458
fix(relay): remove lastHealthCheckStatus references after upstream merge
saifsmailbox98 May 18, 2026
544ec7c
fix(relay): address PR review — resource type guard, audit event, imp…
saifsmailbox98 May 18, 2026
4905e27
chore(schema): regenerate resource-auth-methods schema from DB
saifsmailbox98 May 18, 2026
4da26ed
fix(relay): replace for-of with reduce to satisfy no-restricted-synta…
saifsmailbox98 May 18, 2026
9910d2e
fix(relay): address second round PR review
saifsmailbox98 May 18, 2026
40d538f
docs(relay): update CLI docs and permissions for resource auth
saifsmailbox98 May 18, 2026
22d0333
docs(relay): match gateway docs pattern — accordion per auth method w…
saifsmailbox98 May 19, 2026
212d4b2
chore(relay): apply prettier formatting
saifsmailbox98 May 19, 2026
96587f2
fix(resource-auth): validate resource type before consuming enrollmen…
saifsmailbox98 May 20, 2026
6d35115
fix(relay): org-scope relay lookups in route handlers
saifsmailbox98 May 20, 2026
2a9c8a3
fix(resource-auth): use resource type in error message instead of har…
saifsmailbox98 May 20, 2026
b744290
fix(relay): add timeout parameter to createRelayConnection, use 15s f…
saifsmailbox98 May 20, 2026
d02e0c5
chore: remove unused BadRequestError imports from relay and gateway r…
saifsmailbox98 May 20, 2026
5327a9f
chore: use realistic timestamp for relay-resource-auth migration
saifsmailbox98 May 20, 2026
40a6ea6
chore: update relay-resource-auth migration timestamp
saifsmailbox98 May 20, 2026
ec81260
docs(relay): remove legacy machine identity section from CLI docs
saifsmailbox98 May 21, 2026
71bec45
docs(relay): update deployment guide and terraform for resource auth
saifsmailbox98 May 21, 2026
628b151
docs(relay): minor copy fix
saifsmailbox98 May 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions backend/src/db/migrations/20260515000000_relay-resource-auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { Knex } from "knex";

import { TableName } from "../schemas";

export async function up(knex: Knex): Promise<void> {
// 1. Add relayId FK to resource_auth_methods — same nullable-FK-per-resource-type
// pattern used for gatewayId (see 20260430143000_resource-auth-methods.ts).
if (await knex.schema.hasTable(TableName.ResourceAuthMethod)) {
const hasRelayId = await knex.schema.hasColumn(TableName.ResourceAuthMethod, "relayId");
if (!hasRelayId) {
await knex.schema.alterTable(TableName.ResourceAuthMethod, (t) => {
t.uuid("relayId").nullable();
t.foreign("relayId").references("id").inTable(TableName.Relay).onDelete("CASCADE");
});

await knex.schema.raw(`
CREATE UNIQUE INDEX one_method_per_relay
ON ${TableName.ResourceAuthMethod} ("relayId")
WHERE "relayId" IS NOT NULL
`);
}
}

// 2. Add tokenVersion to relays — used for stateless JWT revocation,
// same pattern as gateways_v2.tokenVersion.
if (await knex.schema.hasTable(TableName.Relay)) {
const hasTokenVersion = await knex.schema.hasColumn(TableName.Relay, "tokenVersion");
if (!hasTokenVersion) {
await knex.schema.alterTable(TableName.Relay, (t) => {
t.integer("tokenVersion").notNullable().defaultTo(0);
});
}
}
}

export async function down(knex: Knex): Promise<void> {
if (await knex.schema.hasTable(TableName.Relay)) {
const hasTokenVersion = await knex.schema.hasColumn(TableName.Relay, "tokenVersion");
if (hasTokenVersion) {
await knex.schema.alterTable(TableName.Relay, (t) => {
t.dropColumn("tokenVersion");
});
}
}

if (await knex.schema.hasTable(TableName.ResourceAuthMethod)) {
const hasRelayId = await knex.schema.hasColumn(TableName.ResourceAuthMethod, "relayId");
if (hasRelayId) {
await knex.schema.raw(`DROP INDEX IF EXISTS one_method_per_relay`);
await knex.schema.alterTable(TableName.ResourceAuthMethod, (t) => {
t.dropColumn("relayId");
});
}
}
}
3 changes: 2 additions & 1 deletion backend/src/db/schemas/relays.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ export const RelaysSchema = z.object({
name: z.string(),
host: z.string(),
heartbeat: z.date().nullable().optional(),
healthAlertedAt: z.date().nullable().optional()
healthAlertedAt: z.date().nullable().optional(),
tokenVersion: z.number().default(0)
});

export type TRelays = z.infer<typeof RelaysSchema>;
Expand Down
3 changes: 2 additions & 1 deletion backend/src/db/schemas/resource-auth-methods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ export const ResourceAuthMethodsSchema = z.object({
gatewayId: z.string().uuid().nullable().optional(),
method: z.string(),
createdAt: z.date(),
updatedAt: z.date()
updatedAt: z.date(),
relayId: z.string().uuid().nullable().optional()
});

export type TResourceAuthMethods = z.infer<typeof ResourceAuthMethodsSchema>;
Expand Down
2 changes: 2 additions & 0 deletions backend/src/ee/routes/v1/relay-router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export const registerRelayRouter = async (server: FastifyZodProvider) => {
rateLimit: writeLimit
},
schema: {
hide: true,
operationId: "registerOrgRelay",
body: z.object({
host: z.string(),
Expand Down Expand Up @@ -212,6 +213,7 @@ export const registerRelayRouter = async (server: FastifyZodProvider) => {
rateLimit: writeLimit
},
schema: {
hide: true,
operationId: "heartbeatOrgRelay",
body: z.object({
name: slugSchema({ min: 1, max: 32, field: "name" })
Expand Down
3 changes: 3 additions & 0 deletions backend/src/ee/routes/v2/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
import { registerDeprecatedProjectRoleRouter } from "./deprecated-project-role-router";
import { registerGatewayV2Router } from "./gateway-router";
import { registerIdentityProjectAdditionalPrivilegeRouter } from "./identity-project-additional-privilege-router";
import { registerRelayV2Router } from "./relay-router";
import { registerSecretApprovalPolicyRouter } from "./secret-approval-policy-router";
import { registerSecretVersionRouter } from "./secret-version-router";

Expand Down Expand Up @@ -57,4 +58,6 @@ export const registerV2EERoutes = async (server: FastifyZodProvider) => {
);

await server.register(registerSecretVersionRouter, { prefix: "/secret-versions" });

await server.register(registerRelayV2Router, { prefix: "/relays" });
};
Loading
Loading