diff --git a/backend/src/@types/knex.d.ts b/backend/src/@types/knex.d.ts index c26f1128e..552425a45 100644 --- a/backend/src/@types/knex.d.ts +++ b/backend/src/@types/knex.d.ts @@ -215,9 +215,6 @@ import { TProjectEnvironments, TProjectEnvironmentsInsert, TProjectEnvironmentsUpdate, - TProjectGateways, - TProjectGatewaysInsert, - TProjectGatewaysUpdate, TProjectKeys, TProjectKeysInsert, TProjectKeysUpdate, @@ -1018,11 +1015,6 @@ declare module "knex/types/tables" { TKmipClientCertificatesUpdate >; [TableName.Gateway]: KnexOriginal.CompositeTableType; - [TableName.ProjectGateway]: KnexOriginal.CompositeTableType< - TProjectGateways, - TProjectGatewaysInsert, - TProjectGatewaysUpdate - >; [TableName.OrgGatewayConfig]: KnexOriginal.CompositeTableType< TOrgGatewayConfig, TOrgGatewayConfigInsert, diff --git a/backend/src/db/migrations/20250212191958_create-gateway.ts b/backend/src/db/migrations/20250212191958_create-gateway.ts index 14c498ca9..73f4c5fd7 100644 --- a/backend/src/db/migrations/20250212191958_create-gateway.ts +++ b/backend/src/db/migrations/20250212191958_create-gateway.ts @@ -68,8 +68,8 @@ export async function up(knex: Knex): Promise { await createOnUpdateTrigger(knex, TableName.Gateway); } - if (!(await knex.schema.hasTable(TableName.ProjectGateway))) { - await knex.schema.createTable(TableName.ProjectGateway, (t) => { + if (!(await knex.schema.hasTable("project_gateways"))) { + await knex.schema.createTable("project_gateways", (t) => { t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid()); t.string("projectId").notNullable(); @@ -81,7 +81,7 @@ export async function up(knex: Knex): Promise { t.timestamps(true, true, true); }); - await createOnUpdateTrigger(knex, TableName.ProjectGateway); + await createOnUpdateTrigger(knex, "project_gateways"); } if (await knex.schema.hasTable(TableName.DynamicSecret)) { @@ -90,7 +90,7 @@ export async function up(knex: Knex): Promise { // not setting a foreign constraint so that cascade effects are not triggered if (!doesGatewayColExist) { t.uuid("projectGatewayId"); - t.foreign("projectGatewayId").references("id").inTable(TableName.ProjectGateway); + t.foreign("projectGatewayId").references("id").inTable("project_gateways"); } }); } @@ -104,8 +104,8 @@ export async function down(knex: Knex): Promise { }); } - await knex.schema.dropTableIfExists(TableName.ProjectGateway); - await dropOnUpdateTrigger(knex, TableName.ProjectGateway); + await knex.schema.dropTableIfExists("project_gateways"); + await dropOnUpdateTrigger(knex, "project_gateways"); await knex.schema.dropTableIfExists(TableName.Gateway); await dropOnUpdateTrigger(knex, TableName.Gateway); diff --git a/backend/src/db/migrations/20250513081738_remove-gateway-project-link.ts b/backend/src/db/migrations/20250513081738_remove-gateway-project-link.ts new file mode 100644 index 000000000..86936eca0 --- /dev/null +++ b/backend/src/db/migrations/20250513081738_remove-gateway-project-link.ts @@ -0,0 +1,20 @@ +import { Knex } from "knex"; + +import { TableName } from "../schemas"; + +// Note(daniel): We aren't dropping tables or columns in this migrations so we can easily rollback if needed. +// In the future we need to drop the projectGatewayId on the dynamic secrets table, and drop the project_gateways table entirely. + +export async function up(knex: Knex): Promise { + await knex.schema.alterTable(TableName.DynamicSecret, (table) => { + table.uuid("gatewayId").nullable(); + table.foreign("gatewayId").references("id").inTable(TableName.Gateway).onDelete("SET NULL"); + }); +} + +export async function down(knex: Knex): Promise { + await knex.schema.alterTable(TableName.DynamicSecret, (table) => { + table.dropForeign("gatewayId"); + table.dropColumn("gatewayId"); + }); +} diff --git a/backend/src/db/schemas/dynamic-secrets.ts b/backend/src/db/schemas/dynamic-secrets.ts index 913a6d475..350a32b7a 100644 --- a/backend/src/db/schemas/dynamic-secrets.ts +++ b/backend/src/db/schemas/dynamic-secrets.ts @@ -27,7 +27,8 @@ export const DynamicSecretsSchema = z.object({ createdAt: z.date(), updatedAt: z.date(), encryptedInput: zodBuffer, - projectGatewayId: z.string().uuid().nullable().optional() + projectGatewayId: z.string().uuid().nullable().optional(), + gatewayId: z.string().uuid().nullable().optional() }); export type TDynamicSecrets = z.infer; diff --git a/backend/src/db/schemas/index.ts b/backend/src/db/schemas/index.ts index b71d51908..63f2e082c 100644 --- a/backend/src/db/schemas/index.ts +++ b/backend/src/db/schemas/index.ts @@ -71,7 +71,6 @@ export * from "./pki-collection-items"; export * from "./pki-collections"; export * from "./project-bots"; export * from "./project-environments"; -export * from "./project-gateways"; export * from "./project-keys"; export * from "./project-memberships"; export * from "./project-roles"; diff --git a/backend/src/db/schemas/models.ts b/backend/src/db/schemas/models.ts index 81d5319e1..eb44ffe1c 100644 --- a/backend/src/db/schemas/models.ts +++ b/backend/src/db/schemas/models.ts @@ -123,7 +123,6 @@ export enum TableName { // Gateway OrgGatewayConfig = "org_gateway_config", Gateway = "gateways", - ProjectGateway = "project_gateways", // junction tables with tags SecretV2JnTag = "secret_v2_tag_junction", JnSecretTag = "secret_tag_junction", diff --git a/backend/src/ee/routes/v1/gateway-router.ts b/backend/src/ee/routes/v1/gateway-router.ts index c916e229e..40e9c1580 100644 --- a/backend/src/ee/routes/v1/gateway-router.ts +++ b/backend/src/ee/routes/v1/gateway-router.ts @@ -121,14 +121,7 @@ export const registerGatewayRouter = async (server: FastifyZodProvider) => { identity: z.object({ name: z.string(), id: z.string() - }), - projects: z - .object({ - name: z.string(), - id: z.string(), - slug: z.string() - }) - .array() + }) }).array() }) } @@ -158,17 +151,15 @@ export const registerGatewayRouter = async (server: FastifyZodProvider) => { identity: z.object({ name: z.string(), id: z.string() - }), - projectGatewayId: z.string() + }) }).array() }) } }, onRequest: verifyAuth([AuthMode.IDENTITY_ACCESS_TOKEN, AuthMode.JWT]), handler: async (req) => { - const gateways = await server.services.gateway.getProjectGateways({ - projectId: req.params.projectId, - projectPermission: req.permission + const gateways = await server.services.gateway.listGateways({ + orgPermission: req.permission }); return { gateways }; } @@ -216,8 +207,7 @@ export const registerGatewayRouter = async (server: FastifyZodProvider) => { id: z.string() }), body: z.object({ - name: slugSchema({ field: "name" }).optional(), - projectIds: z.string().array().optional() + name: slugSchema({ field: "name" }).optional() }), response: { 200: z.object({ @@ -230,8 +220,7 @@ export const registerGatewayRouter = async (server: FastifyZodProvider) => { const gateway = await server.services.gateway.updateGatewayById({ orgPermission: req.permission, id: req.params.id, - name: req.body.name, - projectIds: req.body.projectIds + name: req.body.name }); return { gateway }; } diff --git a/backend/src/ee/services/dynamic-secret/dynamic-secret-service.ts b/backend/src/ee/services/dynamic-secret/dynamic-secret-service.ts index 44c18b001..5a74236a9 100644 --- a/backend/src/ee/services/dynamic-secret/dynamic-secret-service.ts +++ b/backend/src/ee/services/dynamic-secret/dynamic-secret-service.ts @@ -17,7 +17,8 @@ import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-fold import { TDynamicSecretLeaseDALFactory } from "../dynamic-secret-lease/dynamic-secret-lease-dal"; import { TDynamicSecretLeaseQueueServiceFactory } from "../dynamic-secret-lease/dynamic-secret-lease-queue"; -import { TProjectGatewayDALFactory } from "../gateway/project-gateway-dal"; +import { TGatewayDALFactory } from "../gateway/gateway-dal"; +import { OrgPermissionGatewayActions, OrgPermissionSubjects } from "../permission/org-permission"; import { TDynamicSecretDALFactory } from "./dynamic-secret-dal"; import { DynamicSecretStatus, @@ -44,9 +45,9 @@ type TDynamicSecretServiceFactoryDep = { licenseService: Pick; folderDAL: Pick; projectDAL: Pick; - permissionService: Pick; + permissionService: Pick; kmsService: Pick; - projectGatewayDAL: Pick; + gatewayDAL: Pick; resourceMetadataDAL: Pick; }; @@ -62,7 +63,7 @@ export const dynamicSecretServiceFactory = ({ dynamicSecretQueueService, projectDAL, kmsService, - projectGatewayDAL, + gatewayDAL, resourceMetadataDAL }: TDynamicSecretServiceFactoryDep) => { const create = async ({ @@ -117,15 +118,31 @@ export const dynamicSecretServiceFactory = ({ const inputs = await selectedProvider.validateProviderInputs(provider.inputs); let selectedGatewayId: string | null = null; - if (inputs && typeof inputs === "object" && "projectGatewayId" in inputs && inputs.projectGatewayId) { - const projectGatewayId = inputs.projectGatewayId as string; + if (inputs && typeof inputs === "object" && "gatewayId" in inputs && inputs.gatewayId) { + const gatewayId = inputs.gatewayId as string; - const projectGateway = await projectGatewayDAL.findOne({ id: projectGatewayId, projectId }); - if (!projectGateway) + const [gateway] = await gatewayDAL.find({ id: gatewayId }); + + if (!gateway) { throw new NotFoundError({ - message: `Project gateway with ${projectGatewayId} not found` + message: `Gateway with ID ${gatewayId} not found` }); - selectedGatewayId = projectGateway.id; + } + + const { permission: orgPermission } = await permissionService.getOrgPermission( + actor, + actorId, + gateway.orgId, + actorAuthMethod, + actorOrgId + ); + + ForbiddenError.from(orgPermission).throwUnlessCan( + OrgPermissionGatewayActions.AttachGateways, + OrgPermissionSubjects.Gateway + ); + + selectedGatewayId = gateway.id; } const isConnected = await selectedProvider.validateConnection(provider.inputs); @@ -146,7 +163,7 @@ export const dynamicSecretServiceFactory = ({ defaultTTL, folderId: folder.id, name, - projectGatewayId: selectedGatewayId + gatewayId: selectedGatewayId }, tx ); @@ -255,20 +272,30 @@ export const dynamicSecretServiceFactory = ({ const updatedInput = await selectedProvider.validateProviderInputs(newInput); let selectedGatewayId: string | null = null; - if ( - updatedInput && - typeof updatedInput === "object" && - "projectGatewayId" in updatedInput && - updatedInput?.projectGatewayId - ) { - const projectGatewayId = updatedInput.projectGatewayId as string; + if (updatedInput && typeof updatedInput === "object" && "gatewayId" in updatedInput && updatedInput?.gatewayId) { + const gatewayId = updatedInput.gatewayId as string; - const projectGateway = await projectGatewayDAL.findOne({ id: projectGatewayId, projectId }); - if (!projectGateway) + const [gateway] = await gatewayDAL.find({ id: gatewayId }); + if (!gateway) { throw new NotFoundError({ - message: `Project gateway with ${projectGatewayId} not found` + message: `Gateway with ID ${gatewayId} not found` }); - selectedGatewayId = projectGateway.id; + } + + const { permission: orgPermission } = await permissionService.getOrgPermission( + actor, + actorId, + gateway.orgId, + actorAuthMethod, + actorOrgId + ); + + ForbiddenError.from(orgPermission).throwUnlessCan( + OrgPermissionGatewayActions.AttachGateways, + OrgPermissionSubjects.Gateway + ); + + selectedGatewayId = gateway.id; } const isConnected = await selectedProvider.validateConnection(newInput); @@ -284,7 +311,7 @@ export const dynamicSecretServiceFactory = ({ defaultTTL, name: newName ?? name, status: null, - projectGatewayId: selectedGatewayId + gatewayId: selectedGatewayId }, tx ); diff --git a/backend/src/ee/services/dynamic-secret/providers/index.ts b/backend/src/ee/services/dynamic-secret/providers/index.ts index faa671980..737aaadea 100644 --- a/backend/src/ee/services/dynamic-secret/providers/index.ts +++ b/backend/src/ee/services/dynamic-secret/providers/index.ts @@ -18,7 +18,7 @@ import { SqlDatabaseProvider } from "./sql-database"; import { TotpProvider } from "./totp"; type TBuildDynamicSecretProviderDTO = { - gatewayService: Pick; + gatewayService: Pick; }; export const buildDynamicSecretProviders = ({ diff --git a/backend/src/ee/services/dynamic-secret/providers/models.ts b/backend/src/ee/services/dynamic-secret/providers/models.ts index 449f6d8f6..0c6eaf151 100644 --- a/backend/src/ee/services/dynamic-secret/providers/models.ts +++ b/backend/src/ee/services/dynamic-secret/providers/models.ts @@ -137,7 +137,7 @@ export const DynamicSecretSqlDBSchema = z.object({ revocationStatement: z.string().trim(), renewStatement: z.string().trim().optional(), ca: z.string().optional(), - projectGatewayId: z.string().nullable().optional() + gatewayId: z.string().nullable().optional() }); export const DynamicSecretCassandraSchema = z.object({ diff --git a/backend/src/ee/services/dynamic-secret/providers/sql-database.ts b/backend/src/ee/services/dynamic-secret/providers/sql-database.ts index 178ca4ef9..3ae85ed7b 100644 --- a/backend/src/ee/services/dynamic-secret/providers/sql-database.ts +++ b/backend/src/ee/services/dynamic-secret/providers/sql-database.ts @@ -112,14 +112,14 @@ const generateUsername = (provider: SqlProviders) => { }; type TSqlDatabaseProviderDTO = { - gatewayService: Pick; + gatewayService: Pick; }; export const SqlDatabaseProvider = ({ gatewayService }: TSqlDatabaseProviderDTO): TDynamicProviderFns => { const validateProviderInputs = async (inputs: unknown) => { const providerInputs = await DynamicSecretSqlDBSchema.parseAsync(inputs); - const [hostIp] = await verifyHostInputValidity(providerInputs.host, Boolean(providerInputs.projectGatewayId)); + const [hostIp] = await verifyHostInputValidity(providerInputs.host, Boolean(providerInputs.gatewayId)); validateHandlebarTemplate("SQL creation", providerInputs.creationStatement, { allowedExpressions: (val) => ["username", "password", "expiration", "database"].includes(val) }); @@ -168,7 +168,7 @@ export const SqlDatabaseProvider = ({ gatewayService }: TSqlDatabaseProviderDTO) providerInputs: z.infer, gatewayCallback: (host: string, port: number) => Promise ) => { - const relayDetails = await gatewayService.fnGetGatewayClientTls(providerInputs.projectGatewayId as string); + const relayDetails = await gatewayService.fnGetGatewayClientTlsByGatewayId(providerInputs.gatewayId as string); const [relayHost, relayPort] = relayDetails.relayAddress.split(":"); await withGatewayProxy( async (port) => { @@ -202,7 +202,7 @@ export const SqlDatabaseProvider = ({ gatewayService }: TSqlDatabaseProviderDTO) await db.destroy(); }; - if (providerInputs.projectGatewayId) { + if (providerInputs.gatewayId) { await gatewayProxyWrapper(providerInputs, gatewayCallback); } else { await gatewayCallback(); @@ -238,7 +238,7 @@ export const SqlDatabaseProvider = ({ gatewayService }: TSqlDatabaseProviderDTO) await db.destroy(); } }; - if (providerInputs.projectGatewayId) { + if (providerInputs.gatewayId) { await gatewayProxyWrapper(providerInputs, gatewayCallback); } else { await gatewayCallback(); @@ -265,7 +265,7 @@ export const SqlDatabaseProvider = ({ gatewayService }: TSqlDatabaseProviderDTO) await db.destroy(); } }; - if (providerInputs.projectGatewayId) { + if (providerInputs.gatewayId) { await gatewayProxyWrapper(providerInputs, gatewayCallback); } else { await gatewayCallback(); @@ -301,7 +301,7 @@ export const SqlDatabaseProvider = ({ gatewayService }: TSqlDatabaseProviderDTO) await db.destroy(); } }; - if (providerInputs.projectGatewayId) { + if (providerInputs.gatewayId) { await gatewayProxyWrapper(providerInputs, gatewayCallback); } else { await gatewayCallback(); diff --git a/backend/src/ee/services/gateway/gateway-dal.ts b/backend/src/ee/services/gateway/gateway-dal.ts index fbf5558e4..b51c781ee 100644 --- a/backend/src/ee/services/gateway/gateway-dal.ts +++ b/backend/src/ee/services/gateway/gateway-dal.ts @@ -1,16 +1,7 @@ -import { Knex } from "knex"; - import { TDbClient } from "@app/db"; import { GatewaysSchema, TableName, TGateways } from "@app/db/schemas"; import { DatabaseError } from "@app/lib/errors"; -import { - buildFindFilter, - ormify, - selectAllTableCols, - sqlNestRelationships, - TFindFilter, - TFindOpt -} from "@app/lib/knex"; +import { buildFindFilter, ormify, selectAllTableCols, TFindFilter, TFindOpt } from "@app/lib/knex"; export type TGatewayDALFactory = ReturnType; @@ -21,17 +12,16 @@ export const gatewayDALFactory = (db: TDbClient) => { try { const query = (tx || db)(TableName.Gateway) // eslint-disable-next-line @typescript-eslint/no-misused-promises - .where(buildFindFilter(filter)) + .where(buildFindFilter(filter, TableName.Gateway)) .join(TableName.Identity, `${TableName.Identity}.id`, `${TableName.Gateway}.identityId`) - .leftJoin(TableName.ProjectGateway, `${TableName.ProjectGateway}.gatewayId`, `${TableName.Gateway}.id`) - .leftJoin(TableName.Project, `${TableName.Project}.id`, `${TableName.ProjectGateway}.projectId`) + .join( + TableName.IdentityOrgMembership, + `${TableName.IdentityOrgMembership}.identityId`, + `${TableName.Gateway}.identityId` + ) .select(selectAllTableCols(TableName.Gateway)) - .select( - db.ref("name").withSchema(TableName.Identity).as("identityName"), - db.ref("name").withSchema(TableName.Project).as("projectName"), - db.ref("slug").withSchema(TableName.Project).as("projectSlug"), - db.ref("id").withSchema(TableName.Project).as("projectId") - ); + .select(db.ref("orgId").withSchema(TableName.IdentityOrgMembership).as("identityOrgId")) + .select(db.ref("name").withSchema(TableName.Identity).as("identityName")); if (limit) void query.limit(limit); if (offset) void query.offset(offset); if (sort) { @@ -39,48 +29,16 @@ export const gatewayDALFactory = (db: TDbClient) => { } const docs = await query; - return sqlNestRelationships({ - data: docs, - key: "id", - parentMapper: (data) => ({ - ...GatewaysSchema.parse(data), - identity: { id: data.identityId, name: data.identityName } - }), - childrenMapper: [ - { - key: "projectId", - label: "projects" as const, - mapper: ({ projectId, projectName, projectSlug }) => ({ - id: projectId, - name: projectName, - slug: projectSlug - }) - } - ] - }); + + return docs.map((el) => ({ + ...GatewaysSchema.parse(el), + orgId: el.identityOrgId as string, // todo(daniel): figure out why typescript is not inferring this as a string + identity: { id: el.identityId, name: el.identityName } + })); } catch (error) { throw new DatabaseError({ error, name: `${TableName.Gateway}: Find` }); } }; - const findByProjectId = async (projectId: string, tx?: Knex) => { - try { - const query = (tx || db)(TableName.Gateway) - .join(TableName.Identity, `${TableName.Identity}.id`, `${TableName.Gateway}.identityId`) - .join(TableName.ProjectGateway, `${TableName.ProjectGateway}.gatewayId`, `${TableName.Gateway}.id`) - .select(selectAllTableCols(TableName.Gateway)) - .select( - db.ref("name").withSchema(TableName.Identity).as("identityName"), - db.ref("id").withSchema(TableName.ProjectGateway).as("projectGatewayId") - ) - .where({ [`${TableName.ProjectGateway}.projectId` as "projectId"]: projectId }); - - const docs = await query; - return docs.map((el) => ({ ...el, identity: { id: el.identityId, name: el.identityName } })); - } catch (error) { - throw new DatabaseError({ error, name: `${TableName.Gateway}: Find by project id` }); - } - }; - - return { ...orm, find, findByProjectId }; + return { ...orm, find }; }; diff --git a/backend/src/ee/services/gateway/gateway-service.ts b/backend/src/ee/services/gateway/gateway-service.ts index 141797c98..25f0b384a 100644 --- a/backend/src/ee/services/gateway/gateway-service.ts +++ b/backend/src/ee/services/gateway/gateway-service.ts @@ -4,7 +4,6 @@ import { ForbiddenError } from "@casl/ability"; import * as x509 from "@peculiar/x509"; import { z } from "zod"; -import { ActionProjectType } from "@app/db/schemas"; import { KeyStorePrefixes, PgSqlLock, TKeyStoreFactory } from "@app/keystore/keystore"; import { getConfig } from "@app/lib/config/env"; import { BadRequestError, NotFoundError } from "@app/lib/errors"; @@ -27,17 +26,14 @@ import { TGatewayDALFactory } from "./gateway-dal"; import { TExchangeAllocatedRelayAddressDTO, TGetGatewayByIdDTO, - TGetProjectGatewayByIdDTO, THeartBeatDTO, TListGatewaysDTO, TUpdateGatewayByIdDTO } from "./gateway-types"; import { TOrgGatewayConfigDALFactory } from "./org-gateway-config-dal"; -import { TProjectGatewayDALFactory } from "./project-gateway-dal"; type TGatewayServiceFactoryDep = { gatewayDAL: TGatewayDALFactory; - projectGatewayDAL: TProjectGatewayDALFactory; orgGatewayConfigDAL: Pick; licenseService: Pick; kmsService: Pick; @@ -57,8 +53,7 @@ export const gatewayServiceFactory = ({ kmsService, permissionService, orgGatewayConfigDAL, - keyStore, - projectGatewayDAL + keyStore }: TGatewayServiceFactoryDep) => { const $validateOrgAccessToGateway = async (orgId: string, actorId: string, actorAuthMethod: ActorAuthMethod) => { // if (!licenseService.onPremFeatures.gateway) { @@ -526,7 +521,7 @@ export const gatewayServiceFactory = ({ return gateway; }; - const updateGatewayById = async ({ orgPermission, id, name, projectIds }: TUpdateGatewayByIdDTO) => { + const updateGatewayById = async ({ orgPermission, id, name }: TUpdateGatewayByIdDTO) => { const { permission } = await permissionService.getOrgPermission( orgPermission.type, orgPermission.id, @@ -543,15 +538,6 @@ export const gatewayServiceFactory = ({ const [gateway] = await gatewayDAL.update({ id, orgGatewayRootCaId: orgGatewayConfig.id }, { name }); if (!gateway) throw new NotFoundError({ message: `Gateway with ID ${id} not found.` }); - if (projectIds) { - await projectGatewayDAL.transaction(async (tx) => { - await projectGatewayDAL.delete({ gatewayId: gateway.id }, tx); - await projectGatewayDAL.insertMany( - projectIds.map((el) => ({ gatewayId: gateway.id, projectId: el })), - tx - ); - }); - } return gateway; }; @@ -576,20 +562,6 @@ export const gatewayServiceFactory = ({ return gateway; }; - const getProjectGateways = async ({ projectId, projectPermission }: TGetProjectGatewayByIdDTO) => { - await permissionService.getProjectPermission({ - projectId, - actor: projectPermission.type, - actorId: projectPermission.id, - actorOrgId: projectPermission.orgId, - actorAuthMethod: projectPermission.authMethod, - actionProjectType: ActionProjectType.Any - }); - - const gateways = await gatewayDAL.findByProjectId(projectId); - return gateways; - }; - const fnGetGatewayClientTlsByGatewayId = async (gatewayId: string) => { const gateway = await gatewayDAL.findById(gatewayId); if (!gateway) throw new NotFoundError({ message: `Gateway with ID ${gatewayId} not found.` }); @@ -632,17 +604,6 @@ export const gatewayServiceFactory = ({ }; }; - // this has no permission check and used for dynamic secrets directly - // assumes permission check is already done - const fnGetGatewayClientTls = async (projectGatewayId: string) => { - const projectGateway = await projectGatewayDAL.findById(projectGatewayId); - if (!projectGateway) throw new NotFoundError({ message: `Project gateway with ID ${projectGatewayId} not found.` }); - - const gatewayDetails = await fnGetGatewayClientTlsByGatewayId(projectGateway.gatewayId); - - return gatewayDetails; - }; - return { getGatewayRelayDetails, exchangeAllocatedRelayAddress, @@ -650,8 +611,6 @@ export const gatewayServiceFactory = ({ getGatewayById, updateGatewayById, deleteGatewayById, - getProjectGateways, - fnGetGatewayClientTls, fnGetGatewayClientTlsByGatewayId, heartbeat }; diff --git a/backend/src/ee/services/gateway/gateway-types.ts b/backend/src/ee/services/gateway/gateway-types.ts index 220dc7147..823028154 100644 --- a/backend/src/ee/services/gateway/gateway-types.ts +++ b/backend/src/ee/services/gateway/gateway-types.ts @@ -20,7 +20,6 @@ export type TGetGatewayByIdDTO = { export type TUpdateGatewayByIdDTO = { id: string; name?: string; - projectIds?: string[]; orgPermission: OrgServiceActor; }; diff --git a/backend/src/ee/services/gateway/project-gateway-dal.ts b/backend/src/ee/services/gateway/project-gateway-dal.ts deleted file mode 100644 index 44c36f5f6..000000000 --- a/backend/src/ee/services/gateway/project-gateway-dal.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { TDbClient } from "@app/db"; -import { TableName } from "@app/db/schemas"; -import { ormify } from "@app/lib/knex"; - -export type TProjectGatewayDALFactory = ReturnType; - -export const projectGatewayDALFactory = (db: TDbClient) => { - const orm = ormify(db, TableName.ProjectGateway); - return orm; -}; diff --git a/backend/src/ee/services/permission/org-permission.ts b/backend/src/ee/services/permission/org-permission.ts index 7026899c7..612914bcc 100644 --- a/backend/src/ee/services/permission/org-permission.ts +++ b/backend/src/ee/services/permission/org-permission.ts @@ -41,7 +41,8 @@ export enum OrgPermissionGatewayActions { CreateGateways = "create-gateways", ListGateways = "list-gateways", EditGateways = "edit-gateways", - DeleteGateways = "delete-gateways" + DeleteGateways = "delete-gateways", + AttachGateways = "attach-gateways" } export enum OrgPermissionIdentityActions { @@ -337,6 +338,7 @@ const buildAdminPermission = () => { can(OrgPermissionGatewayActions.CreateGateways, OrgPermissionSubjects.Gateway); can(OrgPermissionGatewayActions.EditGateways, OrgPermissionSubjects.Gateway); can(OrgPermissionGatewayActions.DeleteGateways, OrgPermissionSubjects.Gateway); + can(OrgPermissionGatewayActions.AttachGateways, OrgPermissionSubjects.Gateway); can(OrgPermissionAdminConsoleAction.AccessAllProjects, OrgPermissionSubjects.AdminConsole); @@ -378,6 +380,7 @@ const buildMemberPermission = () => { can(OrgPermissionAppConnectionActions.Connect, OrgPermissionSubjects.AppConnections); can(OrgPermissionGatewayActions.ListGateways, OrgPermissionSubjects.Gateway); can(OrgPermissionGatewayActions.CreateGateways, OrgPermissionSubjects.Gateway); + can(OrgPermissionGatewayActions.AttachGateways, OrgPermissionSubjects.Gateway); return rules; }; diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index 6367496f1..43720c40c 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -32,7 +32,6 @@ import { externalKmsServiceFactory } from "@app/ee/services/external-kms/externa import { gatewayDALFactory } from "@app/ee/services/gateway/gateway-dal"; import { gatewayServiceFactory } from "@app/ee/services/gateway/gateway-service"; import { orgGatewayConfigDALFactory } from "@app/ee/services/gateway/org-gateway-config-dal"; -import { projectGatewayDALFactory } from "@app/ee/services/gateway/project-gateway-dal"; import { githubOrgSyncDALFactory } from "@app/ee/services/github-org-sync/github-org-sync-dal"; import { githubOrgSyncServiceFactory } from "@app/ee/services/github-org-sync/github-org-sync-service"; import { groupDALFactory } from "@app/ee/services/group/group-dal"; @@ -434,7 +433,6 @@ export const registerRoutes = async ( const orgGatewayConfigDAL = orgGatewayConfigDALFactory(db); const gatewayDAL = gatewayDALFactory(db); - const projectGatewayDAL = projectGatewayDALFactory(db); const secretReminderRecipientsDAL = secretReminderRecipientsDALFactory(db); const githubOrgSyncDAL = githubOrgSyncDALFactory(db); @@ -1408,8 +1406,7 @@ export const registerRoutes = async ( kmsService, licenseService, orgGatewayConfigDAL, - keyStore, - projectGatewayDAL + keyStore }); const identityKubernetesAuthService = identityKubernetesAuthServiceFactory({ @@ -1419,6 +1416,7 @@ export const registerRoutes = async ( permissionService, licenseService, gatewayService, + gatewayDAL, kmsService }); const identityGcpAuthService = identityGcpAuthServiceFactory({ @@ -1494,7 +1492,7 @@ export const registerRoutes = async ( permissionService, licenseService, kmsService, - projectGatewayDAL, + gatewayDAL, resourceMetadataDAL }); diff --git a/backend/src/services/identity-kubernetes-auth/identity-kubernetes-auth-service.ts b/backend/src/services/identity-kubernetes-auth/identity-kubernetes-auth-service.ts index 2e7950f80..a1005cf1b 100644 --- a/backend/src/services/identity-kubernetes-auth/identity-kubernetes-auth-service.ts +++ b/backend/src/services/identity-kubernetes-auth/identity-kubernetes-auth-service.ts @@ -4,9 +4,14 @@ import https from "https"; import jwt from "jsonwebtoken"; import { IdentityAuthMethod, TIdentityKubernetesAuthsUpdate } from "@app/db/schemas"; +import { TGatewayDALFactory } from "@app/ee/services/gateway/gateway-dal"; import { TGatewayServiceFactory } from "@app/ee/services/gateway/gateway-service"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; -import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; +import { + OrgPermissionGatewayActions, + OrgPermissionIdentityActions, + OrgPermissionSubjects +} from "@app/ee/services/permission/org-permission"; import { constructPermissionErrorMessage, validatePrivilegeChangeOperation @@ -46,6 +51,7 @@ type TIdentityKubernetesAuthServiceFactoryDep = { licenseService: Pick; kmsService: Pick; gatewayService: TGatewayServiceFactory; + gatewayDAL: Pick; }; export type TIdentityKubernetesAuthServiceFactory = ReturnType; @@ -57,6 +63,7 @@ export const identityKubernetesAuthServiceFactory = ({ permissionService, licenseService, gatewayService, + gatewayDAL, kmsService }: TIdentityKubernetesAuthServiceFactoryDep) => { const $gatewayProxyWrapper = async ( @@ -343,6 +350,27 @@ export const identityKubernetesAuthServiceFactory = ({ return extractIPDetails(accessTokenTrustedIp.ipAddress); }); + if (gatewayId) { + const [gateway] = await gatewayDAL.find({ id: gatewayId }); + if (!gateway) { + throw new NotFoundError({ + message: `Gateway with ID ${gatewayId} not found` + }); + } + + const { permission: orgPermission } = await permissionService.getOrgPermission( + actor, + actorId, + identityMembershipOrg.orgId, + actorAuthMethod, + actorOrgId + ); + ForbiddenError.from(orgPermission).throwUnlessCan( + OrgPermissionGatewayActions.AttachGateways, + OrgPermissionSubjects.Gateway + ); + } + const { encryptor } = await kmsService.createCipherPairWithDataKey({ type: KmsDataKey.Organization, orgId: identityMembershipOrg.orgId @@ -438,6 +466,27 @@ export const identityKubernetesAuthServiceFactory = ({ return extractIPDetails(accessTokenTrustedIp.ipAddress); }); + if (gatewayId) { + const [gateway] = await gatewayDAL.find({ id: gatewayId }); + if (!gateway) { + throw new NotFoundError({ + message: `Gateway with ID ${gatewayId} not found` + }); + } + + const { permission: orgPermission } = await permissionService.getOrgPermission( + actor, + actorId, + identityMembershipOrg.orgId, + actorAuthMethod, + actorOrgId + ); + ForbiddenError.from(orgPermission).throwUnlessCan( + OrgPermissionGatewayActions.AttachGateways, + OrgPermissionSubjects.Gateway + ); + } + const updateQuery: TIdentityKubernetesAuthsUpdate = { kubernetesHost, allowedNamespaces, diff --git a/frontend/src/context/OrgPermissionContext/types.ts b/frontend/src/context/OrgPermissionContext/types.ts index 2dbfaacb7..a4bd202bf 100644 --- a/frontend/src/context/OrgPermissionContext/types.ts +++ b/frontend/src/context/OrgPermissionContext/types.ts @@ -12,7 +12,8 @@ export enum OrgGatewayPermissionActions { CreateGateways = "create-gateways", ListGateways = "list-gateways", EditGateways = "edit-gateways", - DeleteGateways = "delete-gateways" + DeleteGateways = "delete-gateways", + AttachGateways = "attach-gateways" } export enum OrgPermissionSubjects { diff --git a/frontend/src/hooks/api/gateways/mutation.tsx b/frontend/src/hooks/api/gateways/mutation.tsx index e93197fdd..ef292cb39 100644 --- a/frontend/src/hooks/api/gateways/mutation.tsx +++ b/frontend/src/hooks/api/gateways/mutation.tsx @@ -20,8 +20,8 @@ export const useDeleteGatewayById = () => { export const useUpdateGatewayById = () => { const queryClient = useQueryClient(); return useMutation({ - mutationFn: ({ id, name, projectIds }: TUpdateGatewayDTO) => { - return apiRequest.patch(`/api/v1/gateways/${id}`, { name, projectIds }); + mutationFn: ({ id, name }: TUpdateGatewayDTO) => { + return apiRequest.patch(`/api/v1/gateways/${id}`, { name }); }, onSuccess: () => { queryClient.invalidateQueries(gatewaysQueryKeys.list()); diff --git a/frontend/src/hooks/api/gateways/queries.tsx b/frontend/src/hooks/api/gateways/queries.tsx index 6ec374a6c..bb05b17a4 100644 --- a/frontend/src/hooks/api/gateways/queries.tsx +++ b/frontend/src/hooks/api/gateways/queries.tsx @@ -2,7 +2,7 @@ import { queryOptions } from "@tanstack/react-query"; import { apiRequest } from "@app/config/request"; -import { TGateway, TListProjectGatewayDTO, TProjectGateway } from "./types"; +import { TGateway } from "./types"; export const gatewaysQueryKeys = { allKey: () => ["gateways"], @@ -14,20 +14,5 @@ export const gatewaysQueryKeys = { const { data } = await apiRequest.get<{ gateways: TGateway[] }>("/api/v1/gateways"); return data.gateways; } - }), - listProjectGatewayKey: ({ projectId }: TListProjectGatewayDTO) => [ - ...gatewaysQueryKeys.allKey(), - "list", - { projectId } - ], - listProjectGateways: ({ projectId }: TListProjectGatewayDTO) => - queryOptions({ - queryKey: gatewaysQueryKeys.listProjectGatewayKey({ projectId }), - queryFn: async () => { - const { data } = await apiRequest.get<{ gateways: TProjectGateway[] }>( - `/api/v1/gateways/projects/${projectId}` - ); - return data.gateways; - } }) }; diff --git a/frontend/src/hooks/api/gateways/types.ts b/frontend/src/hooks/api/gateways/types.ts index a522b6c48..6a3f2d673 100644 --- a/frontend/src/hooks/api/gateways/types.ts +++ b/frontend/src/hooks/api/gateways/types.ts @@ -11,39 +11,13 @@ export type TGateway = { name: string; id: string; }; - projects: { - name: string; - id: string; - slug: string; - }[]; -}; - -export type TProjectGateway = { - id: string; - identityId: string; - name: string; - createdAt: string; - updatedAt: string; - issuedAt: string; - serialNumber: string; - heartbeat: string; - projectGatewayId: string; - identity: { - name: string; - id: string; - }; }; export type TUpdateGatewayDTO = { id: string; name?: string; - projectIds?: string[]; }; export type TDeleteGatewayDTO = { id: string; }; - -export type TListProjectGatewayDTO = { - projectId: string; -}; diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityKubernetesAuthForm.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityKubernetesAuthForm.tsx index d7832fff8..12cd8a6cf 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityKubernetesAuthForm.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityKubernetesAuthForm.tsx @@ -7,6 +7,7 @@ import { useQuery } from "@tanstack/react-query"; import { z } from "zod"; import { createNotification } from "@app/components/notifications"; +import { OrgPermissionCan } from "@app/components/permissions"; import { Button, FormControl, @@ -18,9 +19,14 @@ import { TabList, TabPanel, Tabs, - TextArea + TextArea, + Tooltip } from "@app/components/v2"; import { useOrganization, useSubscription } from "@app/context"; +import { + OrgGatewayPermissionActions, + OrgPermissionSubjects +} from "@app/context/OrgPermissionContext/types"; import { gatewaysQueryKeys, useAddIdentityKubernetesAuth, @@ -103,7 +109,7 @@ export const IdentityKubernetesAuthForm = ({ tokenReviewerJwt: "", allowedNames: "", allowedNamespaces: "", - gatewayId: null, + gatewayId: "", allowedAudience: "", caCert: "", accessTokenTTL: "2592000", @@ -294,38 +300,60 @@ export const IdentityKubernetesAuthForm = ({ )} /> - ( - - - + + {(isAllowed) => ( + ( + + +
+ +
+
+
+ )} + /> )} - /> +
Name Cert Issued At - Projects Identity Health Check @@ -151,13 +149,6 @@ export const GatewayListPage = withPermission( {el.name} {format(new Date(el.issuedAt), "yyyy-MM-dd hh:mm:ss aaa")} - - {el.projects.map((projectDetails) => ( - - {projectDetails.name} - - ))} - {el.identity.name} {el.heartbeat diff --git a/frontend/src/pages/organization/Gateways/GatewayListPage/components/EditGatewayDetailsModal.tsx b/frontend/src/pages/organization/Gateways/GatewayListPage/components/EditGatewayDetailsModal.tsx index d41fd5489..1e07036ba 100644 --- a/frontend/src/pages/organization/Gateways/GatewayListPage/components/EditGatewayDetailsModal.tsx +++ b/frontend/src/pages/organization/Gateways/GatewayListPage/components/EditGatewayDetailsModal.tsx @@ -3,10 +3,9 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; import { createNotification } from "@app/components/notifications"; -import { Button, FilterableSelect, FormControl, Input } from "@app/components/v2"; -import { useGetUserWorkspaces, useUpdateGatewayById } from "@app/hooks/api"; +import { Button, FormControl, Input } from "@app/components/v2"; +import { useUpdateGatewayById } from "@app/hooks/api"; import { TGateway } from "@app/hooks/api/gateways/types"; -import { ProjectType } from "@app/hooks/api/workspace/types"; type Props = { gatewayDetails: TGateway; @@ -14,13 +13,7 @@ type Props = { }; const schema = z.object({ - name: z.string(), - projects: z - .object({ - id: z.string(), - name: z.string() - }) - .array() + name: z.string() }); export type FormData = z.infer; @@ -38,20 +31,13 @@ export const EditGatewayDetailsModal = ({ gatewayDetails, onClose }: Props) => { }); const updateGatewayById = useUpdateGatewayById(); - // when gateway goes to other products switch to all - const { data: secretManagerWorkspaces, isLoading: isSecretManagerLoading } = useGetUserWorkspaces( - { - type: ProjectType.SecretManager - } - ); - const onFormSubmit = ({ name, projects }: FormData) => { + const onFormSubmit = ({ name }: FormData) => { if (isSubmitting) return; updateGatewayById.mutate( { id: gatewayDetails.id, - name, - projectIds: projects.map((el) => el.id) + name }, { onSuccess: () => { @@ -76,30 +62,6 @@ export const EditGatewayDetailsModal = ({ gatewayDetails, onClose }: Props) => { )} /> - ( - - option.id} - getOptionLabel={(option) => option.name} - /> - - )} - />
- ( - - - + +
+ +
+
+ + )} + /> )} - /> +
Service
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretSqlProviderForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretSqlProviderForm.tsx index 7c15db140..3966111ee 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretSqlProviderForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretSqlProviderForm.tsx @@ -6,6 +6,7 @@ import { z } from "zod"; import { TtlFormLabel } from "@app/components/features"; import { createNotification } from "@app/components/notifications"; +import { OrgPermissionCan } from "@app/components/permissions"; import { Accordion, AccordionContent, @@ -17,9 +18,11 @@ import { SecretInput, Select, SelectItem, - TextArea + TextArea, + Tooltip } from "@app/components/v2"; -import { useWorkspace } from "@app/context"; +import { OrgPermissionSubjects } from "@app/context"; +import { OrgGatewayPermissionActions } from "@app/context/OrgPermissionContext/types"; import { gatewaysQueryKeys, useUpdateDynamicSecret } from "@app/hooks/api"; import { SqlProviders, TDynamicSecret } from "@app/hooks/api/dynamicSecret/types"; @@ -60,7 +63,7 @@ const formSchema = z.object({ revocationStatement: z.string().min(1), renewStatement: z.string().optional(), ca: z.string().optional(), - projectGatewayId: z.string().optional().nullable() + gatewayId: z.string().optional().nullable() }) .partial(), defaultTTL: z.string().superRefine((val, ctx) => { @@ -147,15 +150,11 @@ export const EditDynamicSecretSqlProviderForm = ({ } }); - const { currentWorkspace } = useWorkspace(); - const { data: projectGateways, isPending: isProjectGatewaysLoading } = useQuery( - gatewaysQueryKeys.listProjectGateways({ projectId: currentWorkspace.id }) - ); + const { data: gateways, isPending: isGatewaysLoading } = useQuery(gatewaysQueryKeys.list()); const updateDynamicSecret = useUpdateDynamicSecret(); - const selectedProjectGatewayId = watch("inputs.projectGatewayId"); - const isGatewayInActive = - projectGateways?.findIndex((el) => el.projectGatewayId === selectedProjectGatewayId) === -1; + const selectedGatewayId = watch("inputs.gatewayId"); + const isGatewayInActive = gateways?.findIndex((el) => el.id === selectedGatewayId) === -1; const handleUpdateDynamicSecret = async ({ inputs, @@ -177,7 +176,7 @@ export const EditDynamicSecretSqlProviderForm = ({ defaultTTL, inputs: { ...inputs, - projectGatewayId: isGatewayInActive ? null : inputs.projectGatewayId + gatewayId: isGatewayInActive ? null : inputs.gatewayId }, newName: newName === dynamicSecret.name ? undefined : newName, metadata @@ -250,45 +249,60 @@ export const EditDynamicSecretSqlProviderForm = ({
Configuration
- ( - - - + +
+ +
+
+ + )} + /> )} - /> +