From 9a9cdf140ae3c21a2c06cf5551cbc9d2472d64fb Mon Sep 17 00:00:00 2001 From: Sheen Capadngan Date: Wed, 3 Sep 2025 18:45:26 +0800 Subject: [PATCH] feat: integrated with github app connection to gateway and some adjustments --- backend/src/ee/routes/v1/proxy-router.ts | 19 ++- backend/src/ee/routes/v2/gateway-router.ts | 37 +++++- .../services/gateway-v2/gateway-v2-service.ts | 93 +++++++++---- .../src/ee/services/proxy/proxy-service.ts | 8 +- backend/src/server/routes/index.ts | 36 +++--- .../app-connection/app-connection-service.ts | 2 +- .../github/github-connection-fns.ts | 122 ++++++++++++++---- .../github/github-connection-service.ts | 10 +- .../secret-sync/github/github-sync-fns.ts | 32 +++-- .../services/secret-sync/secret-sync-fns.ts | 10 +- .../services/secret-sync/secret-sync-queue.ts | 14 +- 11 files changed, 277 insertions(+), 106 deletions(-) diff --git a/backend/src/ee/routes/v1/proxy-router.ts b/backend/src/ee/routes/v1/proxy-router.ts index 561fe7780..e837eb624 100644 --- a/backend/src/ee/routes/v1/proxy-router.ts +++ b/backend/src/ee/routes/v1/proxy-router.ts @@ -1,7 +1,7 @@ import { z } from "zod"; import { getConfig } from "@app/lib/config/env"; -import { UnauthorizedError } from "@app/lib/errors"; +import { BadRequestError, UnauthorizedError } from "@app/lib/errors"; import { writeLimit } from "@app/server/config/rateLimiter"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AuthMode } from "@app/services/auth/auth-type"; @@ -21,7 +21,18 @@ export const registerProxyRouter = async (server: FastifyZodProvider) => { name: z.string() }), response: { - 200: z.any() + 200: z.object({ + pki: z.object({ + serverCertificate: z.string(), + serverPrivateKey: z.string(), + clientCertificateChain: z.string() + }), + ssh: z.object({ + serverCertificate: z.string(), + serverPrivateKey: z.string(), + clientCAPublicKey: z.string() + }) + }) } }, onRequest: (req, _, next) => { @@ -59,6 +70,10 @@ export const registerProxyRouter = async (server: FastifyZodProvider) => { }, onRequest: verifyAuth([AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { + throw new BadRequestError({ + message: "Org proxy registration is not yet supported" + }); + return server.services.proxy.registerProxy({ ...req.body, identityId: req.permission.id, diff --git a/backend/src/ee/routes/v2/gateway-router.ts b/backend/src/ee/routes/v2/gateway-router.ts index e7171aff8..114672a23 100644 --- a/backend/src/ee/routes/v2/gateway-router.ts +++ b/backend/src/ee/routes/v2/gateway-router.ts @@ -1,9 +1,19 @@ import z from "zod"; +import { GatewaysV2Schema } from "@app/db/schemas"; import { writeLimit } from "@app/server/config/rateLimiter"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AuthMode } from "@app/services/auth/auth-type"; +const SanitizedGatewayV2Schema = GatewaysV2Schema.pick({ + id: true, + identityId: true, + name: true, + createdAt: true, + updatedAt: true, + heartbeat: true +}); + export const registerGatewayV2Router = async (server: FastifyZodProvider) => { server.route({ method: "POST", @@ -14,7 +24,20 @@ export const registerGatewayV2Router = async (server: FastifyZodProvider) => { name: z.string() }), response: { - 200: z.any() + 200: z.object({ + gatewayId: z.string(), + proxyIp: z.string(), + pki: z.object({ + serverCertificate: z.string(), + serverPrivateKey: z.string(), + clientCertificateChain: z.string() + }), + ssh: z.object({ + clientCertificate: z.string(), + clientPrivateKey: z.string(), + serverCAPublicKey: z.string() + }) + }) } }, onRequest: verifyAuth([AuthMode.IDENTITY_ACCESS_TOKEN]), @@ -23,6 +46,7 @@ export const registerGatewayV2Router = async (server: FastifyZodProvider) => { orgId: req.permission.orgId, proxyName: req.body.proxyName, actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, name: req.body.name }); @@ -58,7 +82,12 @@ export const registerGatewayV2Router = async (server: FastifyZodProvider) => { url: "/", schema: { response: { - 200: z.any() + 200: SanitizedGatewayV2Schema.extend({ + identity: z.object({ + name: z.string(), + id: z.string() + }) + }).array() } }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), @@ -82,7 +111,7 @@ export const registerGatewayV2Router = async (server: FastifyZodProvider) => { id: z.string() }), response: { - 200: z.any() + 200: SanitizedGatewayV2Schema } }, onRequest: verifyAuth([AuthMode.IDENTITY_ACCESS_TOKEN, AuthMode.JWT]), @@ -91,7 +120,7 @@ export const registerGatewayV2Router = async (server: FastifyZodProvider) => { orgPermission: req.permission, id: req.params.id }); - return { gateway }; + return gateway; } }); }; diff --git a/backend/src/ee/services/gateway-v2/gateway-v2-service.ts b/backend/src/ee/services/gateway-v2/gateway-v2-service.ts index f6d56b7e8..e8aede3b5 100644 --- a/backend/src/ee/services/gateway-v2/gateway-v2-service.ts +++ b/backend/src/ee/services/gateway-v2/gateway-v2-service.ts @@ -1,5 +1,6 @@ import net from "node:net"; +import { ForbiddenError } from "@casl/ability"; import * as x509 from "@peculiar/x509"; import { TProxies } from "@app/db/schemas"; @@ -9,7 +10,7 @@ import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { GatewayProxyProtocol } from "@app/lib/gateway/types"; import { withGatewayV2Proxy } from "@app/lib/gateway-v2/gateway-v2"; import { OrgServiceActor } from "@app/lib/types"; -import { ActorType } from "@app/services/auth/auth-type"; +import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type"; import { constructPemChainFromCerts } from "@app/services/certificate/certificate-fns"; import { CertExtendedKeyUsage, CertKeyAlgorithm, CertKeyUsage } from "@app/services/certificate/certificate-types"; import { @@ -20,6 +21,8 @@ import { TKmsServiceFactory } from "@app/services/kms/kms-service"; import { KmsDataKey } from "@app/services/kms/kms-types"; import { TLicenseServiceFactory } from "../license/license-service"; +import { OrgPermissionGatewayActions, OrgPermissionSubjects } from "../permission/org-permission"; +import { TPermissionServiceFactory } from "../permission/permission-service-types"; import { TProxyDALFactory } from "../proxy/proxy-dal"; import { isInstanceProxy } from "../proxy/proxy-fns"; import { TProxyServiceFactory } from "../proxy/proxy-service"; @@ -34,6 +37,7 @@ type TGatewayV2ServiceFactoryDep = { proxyService: TProxyServiceFactory; gatewayV2DAL: TGatewayV2DALFactory; proxyDAL: TProxyDALFactory; + permissionService: TPermissionServiceFactory; }; export type TGatewayV2ServiceFactory = ReturnType; @@ -44,8 +48,32 @@ export const gatewayV2ServiceFactory = ({ kmsService, proxyService, gatewayV2DAL, - proxyDAL + proxyDAL, + permissionService }: TGatewayV2ServiceFactoryDep) => { + const $validateIdentityAccessToGateway = async (orgId: string, actorId: string, actorAuthMethod: ActorAuthMethod) => { + const orgLicensePlan = await licenseService.getPlan(orgId); + if (!orgLicensePlan.gateway) { + throw new BadRequestError({ + message: + "Gateway operation failed due to organization plan restrictions. Please upgrade your instance to Infisical's Enterprise plan." + }); + } + + const { permission } = await permissionService.getOrgPermission( + ActorType.IDENTITY, + actorId, + orgId, + actorAuthMethod, + orgId + ); + + ForbiddenError.from(permission).throwUnlessCan( + OrgPermissionGatewayActions.CreateGateways, + OrgPermissionSubjects.Gateway + ); + }; + const $getOrgCAs = async (orgId: string) => { const { encryptor: orgKmsEncryptor, decryptor: orgKmsDecryptor } = await kmsService.createCipherPairWithDataKey({ type: KmsDataKey.Organization, @@ -217,20 +245,18 @@ export const gatewayV2ServiceFactory = ({ }; const listGateways = async ({ orgPermission }: { orgPermission: OrgServiceActor }) => { - // const { permission } = await permissionService.getOrgPermission( - // orgPermission.type, - // orgPermission.id, - // orgPermission.orgId, - // orgPermission.authMethod, - // orgPermission.orgId - // ); - // ForbiddenError.from(permission).throwUnlessCan( - // OrgPermissionGatewayActions.ListGateways, - // OrgPermissionSubjects.Gateway - // ); + const { permission } = await permissionService.getOrgPermission( + orgPermission.type, + orgPermission.id, + orgPermission.orgId, + orgPermission.authMethod, + orgPermission.orgId + ); - const orgGatewayConfig = await orgGatewayConfigV2DAL.findOne({ orgId: orgPermission.orgId }); - if (!orgGatewayConfig) return []; + ForbiddenError.from(permission).throwUnlessCan( + OrgPermissionGatewayActions.ListGateways, + OrgPermissionSubjects.Gateway + ); const gateways = await gatewayV2DAL.find({ orgId: orgPermission.orgId @@ -389,14 +415,17 @@ export const gatewayV2ServiceFactory = ({ const registerGateway = async ({ orgId, actorId, + actorAuthMethod, proxyName, name }: { orgId: string; actorId: string; + actorAuthMethod: ActorAuthMethod; proxyName: string; name: string; }) => { + await $validateIdentityAccessToGateway(orgId, actorId, actorAuthMethod); const orgCAs = await $getOrgCAs(orgId); let proxy: TProxies; @@ -407,7 +436,7 @@ export const gatewayV2ServiceFactory = ({ } if (!proxy) { - throw new Error("Proxy not found"); + throw new NotFoundError({ message: `Proxy ${proxyName} not found` }); } const [gateway] = await gatewayV2DAL.upsert( @@ -499,6 +528,8 @@ export const gatewayV2ServiceFactory = ({ }; const heartbeat = async ({ orgPermission }: { orgPermission: OrgServiceActor }) => { + await $validateIdentityAccessToGateway(orgPermission.orgId, orgPermission.id, orgPermission.authMethod); + const gateway = await gatewayV2DAL.findOne({ orgId: orgPermission.orgId, identityId: orgPermission.id @@ -587,19 +618,25 @@ export const gatewayV2ServiceFactory = ({ }; const deleteGatewayById = async ({ orgPermission, id }: { orgPermission: OrgServiceActor; id: string }) => { - // const { permission } = await permissionService.getOrgPermission( - // orgPermission.type, - // orgPermission.id, - // orgPermission.orgId, - // orgPermission.authMethod, - // orgPermission.orgId - // ); - // ForbiddenError.from(permission).throwUnlessCan( - // OrgPermissionGatewayActions.DeleteGateways, - // OrgPermissionSubjects.Gateway - // ); + const gateway = await gatewayV2DAL.findOne({ id, orgId: orgPermission.orgId }); + if (!gateway) { + throw new NotFoundError({ message: `Gateway ${id} not found` }); + } - return gatewayV2DAL.deleteById(id); + const { permission } = await permissionService.getOrgPermission( + orgPermission.type, + orgPermission.id, + gateway.orgId, + orgPermission.authMethod, + orgPermission.orgId + ); + + ForbiddenError.from(permission).throwUnlessCan( + OrgPermissionGatewayActions.DeleteGateways, + OrgPermissionSubjects.Gateway + ); + + return gatewayV2DAL.deleteById(gateway.id); }; return { diff --git a/backend/src/ee/services/proxy/proxy-service.ts b/backend/src/ee/services/proxy/proxy-service.ts index 96ecf3574..37b9ab7e3 100644 --- a/backend/src/ee/services/proxy/proxy-service.ts +++ b/backend/src/ee/services/proxy/proxy-service.ts @@ -799,7 +799,7 @@ export const proxyServiceFactory = ({ const proxyClientSshCert = await createSshCert({ caPrivateKey: instanceCAs.instanceProxySshServerCaPrivateKey.toString("utf8"), clientPublicKey: proxyClientSshPublicKey, - keyId: `proxy-client-${proxy.id}`, + keyId: `client-${proxyName}`, principals: [gatewayId], certType: SshCertType.USER, requestedTtl: "30d" @@ -898,7 +898,6 @@ export const proxyServiceFactory = ({ const isOrgProxy = identityId && orgId; if (isOrgProxy) { - // organization proxy if (isInstanceProxy(name)) { throw new BadRequestError({ message: "Org proxy name cannot start with 'infisical-'. This is reserved for internal use." @@ -935,8 +934,7 @@ export const proxyServiceFactory = ({ return existingProxy; }); } else { - // instance proxy - if (!name.startsWith("infisical-")) { + if (!isInstanceProxy(name)) { throw new BadRequestError({ message: "Instance proxy name must start with 'infisical-'." }); @@ -952,7 +950,7 @@ export const proxyServiceFactory = ({ if (existingProxy && existingProxy.ip !== ip) { throw new BadRequestError({ - message: "Instance proxy with this name already exists" + message: "Instance proxy with this name already exists with a different IP address" }); } diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index 2bded6dbc..b38cb5e13 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -1071,6 +1071,23 @@ export const registerRoutes = async ( keyStore }); + const proxyService = proxyServiceFactory({ + instanceProxyConfigDAL, + orgProxyConfigDAL, + proxyDAL, + kmsService + }); + + const gatewayV2Service = gatewayV2ServiceFactory({ + kmsService, + licenseService, + proxyService, + orgGatewayConfigV2DAL, + gatewayV2DAL, + proxyDAL, + permissionService + }); + const secretSyncQueue = secretSyncQueueFactory({ queueService, secretSyncDAL, @@ -1095,7 +1112,8 @@ export const registerRoutes = async ( resourceMetadataDAL, appConnectionDAL, licenseService, - gatewayService + gatewayService, + gatewayV2Service }); const secretQueueService = secretQueueFactory({ @@ -1463,22 +1481,6 @@ export const registerRoutes = async ( smtpService }); - const proxyService = proxyServiceFactory({ - instanceProxyConfigDAL, - orgProxyConfigDAL, - proxyDAL, - kmsService - }); - - const gatewayV2Service = gatewayV2ServiceFactory({ - kmsService, - licenseService, - proxyService, - orgGatewayConfigV2DAL, - gatewayV2DAL, - proxyDAL - }); - const identityService = identityServiceFactory({ permissionService, identityDAL, diff --git a/backend/src/services/app-connection/app-connection-service.ts b/backend/src/services/app-connection/app-connection-service.ts index 73563da79..a40d0f5bd 100644 --- a/backend/src/services/app-connection/app-connection-service.ts +++ b/backend/src/services/app-connection/app-connection-service.ts @@ -597,7 +597,7 @@ export const appConnectionServiceFactory = ({ deleteAppConnection, connectAppConnectionById, listAvailableAppConnectionsForUser, - github: githubConnectionService(connectAppConnectionById, gatewayService), + github: githubConnectionService(connectAppConnectionById, gatewayService, gatewayV2Service), githubRadar: githubRadarConnectionService(connectAppConnectionById), gcp: gcpConnectionService(connectAppConnectionById), databricks: databricksConnectionService(connectAppConnectionById, appConnectionDAL, kmsService), diff --git a/backend/src/services/app-connection/github/github-connection-fns.ts b/backend/src/services/app-connection/github/github-connection-fns.ts index a71036d82..ef7a9cfd6 100644 --- a/backend/src/services/app-connection/github/github-connection-fns.ts +++ b/backend/src/services/app-connection/github/github-connection-fns.ts @@ -6,10 +6,12 @@ import RE2 from "re2"; import { verifyHostInputValidity } from "@app/ee/services/dynamic-secret/dynamic-secret-fns"; import { TGatewayServiceFactory } from "@app/ee/services/gateway/gateway-service"; +import { TGatewayV2ServiceFactory } from "@app/ee/services/gateway-v2/gateway-v2-service"; import { getConfig } from "@app/lib/config/env"; import { request as httpRequest } from "@app/lib/config/request"; import { BadRequestError, ForbiddenRequestError, InternalServerError } from "@app/lib/errors"; import { GatewayProxyProtocol, withGatewayProxy } from "@app/lib/gateway"; +import { withGatewayV2Proxy } from "@app/lib/gateway-v2/gateway-v2"; import { logger } from "@app/lib/logger"; import { blockLocalAndPrivateIpAddresses } from "@app/lib/validator"; import { getAppConnectionMethodName } from "@app/services/app-connection/app-connection-fns"; @@ -50,6 +52,7 @@ export const getGitHubInstanceApiUrl = async (config: { export const requestWithGitHubGateway = async ( appConnection: { gatewayId?: string | null }, gatewayService: Pick, + gatewayV2Service: Pick, requestConfig: AxiosRequestConfig ): Promise> => { const { gatewayId } = appConnection; @@ -64,6 +67,52 @@ export const requestWithGitHubGateway = async ( await blockLocalAndPrivateIpAddresses(url.toString()); const [targetHost] = await verifyHostInputValidity(url.host, true); + const gatewayConnectionDetails = await gatewayV2Service.getPlatformConnectionDetailsByGatewayId({ + gatewayId, + targetHost, + targetPort: 443 + }); + + if (gatewayConnectionDetails) { + return withGatewayV2Proxy( + async (proxyPort) => { + const httpsAgent = new https.Agent({ + servername: targetHost + }); + + url.protocol = "https:"; + url.host = `localhost:${proxyPort}`; + + const finalRequestConfig: AxiosRequestConfig = { + ...requestConfig, + url: url.toString(), + httpsAgent, + headers: { + ...requestConfig.headers, + Host: targetHost + } + }; + + try { + return await httpRequest.request(finalRequestConfig); + } catch (error) { + const axiosError = error as AxiosError; + logger.error( + { message: axiosError.message, data: axiosError.response?.data }, + "Error during GitHub gateway request:" + ); + throw error; + } + }, + { + protocol: GatewayProxyProtocol.Tcp, + proxyIp: gatewayConnectionDetails.proxyIp, + gateway: gatewayConnectionDetails.gateway, + proxy: gatewayConnectionDetails.proxy + } + ); + } + const relayDetails = await gatewayService.fnGetGatewayClientTlsByGatewayId(gatewayId); const [relayHost, relayPort] = relayDetails.relayAddress.split(":"); @@ -168,6 +217,7 @@ function extractNextPageUrl(linkHeader: string | undefined): string | null { export const makePaginatedGitHubRequest = async ( appConnection: TGitHubConnection, gatewayService: Pick, + gatewayV2Service: Pick, path: string, dataMapper?: (data: R) => T[] ): Promise => { @@ -184,15 +234,20 @@ export const makePaginatedGitHubRequest = async ( const maxIterations = 1000; // Make initial request to get link header - const firstResponse: AxiosResponse = await requestWithGitHubGateway(appConnection, gatewayService, { - url: initialUrlObj.toString(), - method: "GET", - headers: { - Accept: "application/vnd.github+json", - Authorization: `Bearer ${token}`, - "X-GitHub-Api-Version": "2022-11-28" + const firstResponse: AxiosResponse = await requestWithGitHubGateway( + appConnection, + gatewayService, + gatewayV2Service, + { + url: initialUrlObj.toString(), + method: "GET", + headers: { + Accept: "application/vnd.github+json", + Authorization: `Bearer ${token}`, + "X-GitHub-Api-Version": "2022-11-28" + } } - }); + ); const firstPageItems = dataMapper ? dataMapper(firstResponse.data) : (firstResponse.data as unknown as T[]); results = results.concat(firstPageItems); @@ -212,7 +267,7 @@ export const makePaginatedGitHubRequest = async ( pageUrlObj.searchParams.set("page", pageNum.toString()); pageRequests.push( - requestWithGitHubGateway(appConnection, gatewayService, { + requestWithGitHubGateway(appConnection, gatewayService, gatewayV2Service, { url: pageUrlObj.toString(), method: "GET", headers: { @@ -236,15 +291,20 @@ export const makePaginatedGitHubRequest = async ( while (url && i < maxIterations) { // eslint-disable-next-line no-await-in-loop - const response: AxiosResponse = await requestWithGitHubGateway(appConnection, gatewayService, { - url, - method: "GET", - headers: { - Accept: "application/vnd.github+json", - Authorization: `Bearer ${token}`, - "X-GitHub-Api-Version": "2022-11-28" + const response: AxiosResponse = await requestWithGitHubGateway( + appConnection, + gatewayService, + gatewayV2Service, + { + url, + method: "GET", + headers: { + Accept: "application/vnd.github+json", + Authorization: `Bearer ${token}`, + "X-GitHub-Api-Version": "2022-11-28" + } } - }); + ); const items = dataMapper ? dataMapper(response.data) : (response.data as unknown as T[]); results = results.concat(items); @@ -283,30 +343,39 @@ type GitHubEnvironment = { export const getGitHubRepositories = async ( appConnection: TGitHubConnection, - gatewayService: Pick + gatewayService: Pick, + gatewayV2Service: Pick ) => { if (appConnection.method === GitHubConnectionMethod.App) { return makePaginatedGitHubRequest( appConnection, gatewayService, + gatewayV2Service, "/installation/repositories", (data) => data.repositories ); } - const repos = await makePaginatedGitHubRequest(appConnection, gatewayService, "/user/repos"); + const repos = await makePaginatedGitHubRequest( + appConnection, + gatewayService, + gatewayV2Service, + "/user/repos" + ); + return repos.filter((repo) => repo.permissions?.admin); }; export const getGitHubOrganizations = async ( appConnection: TGitHubConnection, - gatewayService: Pick + gatewayService: Pick, + gatewayV2Service: Pick ) => { if (appConnection.method === GitHubConnectionMethod.App) { const installationRepositories = await makePaginatedGitHubRequest< GitHubRepository, { repositories: GitHubRepository[] } - >(appConnection, gatewayService, "/installation/repositories", (data) => data.repositories); + >(appConnection, gatewayService, gatewayV2Service, "/installation/repositories", (data) => data.repositories); const organizationMap: Record = {}; installationRepositories.forEach((repo) => { @@ -318,12 +387,13 @@ export const getGitHubOrganizations = async ( return Object.values(organizationMap); } - return makePaginatedGitHubRequest(appConnection, gatewayService, "/user/orgs"); + return makePaginatedGitHubRequest(appConnection, gatewayService, gatewayV2Service, "/user/orgs"); }; export const getGitHubEnvironments = async ( appConnection: TGitHubConnection, gatewayService: Pick, + gatewayV2Service: Pick, owner: string, repo: string ) => { @@ -331,6 +401,7 @@ export const getGitHubEnvironments = async ( return await makePaginatedGitHubRequest( appConnection, gatewayService, + gatewayV2Service, `/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/environments`, (data) => data.environments ); @@ -358,7 +429,8 @@ export function isGithubErrorResponse(data: GithubTokenRespData): data is Github export const validateGitHubConnectionCredentials = async ( config: TGitHubConnectionConfig, - gatewayService: Pick + gatewayService: Pick, + gatewayV2Service: Pick ) => { const { credentials, method } = config; const { @@ -394,7 +466,7 @@ export const validateGitHubConnectionCredentials = async ( const host = credentials.host || "github.com"; try { - tokenResp = await requestWithGitHubGateway(config, gatewayService, { + tokenResp = await requestWithGitHubGateway(config, gatewayService, gatewayV2Service, { url: `https://${host}/login/oauth/access_token`, method: "POST", data: { @@ -446,7 +518,7 @@ export const validateGitHubConnectionCredentials = async ( id: number; }; }[]; - }>(config, gatewayService, { + }>(config, gatewayService, gatewayV2Service, { url: `https://${await getGitHubInstanceApiUrl(config)}/user/installations`, headers: { Accept: "application/json", diff --git a/backend/src/services/app-connection/github/github-connection-service.ts b/backend/src/services/app-connection/github/github-connection-service.ts index f1198ddfa..8292d94e0 100644 --- a/backend/src/services/app-connection/github/github-connection-service.ts +++ b/backend/src/services/app-connection/github/github-connection-service.ts @@ -1,4 +1,5 @@ import { TGatewayServiceFactory } from "@app/ee/services/gateway/gateway-service"; +import { TGatewayV2ServiceFactory } from "@app/ee/services/gateway-v2/gateway-v2-service"; import { OrgServiceActor } from "@app/lib/types"; import { AppConnection } from "@app/services/app-connection/app-connection-enums"; import { @@ -22,12 +23,13 @@ type TListGitHubEnvironmentsDTO = { export const githubConnectionService = ( getAppConnection: TGetAppConnectionFunc, - gatewayService: Pick + gatewayService: Pick, + gatewayV2Service: Pick ) => { const listRepositories = async (connectionId: string, actor: OrgServiceActor) => { const appConnection = await getAppConnection(AppConnection.GitHub, connectionId, actor); - const repositories = await getGitHubRepositories(appConnection, gatewayService); + const repositories = await getGitHubRepositories(appConnection, gatewayService, gatewayV2Service); return repositories; }; @@ -35,7 +37,7 @@ export const githubConnectionService = ( const listOrganizations = async (connectionId: string, actor: OrgServiceActor) => { const appConnection = await getAppConnection(AppConnection.GitHub, connectionId, actor); - const organizations = await getGitHubOrganizations(appConnection, gatewayService); + const organizations = await getGitHubOrganizations(appConnection, gatewayService, gatewayV2Service); return organizations; }; @@ -46,7 +48,7 @@ export const githubConnectionService = ( ) => { const appConnection = await getAppConnection(AppConnection.GitHub, connectionId, actor); - const environments = await getGitHubEnvironments(appConnection, gatewayService, owner, repo); + const environments = await getGitHubEnvironments(appConnection, gatewayService, gatewayV2Service, owner, repo); return environments; }; diff --git a/backend/src/services/secret-sync/github/github-sync-fns.ts b/backend/src/services/secret-sync/github/github-sync-fns.ts index e2cf8f6e8..e41763474 100644 --- a/backend/src/services/secret-sync/github/github-sync-fns.ts +++ b/backend/src/services/secret-sync/github/github-sync-fns.ts @@ -1,6 +1,7 @@ import sodium from "libsodium-wrappers"; import { TGatewayServiceFactory } from "@app/ee/services/gateway/gateway-service"; +import { TGatewayV2ServiceFactory } from "@app/ee/services/gateway-v2/gateway-v2-service"; import { getGitHubAppAuthToken, getGitHubInstanceApiUrl, @@ -20,7 +21,8 @@ import { TGitHubPublicKey, TGitHubSecret, TGitHubSecretPayload, TGitHubSyncWithC const getEncryptedSecrets = async ( secretSync: TGitHubSyncWithCredentials, - gatewayService: Pick + gatewayService: Pick, + gatewayV2Service: Pick ) => { const { destinationConfig, connection } = secretSync; @@ -44,6 +46,7 @@ const getEncryptedSecrets = async ( return makePaginatedGitHubRequest( connection, gatewayService, + gatewayV2Service, path, (data) => data.secrets ); @@ -52,6 +55,7 @@ const getEncryptedSecrets = async ( const getPublicKey = async ( secretSync: TGitHubSyncWithCredentials, gatewayService: Pick, + gatewayV2Service: Pick, token: string ) => { const { destinationConfig, connection } = secretSync; @@ -73,7 +77,7 @@ const getPublicKey = async ( } } - const response = await requestWithGitHubGateway(connection, gatewayService, { + const response = await requestWithGitHubGateway(connection, gatewayService, gatewayV2Service, { url: `https://${await getGitHubInstanceApiUrl(connection)}${path}`, method: "GET", headers: { @@ -89,6 +93,7 @@ const getPublicKey = async ( const deleteSecret = async ( secretSync: TGitHubSyncWithCredentials, gatewayService: Pick, + gatewayV2Service: Pick, token: string, encryptedSecret: TGitHubSecret ) => { @@ -111,7 +116,7 @@ const deleteSecret = async ( } } - await requestWithGitHubGateway(connection, gatewayService, { + await requestWithGitHubGateway(connection, gatewayService, gatewayV2Service, { url: `https://${await getGitHubInstanceApiUrl(connection)}${path}`, method: "DELETE", headers: { @@ -125,6 +130,7 @@ const deleteSecret = async ( const putSecret = async ( secretSync: TGitHubSyncWithCredentials, gatewayService: Pick, + gatewayV2Service: Pick, token: string, payload: TGitHubSecretPayload ) => { @@ -157,7 +163,7 @@ const putSecret = async ( } } - await requestWithGitHubGateway(connection, gatewayService, { + await requestWithGitHubGateway(connection, gatewayService, gatewayV2Service, { url: `https://${await getGitHubInstanceApiUrl(connection)}${path}`, method: "PUT", headers: { @@ -173,7 +179,8 @@ export const GithubSyncFns = { syncSecrets: async ( secretSync: TGitHubSyncWithCredentials, ogSecretMap: TSecretMap, - gatewayService: Pick + gatewayService: Pick, + gatewayV2Service: Pick ) => { const secretMap = Object.fromEntries(Object.entries(ogSecretMap).map(([i, v]) => [i.toUpperCase(), v])); @@ -209,8 +216,8 @@ export const GithubSyncFns = { ? connection.credentials.accessToken : await getGitHubAppAuthToken(connection); - const encryptedSecrets = await getEncryptedSecrets(secretSync, gatewayService); - const publicKey = await getPublicKey(secretSync, gatewayService, token); + const encryptedSecrets = await getEncryptedSecrets(secretSync, gatewayService, gatewayV2Service); + const publicKey = await getPublicKey(secretSync, gatewayService, gatewayV2Service, token); await sodium.ready; for await (const key of Object.keys(secretMap)) { @@ -225,7 +232,7 @@ export const GithubSyncFns = { const encryptedSecretValue = sodium.to_base64(encryptedBytes, sodium.base64_variants.ORIGINAL); try { - await putSecret(secretSync, gatewayService, token, { + await putSecret(secretSync, gatewayService, gatewayV2Service, token, { secret_name: key, encrypted_value: encryptedSecretValue, key_id: publicKey.key_id @@ -246,7 +253,7 @@ export const GithubSyncFns = { continue; if (!(encryptedSecret.name in secretMap)) { - await deleteSecret(secretSync, gatewayService, token, encryptedSecret); + await deleteSecret(secretSync, gatewayService, gatewayV2Service, token, encryptedSecret); } } }, @@ -256,7 +263,8 @@ export const GithubSyncFns = { removeSecrets: async ( secretSync: TGitHubSyncWithCredentials, ogSecretMap: TSecretMap, - gatewayService: Pick + gatewayService: Pick, + gatewayV2Service: Pick ) => { const secretMap = Object.fromEntries(Object.entries(ogSecretMap).map(([i, v]) => [i.toUpperCase(), v])); @@ -266,11 +274,11 @@ export const GithubSyncFns = { ? connection.credentials.accessToken : await getGitHubAppAuthToken(connection); - const encryptedSecrets = await getEncryptedSecrets(secretSync, gatewayService); + const encryptedSecrets = await getEncryptedSecrets(secretSync, gatewayService, gatewayV2Service); for await (const encryptedSecret of encryptedSecrets) { if (encryptedSecret.name in secretMap) { - await deleteSecret(secretSync, gatewayService, token, encryptedSecret); + await deleteSecret(secretSync, gatewayService, gatewayV2Service, token, encryptedSecret); } } } diff --git a/backend/src/services/secret-sync/secret-sync-fns.ts b/backend/src/services/secret-sync/secret-sync-fns.ts index 3ff7cefbc..bbaa4577a 100644 --- a/backend/src/services/secret-sync/secret-sync-fns.ts +++ b/backend/src/services/secret-sync/secret-sync-fns.ts @@ -2,6 +2,7 @@ import { AxiosError } from "axios"; import handlebars from "handlebars"; import { TGatewayServiceFactory } from "@app/ee/services/gateway/gateway-service"; +import { TGatewayV2ServiceFactory } from "@app/ee/services/gateway-v2/gateway-v2-service"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { OCI_VAULT_SYNC_LIST_OPTION, OCIVaultSyncFns } from "@app/ee/services/secret-sync/oci-vault"; import { BadRequestError } from "@app/lib/errors"; @@ -101,6 +102,7 @@ type TSyncSecretDeps = { appConnectionDAL: Pick; kmsService: Pick; gatewayService: Pick; + gatewayV2Service: Pick; }; // Add schema to secret keys @@ -195,7 +197,7 @@ export const SecretSyncFns = { syncSecrets: ( secretSync: TSecretSyncWithCredentials, secretMap: TSecretMap, - { kmsService, appConnectionDAL, gatewayService }: TSyncSecretDeps + { kmsService, appConnectionDAL, gatewayService, gatewayV2Service }: TSyncSecretDeps ): Promise => { const schemaSecretMap = addSchema(secretMap, secretSync.environment?.slug || "", secretSync.syncOptions.keySchema); @@ -205,7 +207,7 @@ export const SecretSyncFns = { case SecretSync.AWSSecretsManager: return AwsSecretsManagerSyncFns.syncSecrets(secretSync, schemaSecretMap); case SecretSync.GitHub: - return GithubSyncFns.syncSecrets(secretSync, schemaSecretMap, gatewayService); + return GithubSyncFns.syncSecrets(secretSync, schemaSecretMap, gatewayService, gatewayV2Service); case SecretSync.GCPSecretManager: return GcpSyncFns.syncSecrets(secretSync, schemaSecretMap); case SecretSync.AzureKeyVault: @@ -404,7 +406,7 @@ export const SecretSyncFns = { removeSecrets: ( secretSync: TSecretSyncWithCredentials, secretMap: TSecretMap, - { kmsService, appConnectionDAL, gatewayService }: TSyncSecretDeps + { kmsService, appConnectionDAL, gatewayService, gatewayV2Service }: TSyncSecretDeps ): Promise => { const schemaSecretMap = addSchema(secretMap, secretSync.environment?.slug || "", secretSync.syncOptions.keySchema); @@ -414,7 +416,7 @@ export const SecretSyncFns = { case SecretSync.AWSSecretsManager: return AwsSecretsManagerSyncFns.removeSecrets(secretSync, schemaSecretMap); case SecretSync.GitHub: - return GithubSyncFns.removeSecrets(secretSync, schemaSecretMap, gatewayService); + return GithubSyncFns.removeSecrets(secretSync, schemaSecretMap, gatewayService, gatewayV2Service); case SecretSync.GCPSecretManager: return GcpSyncFns.removeSecrets(secretSync, schemaSecretMap); case SecretSync.AzureKeyVault: diff --git a/backend/src/services/secret-sync/secret-sync-queue.ts b/backend/src/services/secret-sync/secret-sync-queue.ts index 7bef7d8c7..9b6ea6b0b 100644 --- a/backend/src/services/secret-sync/secret-sync-queue.ts +++ b/backend/src/services/secret-sync/secret-sync-queue.ts @@ -5,6 +5,7 @@ import { Job } from "bullmq"; import { ProjectMembershipRole, SecretType } from "@app/db/schemas"; import { EventType, TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-types"; import { TGatewayServiceFactory } from "@app/ee/services/gateway/gateway-service"; +import { TGatewayV2ServiceFactory } from "@app/ee/services/gateway-v2/gateway-v2-service"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { KeyStorePrefixes, TKeyStoreFactory } from "@app/keystore/keystore"; import { getConfig } from "@app/lib/config/env"; @@ -98,6 +99,7 @@ type TSecretSyncQueueFactoryDep = { folderCommitService: Pick; licenseService: Pick; gatewayService: Pick; + gatewayV2Service: Pick; }; type SecretSyncActionJob = Job< @@ -141,7 +143,8 @@ export const secretSyncQueueFactory = ({ resourceMetadataDAL, folderCommitService, licenseService, - gatewayService + gatewayService, + gatewayV2Service }: TSecretSyncQueueFactoryDep) => { const appCfg = getConfig(); @@ -357,7 +360,8 @@ export const secretSyncQueueFactory = ({ const importedSecrets = await SecretSyncFns.getSecrets(secretSync, { appConnectionDAL, kmsService, - gatewayService + gatewayService, + gatewayV2Service }); if (!Object.keys(importedSecrets).length) return {}; @@ -486,7 +490,8 @@ export const secretSyncQueueFactory = ({ await SecretSyncFns.syncSecrets(secretSyncWithCredentials, secretMap, { appConnectionDAL, kmsService, - gatewayService + gatewayService, + gatewayV2Service }); isSynced = true; @@ -736,7 +741,8 @@ export const secretSyncQueueFactory = ({ { appConnectionDAL, kmsService, - gatewayService + gatewayService, + gatewayV2Service } );