From 4f7b0eaa0d6551a7a0fbffa6d8bcdc27d2deea26 Mon Sep 17 00:00:00 2001 From: Sheen Capadngan Date: Thu, 4 Sep 2025 19:16:50 +0800 Subject: [PATCH] misc: added proper handling for gateway name conflicts --- .../services/gateway-v2/gateway-v2-service.ts | 179 +++++++++--------- 1 file changed, 94 insertions(+), 85 deletions(-) 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 e8aede3b5..8fb53fa20 100644 --- a/backend/src/ee/services/gateway-v2/gateway-v2-service.ts +++ b/backend/src/ee/services/gateway-v2/gateway-v2-service.ts @@ -6,7 +6,8 @@ import * as x509 from "@peculiar/x509"; import { TProxies } from "@app/db/schemas"; import { PgSqlLock } from "@app/keystore/keystore"; import { crypto } from "@app/lib/crypto"; -import { BadRequestError, NotFoundError } from "@app/lib/errors"; +import { DatabaseErrorCode } from "@app/lib/error-codes"; +import { BadRequestError, DatabaseError, 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"; @@ -439,92 +440,100 @@ export const gatewayV2ServiceFactory = ({ throw new NotFoundError({ message: `Proxy ${proxyName} not found` }); } - const [gateway] = await gatewayV2DAL.upsert( - [ - { - orgId, - name, - identityId: actorId, - proxyId: proxy.id + try { + const [gateway] = await gatewayV2DAL.upsert( + [ + { + orgId, + name, + identityId: actorId, + proxyId: proxy.id + } + ], + ["identityId"] + ); + + const alg = keyAlgorithmToAlgCfg(CertKeyAlgorithm.RSA_2048); + const gatewayServerCaCert = new x509.X509Certificate(orgCAs.gatewayServerCaCertificate); + const rootGatewayCaCert = new x509.X509Certificate(orgCAs.rootGatewayCaCertificate); + const gatewayClientCaCert = new x509.X509Certificate(orgCAs.gatewayClientCaCertificate); + + const gatewayServerCaSkObj = crypto.nativeCrypto.createPrivateKey({ + key: orgCAs.gatewayServerCaPrivateKey, + format: "der", + type: "pkcs8" + }); + const gatewayServerCaPrivateKey = await crypto.nativeCrypto.subtle.importKey( + "pkcs8", + gatewayServerCaSkObj.export({ format: "der", type: "pkcs8" }), + alg, + true, + ["sign"] + ); + + const gatewayServerKeys = await crypto.nativeCrypto.subtle.generateKey(alg, true, ["sign", "verify"]); + const gatewayServerCertIssuedAt = new Date(); + const gatewayServerCertExpireAt = new Date(new Date().setMonth(new Date().getMonth() + 1)); + const gatewayServerCertPrivateKey = crypto.nativeCrypto.KeyObject.from(gatewayServerKeys.privateKey); + + const gatewayServerCertExtensions: x509.Extension[] = [ + new x509.BasicConstraintsExtension(false), + await x509.AuthorityKeyIdentifierExtension.create(gatewayServerCaCert, false), + await x509.SubjectKeyIdentifierExtension.create(gatewayServerKeys.publicKey), + new x509.CertificatePolicyExtension(["2.5.29.32.0"]), // anyPolicy + new x509.KeyUsagesExtension( + // eslint-disable-next-line no-bitwise + x509.KeyUsageFlags[CertKeyUsage.DIGITAL_SIGNATURE] | x509.KeyUsageFlags[CertKeyUsage.KEY_ENCIPHERMENT], + true + ), + new x509.ExtendedKeyUsageExtension([x509.ExtendedKeyUsage[CertExtendedKeyUsage.SERVER_AUTH]], true), + new x509.SubjectAlternativeNameExtension([ + { type: "dns", value: "localhost" }, + { type: "ip", value: "127.0.0.1" }, + { type: "ip", value: "::1" } + ]) + ]; + + const gatewayServerSerialNumber = createSerialNumber(); + const gatewayServerCertificate = await x509.X509CertificateGenerator.create({ + serialNumber: gatewayServerSerialNumber, + subject: `O=${orgId},CN=Gateway`, + issuer: gatewayServerCaCert.subject, + notBefore: gatewayServerCertIssuedAt, + notAfter: gatewayServerCertExpireAt, + signingKey: gatewayServerCaPrivateKey, + publicKey: gatewayServerKeys.publicKey, + signingAlgorithm: alg, + extensions: gatewayServerCertExtensions + }); + + const proxyCredentials = await proxyService.getCredentialsForGateway({ + proxyName, + orgId, + gatewayId: gateway.id + }); + + return { + gatewayId: gateway.id, + proxyIp: proxyCredentials.proxyIp, + pki: { + serverCertificate: gatewayServerCertificate.toString("pem"), + serverPrivateKey: gatewayServerCertPrivateKey.export({ format: "pem", type: "pkcs8" }).toString(), + clientCertificateChain: constructPemChainFromCerts([gatewayClientCaCert, rootGatewayCaCert]) + }, + ssh: { + clientCertificate: proxyCredentials.clientSshCert, + clientPrivateKey: proxyCredentials.clientSshPrivateKey, + serverCAPublicKey: proxyCredentials.serverCAPublicKey } - ], - ["identityId"] - ); - - const alg = keyAlgorithmToAlgCfg(CertKeyAlgorithm.RSA_2048); - const gatewayServerCaCert = new x509.X509Certificate(orgCAs.gatewayServerCaCertificate); - const rootGatewayCaCert = new x509.X509Certificate(orgCAs.rootGatewayCaCertificate); - const gatewayClientCaCert = new x509.X509Certificate(orgCAs.gatewayClientCaCertificate); - - const gatewayServerCaSkObj = crypto.nativeCrypto.createPrivateKey({ - key: orgCAs.gatewayServerCaPrivateKey, - format: "der", - type: "pkcs8" - }); - const gatewayServerCaPrivateKey = await crypto.nativeCrypto.subtle.importKey( - "pkcs8", - gatewayServerCaSkObj.export({ format: "der", type: "pkcs8" }), - alg, - true, - ["sign"] - ); - - const gatewayServerKeys = await crypto.nativeCrypto.subtle.generateKey(alg, true, ["sign", "verify"]); - const gatewayServerCertIssuedAt = new Date(); - const gatewayServerCertExpireAt = new Date(new Date().setMonth(new Date().getMonth() + 1)); - const gatewayServerCertPrivateKey = crypto.nativeCrypto.KeyObject.from(gatewayServerKeys.privateKey); - - const gatewayServerCertExtensions: x509.Extension[] = [ - new x509.BasicConstraintsExtension(false), - await x509.AuthorityKeyIdentifierExtension.create(gatewayServerCaCert, false), - await x509.SubjectKeyIdentifierExtension.create(gatewayServerKeys.publicKey), - new x509.CertificatePolicyExtension(["2.5.29.32.0"]), // anyPolicy - new x509.KeyUsagesExtension( - // eslint-disable-next-line no-bitwise - x509.KeyUsageFlags[CertKeyUsage.DIGITAL_SIGNATURE] | x509.KeyUsageFlags[CertKeyUsage.KEY_ENCIPHERMENT], - true - ), - new x509.ExtendedKeyUsageExtension([x509.ExtendedKeyUsage[CertExtendedKeyUsage.SERVER_AUTH]], true), - new x509.SubjectAlternativeNameExtension([ - { type: "dns", value: "localhost" }, - { type: "ip", value: "127.0.0.1" }, - { type: "ip", value: "::1" } - ]) - ]; - - const gatewayServerSerialNumber = createSerialNumber(); - const gatewayServerCertificate = await x509.X509CertificateGenerator.create({ - serialNumber: gatewayServerSerialNumber, - subject: `O=${orgId},CN=Gateway`, - issuer: gatewayServerCaCert.subject, - notBefore: gatewayServerCertIssuedAt, - notAfter: gatewayServerCertExpireAt, - signingKey: gatewayServerCaPrivateKey, - publicKey: gatewayServerKeys.publicKey, - signingAlgorithm: alg, - extensions: gatewayServerCertExtensions - }); - - const proxyCredentials = await proxyService.getCredentialsForGateway({ - proxyName, - orgId, - gatewayId: gateway.id - }); - - return { - gatewayId: gateway.id, - proxyIp: proxyCredentials.proxyIp, - pki: { - serverCertificate: gatewayServerCertificate.toString("pem"), - serverPrivateKey: gatewayServerCertPrivateKey.export({ format: "pem", type: "pkcs8" }).toString(), - clientCertificateChain: constructPemChainFromCerts([gatewayClientCaCert, rootGatewayCaCert]) - }, - ssh: { - clientCertificate: proxyCredentials.clientSshCert, - clientPrivateKey: proxyCredentials.clientSshPrivateKey, - serverCAPublicKey: proxyCredentials.serverCAPublicKey + }; + } catch (err) { + if (err instanceof DatabaseError && (err.error as { code: string })?.code === DatabaseErrorCode.UniqueViolation) { + throw new BadRequestError({ message: `Gateway with name "${name}" already exists` }); } - }; + + throw err; + } }; const heartbeat = async ({ orgPermission }: { orgPermission: OrgServiceActor }) => {