feat: integrated with github app connection to gateway and some adjustments

This commit is contained in:
Sheen Capadngan
2025-09-03 18:45:26 +08:00
parent d5dcaf0d59
commit 9a9cdf140a
11 changed files with 277 additions and 106 deletions
+17 -2
View File
@@ -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,
+33 -4
View File
@@ -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;
}
});
};
@@ -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<typeof gatewayV2ServiceFactory>;
@@ -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 {
@@ -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"
});
}