mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-10 17:29:14 +00:00
618 lines
24 KiB
TypeScript
618 lines
24 KiB
TypeScript
import crypto from "node:crypto";
|
|
|
|
import { ForbiddenError } from "@casl/ability";
|
|
import * as x509 from "@peculiar/x509";
|
|
import { z } from "zod";
|
|
|
|
import { KeyStorePrefixes, PgSqlLock, TKeyStoreFactory } from "@app/keystore/keystore";
|
|
import { getConfig } from "@app/lib/config/env";
|
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
|
import { pingGatewayAndVerify } from "@app/lib/gateway";
|
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
|
import { getTurnCredentials } from "@app/lib/turn/credentials";
|
|
import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type";
|
|
import { CertExtendedKeyUsage, CertKeyAlgorithm, CertKeyUsage } from "@app/services/certificate/certificate-types";
|
|
import {
|
|
createSerialNumber,
|
|
keyAlgorithmToAlgCfg
|
|
} from "@app/services/certificate-authority/certificate-authority-fns";
|
|
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";
|
|
import { TGatewayDALFactory } from "./gateway-dal";
|
|
import {
|
|
TExchangeAllocatedRelayAddressDTO,
|
|
TGetGatewayByIdDTO,
|
|
THeartBeatDTO,
|
|
TListGatewaysDTO,
|
|
TUpdateGatewayByIdDTO
|
|
} from "./gateway-types";
|
|
import { TOrgGatewayConfigDALFactory } from "./org-gateway-config-dal";
|
|
|
|
type TGatewayServiceFactoryDep = {
|
|
gatewayDAL: TGatewayDALFactory;
|
|
orgGatewayConfigDAL: Pick<TOrgGatewayConfigDALFactory, "findOne" | "create" | "transaction" | "findById">;
|
|
licenseService: Pick<TLicenseServiceFactory, "onPremFeatures" | "getPlan">;
|
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey" | "decryptWithRootKey">;
|
|
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission" | "getProjectPermission">;
|
|
keyStore: Pick<TKeyStoreFactory, "getItem" | "setItemWithExpiry">;
|
|
};
|
|
|
|
export type TGatewayServiceFactory = ReturnType<typeof gatewayServiceFactory>;
|
|
const TURN_SERVER_CREDENTIALS_SCHEMA = z.object({
|
|
username: z.string(),
|
|
password: z.string()
|
|
});
|
|
|
|
export const gatewayServiceFactory = ({
|
|
gatewayDAL,
|
|
licenseService,
|
|
kmsService,
|
|
permissionService,
|
|
orgGatewayConfigDAL,
|
|
keyStore
|
|
}: TGatewayServiceFactoryDep) => {
|
|
const $validateOrgAccessToGateway = async (orgId: string, actorId: string, actorAuthMethod: ActorAuthMethod) => {
|
|
// if (!licenseService.onPremFeatures.gateway) {
|
|
// throw new BadRequestError({
|
|
// message:
|
|
// "Gateway handshake failed due to instance plan restrictions. Please upgrade your instance to Infisical's Enterprise plan."
|
|
// });
|
|
// }
|
|
const orgLicensePlan = await licenseService.getPlan(orgId);
|
|
if (!orgLicensePlan.gateway) {
|
|
throw new BadRequestError({
|
|
message:
|
|
"Gateway handshake 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 getGatewayRelayDetails = async (actorId: string, actorOrgId: string, actorAuthMethod: ActorAuthMethod) => {
|
|
const TURN_CRED_EXPIRY = 10 * 60; // 10 minutes
|
|
|
|
const envCfg = getConfig();
|
|
await $validateOrgAccessToGateway(actorOrgId, actorId, actorAuthMethod);
|
|
const { encryptor, decryptor } = await kmsService.createCipherPairWithDataKey({
|
|
type: KmsDataKey.Organization,
|
|
orgId: actorOrgId
|
|
});
|
|
|
|
if (!envCfg.GATEWAY_RELAY_AUTH_SECRET || !envCfg.GATEWAY_RELAY_ADDRESS || !envCfg.GATEWAY_RELAY_REALM) {
|
|
throw new BadRequestError({
|
|
message: "Gateway handshake failed due to missing instance configuration."
|
|
});
|
|
}
|
|
|
|
let turnServerUsername = "";
|
|
let turnServerPassword = "";
|
|
// keep it in redis for 5mins to avoid generating so many credentials
|
|
const previousCredential = await keyStore.getItem(KeyStorePrefixes.GatewayIdentityCredential(actorId));
|
|
if (previousCredential) {
|
|
const el = await TURN_SERVER_CREDENTIALS_SCHEMA.parseAsync(
|
|
JSON.parse(decryptor({ cipherTextBlob: Buffer.from(previousCredential, "hex") }).toString())
|
|
);
|
|
turnServerUsername = el.username;
|
|
turnServerPassword = el.password;
|
|
} else {
|
|
const el = getTurnCredentials(actorId, envCfg.GATEWAY_RELAY_AUTH_SECRET);
|
|
await keyStore.setItemWithExpiry(
|
|
KeyStorePrefixes.GatewayIdentityCredential(actorId),
|
|
TURN_CRED_EXPIRY,
|
|
encryptor({
|
|
plainText: Buffer.from(JSON.stringify({ username: el.username, password: el.password }))
|
|
}).cipherTextBlob.toString("hex")
|
|
);
|
|
turnServerUsername = el.username;
|
|
turnServerPassword = el.password;
|
|
}
|
|
|
|
return {
|
|
turnServerUsername,
|
|
turnServerPassword,
|
|
turnServerRealm: envCfg.GATEWAY_RELAY_REALM,
|
|
turnServerAddress: envCfg.GATEWAY_RELAY_ADDRESS,
|
|
infisicalStaticIp: envCfg.GATEWAY_INFISICAL_STATIC_IP_ADDRESS
|
|
};
|
|
};
|
|
|
|
const exchangeAllocatedRelayAddress = async ({
|
|
identityId,
|
|
identityOrg,
|
|
relayAddress,
|
|
identityOrgAuthMethod
|
|
}: TExchangeAllocatedRelayAddressDTO) => {
|
|
await $validateOrgAccessToGateway(identityOrg, identityId, identityOrgAuthMethod);
|
|
const { encryptor: orgKmsEncryptor, decryptor: orgKmsDecryptor } = await kmsService.createCipherPairWithDataKey({
|
|
type: KmsDataKey.Organization,
|
|
orgId: identityOrg
|
|
});
|
|
|
|
const orgGatewayConfig = await orgGatewayConfigDAL.transaction(async (tx) => {
|
|
await tx.raw("SELECT pg_advisory_xact_lock(?)", [PgSqlLock.OrgGatewayRootCaInit(identityOrg)]);
|
|
const existingGatewayConfig = await orgGatewayConfigDAL.findOne({ orgId: identityOrg });
|
|
if (existingGatewayConfig) return existingGatewayConfig;
|
|
|
|
const alg = keyAlgorithmToAlgCfg(CertKeyAlgorithm.RSA_2048);
|
|
// generate root CA
|
|
const rootCaKeys = await crypto.subtle.generateKey(alg, true, ["sign", "verify"]);
|
|
const rootCaSerialNumber = createSerialNumber();
|
|
const rootCaSkObj = crypto.KeyObject.from(rootCaKeys.privateKey);
|
|
const rootCaIssuedAt = new Date();
|
|
const rootCaKeyAlgorithm = CertKeyAlgorithm.RSA_2048;
|
|
const rootCaExpiration = new Date(new Date().setFullYear(2045));
|
|
const rootCaCert = await x509.X509CertificateGenerator.createSelfSigned({
|
|
name: `O=${identityOrg},CN=Infisical Gateway Root CA`,
|
|
serialNumber: rootCaSerialNumber,
|
|
notBefore: rootCaIssuedAt,
|
|
notAfter: rootCaExpiration,
|
|
signingAlgorithm: alg,
|
|
keys: rootCaKeys,
|
|
extensions: [
|
|
// eslint-disable-next-line no-bitwise
|
|
new x509.KeyUsagesExtension(x509.KeyUsageFlags.keyCertSign | x509.KeyUsageFlags.cRLSign, true),
|
|
await x509.SubjectKeyIdentifierExtension.create(rootCaKeys.publicKey)
|
|
]
|
|
});
|
|
|
|
// generate client ca
|
|
const clientCaSerialNumber = createSerialNumber();
|
|
const clientCaIssuedAt = new Date();
|
|
const clientCaExpiration = new Date(new Date().setFullYear(2045));
|
|
const clientCaKeys = await crypto.subtle.generateKey(alg, true, ["sign", "verify"]);
|
|
const clientCaSkObj = crypto.KeyObject.from(clientCaKeys.privateKey);
|
|
|
|
const clientCaCert = await x509.X509CertificateGenerator.create({
|
|
serialNumber: clientCaSerialNumber,
|
|
subject: `O=${identityOrg},CN=Client Intermediate CA`,
|
|
issuer: rootCaCert.subject,
|
|
notBefore: clientCaIssuedAt,
|
|
notAfter: clientCaExpiration,
|
|
signingKey: rootCaKeys.privateKey,
|
|
publicKey: clientCaKeys.publicKey,
|
|
signingAlgorithm: alg,
|
|
extensions: [
|
|
new x509.KeyUsagesExtension(
|
|
// eslint-disable-next-line no-bitwise
|
|
x509.KeyUsageFlags.keyCertSign |
|
|
x509.KeyUsageFlags.cRLSign |
|
|
x509.KeyUsageFlags.digitalSignature |
|
|
x509.KeyUsageFlags.keyEncipherment,
|
|
true
|
|
),
|
|
new x509.BasicConstraintsExtension(true, 0, true),
|
|
await x509.AuthorityKeyIdentifierExtension.create(rootCaCert, false),
|
|
await x509.SubjectKeyIdentifierExtension.create(clientCaKeys.publicKey)
|
|
]
|
|
});
|
|
|
|
const clientKeys = await crypto.subtle.generateKey(alg, true, ["sign", "verify"]);
|
|
const clientCertSerialNumber = createSerialNumber();
|
|
const clientCert = await x509.X509CertificateGenerator.create({
|
|
serialNumber: clientCertSerialNumber,
|
|
subject: `O=${identityOrg},OU=gateway-client,CN=cloud`,
|
|
issuer: clientCaCert.subject,
|
|
notAfter: clientCaExpiration,
|
|
notBefore: clientCaIssuedAt,
|
|
signingKey: clientCaKeys.privateKey,
|
|
publicKey: clientKeys.publicKey,
|
|
signingAlgorithm: alg,
|
|
extensions: [
|
|
new x509.BasicConstraintsExtension(false),
|
|
await x509.AuthorityKeyIdentifierExtension.create(clientCaCert, false),
|
|
await x509.SubjectKeyIdentifierExtension.create(clientKeys.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] |
|
|
x509.KeyUsageFlags[CertKeyUsage.KEY_AGREEMENT],
|
|
true
|
|
),
|
|
new x509.ExtendedKeyUsageExtension([x509.ExtendedKeyUsage[CertExtendedKeyUsage.CLIENT_AUTH]], true)
|
|
]
|
|
});
|
|
const clientSkObj = crypto.KeyObject.from(clientKeys.privateKey);
|
|
|
|
// generate gateway ca
|
|
const gatewayCaSerialNumber = createSerialNumber();
|
|
const gatewayCaIssuedAt = new Date();
|
|
const gatewayCaExpiration = new Date(new Date().setFullYear(2045));
|
|
const gatewayCaKeys = await crypto.subtle.generateKey(alg, true, ["sign", "verify"]);
|
|
const gatewayCaSkObj = crypto.KeyObject.from(gatewayCaKeys.privateKey);
|
|
const gatewayCaCert = await x509.X509CertificateGenerator.create({
|
|
serialNumber: gatewayCaSerialNumber,
|
|
subject: `O=${identityOrg},CN=Gateway CA`,
|
|
issuer: rootCaCert.subject,
|
|
notBefore: gatewayCaIssuedAt,
|
|
notAfter: gatewayCaExpiration,
|
|
signingKey: rootCaKeys.privateKey,
|
|
publicKey: gatewayCaKeys.publicKey,
|
|
signingAlgorithm: alg,
|
|
extensions: [
|
|
new x509.KeyUsagesExtension(
|
|
// eslint-disable-next-line no-bitwise
|
|
x509.KeyUsageFlags.keyCertSign |
|
|
x509.KeyUsageFlags.cRLSign |
|
|
x509.KeyUsageFlags.digitalSignature |
|
|
x509.KeyUsageFlags.keyEncipherment,
|
|
true
|
|
),
|
|
new x509.BasicConstraintsExtension(true, 0, true),
|
|
await x509.AuthorityKeyIdentifierExtension.create(rootCaCert, false),
|
|
await x509.SubjectKeyIdentifierExtension.create(gatewayCaKeys.publicKey)
|
|
]
|
|
});
|
|
|
|
return orgGatewayConfigDAL.create({
|
|
orgId: identityOrg,
|
|
rootCaIssuedAt,
|
|
rootCaExpiration,
|
|
rootCaSerialNumber,
|
|
rootCaKeyAlgorithm,
|
|
encryptedRootCaPrivateKey: orgKmsEncryptor({
|
|
plainText: rootCaSkObj.export({
|
|
type: "pkcs8",
|
|
format: "der"
|
|
})
|
|
}).cipherTextBlob,
|
|
encryptedRootCaCertificate: orgKmsEncryptor({ plainText: Buffer.from(rootCaCert.rawData) }).cipherTextBlob,
|
|
|
|
clientCaIssuedAt,
|
|
clientCaExpiration,
|
|
clientCaSerialNumber,
|
|
encryptedClientCaPrivateKey: orgKmsEncryptor({
|
|
plainText: clientCaSkObj.export({
|
|
type: "pkcs8",
|
|
format: "der"
|
|
})
|
|
}).cipherTextBlob,
|
|
encryptedClientCaCertificate: orgKmsEncryptor({
|
|
plainText: Buffer.from(clientCaCert.rawData)
|
|
}).cipherTextBlob,
|
|
|
|
clientCertIssuedAt: clientCaIssuedAt,
|
|
clientCertExpiration: clientCaExpiration,
|
|
clientCertKeyAlgorithm: CertKeyAlgorithm.RSA_2048,
|
|
clientCertSerialNumber,
|
|
encryptedClientPrivateKey: orgKmsEncryptor({
|
|
plainText: clientSkObj.export({
|
|
type: "pkcs8",
|
|
format: "der"
|
|
})
|
|
}).cipherTextBlob,
|
|
encryptedClientCertificate: orgKmsEncryptor({
|
|
plainText: Buffer.from(clientCert.rawData)
|
|
}).cipherTextBlob,
|
|
|
|
gatewayCaIssuedAt,
|
|
gatewayCaExpiration,
|
|
gatewayCaSerialNumber,
|
|
encryptedGatewayCaPrivateKey: orgKmsEncryptor({
|
|
plainText: gatewayCaSkObj.export({
|
|
type: "pkcs8",
|
|
format: "der"
|
|
})
|
|
}).cipherTextBlob,
|
|
encryptedGatewayCaCertificate: orgKmsEncryptor({
|
|
plainText: Buffer.from(gatewayCaCert.rawData)
|
|
}).cipherTextBlob
|
|
});
|
|
});
|
|
|
|
const rootCaCert = new x509.X509Certificate(
|
|
orgKmsDecryptor({
|
|
cipherTextBlob: orgGatewayConfig.encryptedRootCaCertificate
|
|
})
|
|
);
|
|
const clientCaCert = new x509.X509Certificate(
|
|
orgKmsDecryptor({
|
|
cipherTextBlob: orgGatewayConfig.encryptedClientCaCertificate
|
|
})
|
|
);
|
|
|
|
const gatewayCaAlg = keyAlgorithmToAlgCfg(orgGatewayConfig.rootCaKeyAlgorithm as CertKeyAlgorithm);
|
|
const gatewayCaSkObj = crypto.createPrivateKey({
|
|
key: orgKmsDecryptor({ cipherTextBlob: orgGatewayConfig.encryptedGatewayCaPrivateKey }),
|
|
format: "der",
|
|
type: "pkcs8"
|
|
});
|
|
const gatewayCaCert = new x509.X509Certificate(
|
|
orgKmsDecryptor({
|
|
cipherTextBlob: orgGatewayConfig.encryptedGatewayCaCertificate
|
|
})
|
|
);
|
|
|
|
const gatewayCaPrivateKey = await crypto.subtle.importKey(
|
|
"pkcs8",
|
|
gatewayCaSkObj.export({ format: "der", type: "pkcs8" }),
|
|
gatewayCaAlg,
|
|
true,
|
|
["sign"]
|
|
);
|
|
|
|
const alg = keyAlgorithmToAlgCfg(CertKeyAlgorithm.RSA_2048);
|
|
const gatewayKeys = await crypto.subtle.generateKey(alg, true, ["sign", "verify"]);
|
|
const certIssuedAt = new Date();
|
|
// then need to periodically init
|
|
const certExpireAt = new Date(new Date().setMonth(new Date().getMonth() + 1));
|
|
|
|
const extensions: x509.Extension[] = [
|
|
new x509.BasicConstraintsExtension(false),
|
|
await x509.AuthorityKeyIdentifierExtension.create(gatewayCaCert, false),
|
|
await x509.SubjectKeyIdentifierExtension.create(gatewayKeys.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),
|
|
// san
|
|
new x509.SubjectAlternativeNameExtension([{ type: "ip", value: relayAddress.split(":")[0] }], false)
|
|
];
|
|
|
|
const serialNumber = createSerialNumber();
|
|
const privateKey = crypto.KeyObject.from(gatewayKeys.privateKey);
|
|
const gatewayCertificate = await x509.X509CertificateGenerator.create({
|
|
serialNumber,
|
|
subject: `CN=${identityId},O=${identityOrg},OU=Gateway`,
|
|
issuer: gatewayCaCert.subject,
|
|
notBefore: certIssuedAt,
|
|
notAfter: certExpireAt,
|
|
signingKey: gatewayCaPrivateKey,
|
|
publicKey: gatewayKeys.publicKey,
|
|
signingAlgorithm: alg,
|
|
extensions
|
|
});
|
|
|
|
const appCfg = getConfig();
|
|
// just for local development
|
|
const formatedRelayAddress =
|
|
appCfg.NODE_ENV === "development" ? relayAddress.replace("127.0.0.1", "host.docker.internal") : relayAddress;
|
|
|
|
await gatewayDAL.transaction(async (tx) => {
|
|
await tx.raw("SELECT pg_advisory_xact_lock(?)", [PgSqlLock.OrgGatewayCertExchange(identityOrg)]);
|
|
const existingGateway = await gatewayDAL.findOne({ identityId, orgGatewayRootCaId: orgGatewayConfig.id });
|
|
|
|
if (existingGateway) {
|
|
return gatewayDAL.updateById(existingGateway.id, {
|
|
keyAlgorithm: CertKeyAlgorithm.RSA_2048,
|
|
issuedAt: certIssuedAt,
|
|
expiration: certExpireAt,
|
|
serialNumber,
|
|
relayAddress: orgKmsEncryptor({
|
|
plainText: Buffer.from(formatedRelayAddress)
|
|
}).cipherTextBlob
|
|
});
|
|
}
|
|
|
|
return gatewayDAL.create({
|
|
keyAlgorithm: CertKeyAlgorithm.RSA_2048,
|
|
issuedAt: certIssuedAt,
|
|
expiration: certExpireAt,
|
|
serialNumber,
|
|
relayAddress: orgKmsEncryptor({
|
|
plainText: Buffer.from(formatedRelayAddress)
|
|
}).cipherTextBlob,
|
|
identityId,
|
|
orgGatewayRootCaId: orgGatewayConfig.id,
|
|
name: `gateway-${alphaNumericNanoId(6).toLowerCase()}`
|
|
});
|
|
});
|
|
|
|
const gatewayCertificateChain = `${clientCaCert.toString("pem")}\n${rootCaCert.toString("pem")}`.trim();
|
|
|
|
return {
|
|
serialNumber,
|
|
privateKey: privateKey.export({ format: "pem", type: "pkcs8" }) as string,
|
|
certificate: gatewayCertificate.toString("pem"),
|
|
certificateChain: gatewayCertificateChain
|
|
};
|
|
};
|
|
|
|
const heartbeat = async ({ orgPermission }: THeartBeatDTO) => {
|
|
await $validateOrgAccessToGateway(orgPermission.orgId, orgPermission.id, orgPermission.authMethod);
|
|
const orgGatewayConfig = await orgGatewayConfigDAL.findOne({ orgId: orgPermission.orgId });
|
|
if (!orgGatewayConfig) throw new NotFoundError({ message: `Identity with ID ${orgPermission.id} not found.` });
|
|
|
|
const [gateway] = await gatewayDAL.find({ identityId: orgPermission.id, orgGatewayRootCaId: orgGatewayConfig.id });
|
|
if (!gateway) throw new NotFoundError({ message: `Gateway with ID ${orgPermission.id} not found.` });
|
|
|
|
const { decryptor: orgKmsDecryptor } = await kmsService.createCipherPairWithDataKey({
|
|
type: KmsDataKey.Organization,
|
|
orgId: orgGatewayConfig.orgId
|
|
});
|
|
|
|
const rootCaCert = new x509.X509Certificate(
|
|
orgKmsDecryptor({
|
|
cipherTextBlob: orgGatewayConfig.encryptedRootCaCertificate
|
|
})
|
|
);
|
|
const gatewayCaCert = new x509.X509Certificate(
|
|
orgKmsDecryptor({
|
|
cipherTextBlob: orgGatewayConfig.encryptedGatewayCaCertificate
|
|
})
|
|
);
|
|
const clientCert = new x509.X509Certificate(
|
|
orgKmsDecryptor({
|
|
cipherTextBlob: orgGatewayConfig.encryptedClientCertificate
|
|
})
|
|
);
|
|
|
|
const privateKey = crypto
|
|
.createPrivateKey({
|
|
key: orgKmsDecryptor({ cipherTextBlob: orgGatewayConfig.encryptedClientPrivateKey }),
|
|
format: "der",
|
|
type: "pkcs8"
|
|
})
|
|
.export({ type: "pkcs8", format: "pem" });
|
|
|
|
const relayAddress = orgKmsDecryptor({ cipherTextBlob: gateway.relayAddress }).toString();
|
|
const [relayHost, relayPort] = relayAddress.split(":");
|
|
|
|
await pingGatewayAndVerify({
|
|
relayHost,
|
|
relayPort: Number(relayPort),
|
|
tlsOptions: {
|
|
key: privateKey.toString(),
|
|
ca: `${gatewayCaCert.toString("pem")}\n${rootCaCert.toString("pem")}`.trim(),
|
|
cert: clientCert.toString("pem")
|
|
},
|
|
identityId: orgPermission.id,
|
|
orgId: orgPermission.orgId
|
|
});
|
|
|
|
await gatewayDAL.updateById(gateway.id, { heartbeat: new Date() });
|
|
};
|
|
|
|
const listGateways = async ({ orgPermission }: TListGatewaysDTO) => {
|
|
const { permission } = await permissionService.getOrgPermission(
|
|
orgPermission.type,
|
|
orgPermission.id,
|
|
orgPermission.orgId,
|
|
orgPermission.authMethod,
|
|
orgPermission.orgId
|
|
);
|
|
ForbiddenError.from(permission).throwUnlessCan(
|
|
OrgPermissionGatewayActions.ListGateways,
|
|
OrgPermissionSubjects.Gateway
|
|
);
|
|
const orgGatewayConfig = await orgGatewayConfigDAL.findOne({ orgId: orgPermission.orgId });
|
|
if (!orgGatewayConfig) return [];
|
|
|
|
const gateways = await gatewayDAL.find({
|
|
orgGatewayRootCaId: orgGatewayConfig.id
|
|
});
|
|
return gateways;
|
|
};
|
|
|
|
const getGatewayById = async ({ orgPermission, id }: TGetGatewayByIdDTO) => {
|
|
const { permission } = await permissionService.getOrgPermission(
|
|
orgPermission.type,
|
|
orgPermission.id,
|
|
orgPermission.orgId,
|
|
orgPermission.authMethod,
|
|
orgPermission.orgId
|
|
);
|
|
ForbiddenError.from(permission).throwUnlessCan(
|
|
OrgPermissionGatewayActions.ListGateways,
|
|
OrgPermissionSubjects.Gateway
|
|
);
|
|
const orgGatewayConfig = await orgGatewayConfigDAL.findOne({ orgId: orgPermission.orgId });
|
|
if (!orgGatewayConfig) throw new NotFoundError({ message: `Gateway with ID ${id} not found.` });
|
|
|
|
const [gateway] = await gatewayDAL.find({ id, orgGatewayRootCaId: orgGatewayConfig.id });
|
|
if (!gateway) throw new NotFoundError({ message: `Gateway with ID ${id} not found.` });
|
|
return gateway;
|
|
};
|
|
|
|
const updateGatewayById = async ({ orgPermission, id, name }: TUpdateGatewayByIdDTO) => {
|
|
const { permission } = await permissionService.getOrgPermission(
|
|
orgPermission.type,
|
|
orgPermission.id,
|
|
orgPermission.orgId,
|
|
orgPermission.authMethod,
|
|
orgPermission.orgId
|
|
);
|
|
ForbiddenError.from(permission).throwUnlessCan(
|
|
OrgPermissionGatewayActions.EditGateways,
|
|
OrgPermissionSubjects.Gateway
|
|
);
|
|
const orgGatewayConfig = await orgGatewayConfigDAL.findOne({ orgId: orgPermission.orgId });
|
|
if (!orgGatewayConfig) throw new NotFoundError({ message: `Gateway with ID ${id} not found.` });
|
|
|
|
const [gateway] = await gatewayDAL.update({ id, orgGatewayRootCaId: orgGatewayConfig.id }, { name });
|
|
if (!gateway) throw new NotFoundError({ message: `Gateway with ID ${id} not found.` });
|
|
|
|
return gateway;
|
|
};
|
|
|
|
const deleteGatewayById = async ({ orgPermission, id }: TGetGatewayByIdDTO) => {
|
|
const { permission } = await permissionService.getOrgPermission(
|
|
orgPermission.type,
|
|
orgPermission.id,
|
|
orgPermission.orgId,
|
|
orgPermission.authMethod,
|
|
orgPermission.orgId
|
|
);
|
|
ForbiddenError.from(permission).throwUnlessCan(
|
|
OrgPermissionGatewayActions.DeleteGateways,
|
|
OrgPermissionSubjects.Gateway
|
|
);
|
|
const orgGatewayConfig = await orgGatewayConfigDAL.findOne({ orgId: orgPermission.orgId });
|
|
if (!orgGatewayConfig) throw new NotFoundError({ message: `Gateway with ID ${id} not found.` });
|
|
|
|
const [gateway] = await gatewayDAL.delete({ id, orgGatewayRootCaId: orgGatewayConfig.id });
|
|
if (!gateway) throw new NotFoundError({ message: `Gateway with ID ${id} not found.` });
|
|
return gateway;
|
|
};
|
|
|
|
const fnGetGatewayClientTlsByGatewayId = async (gatewayId: string) => {
|
|
const gateway = await gatewayDAL.findById(gatewayId);
|
|
if (!gateway) throw new NotFoundError({ message: `Gateway with ID ${gatewayId} not found.` });
|
|
|
|
const orgGatewayConfig = await orgGatewayConfigDAL.findById(gateway.orgGatewayRootCaId);
|
|
const { decryptor: orgKmsDecryptor } = await kmsService.createCipherPairWithDataKey({
|
|
type: KmsDataKey.Organization,
|
|
orgId: orgGatewayConfig.orgId
|
|
});
|
|
|
|
const rootCaCert = new x509.X509Certificate(
|
|
orgKmsDecryptor({
|
|
cipherTextBlob: orgGatewayConfig.encryptedRootCaCertificate
|
|
})
|
|
);
|
|
const gatewayCaCert = new x509.X509Certificate(
|
|
orgKmsDecryptor({
|
|
cipherTextBlob: orgGatewayConfig.encryptedGatewayCaCertificate
|
|
})
|
|
);
|
|
const clientCert = new x509.X509Certificate(
|
|
orgKmsDecryptor({
|
|
cipherTextBlob: orgGatewayConfig.encryptedClientCertificate
|
|
})
|
|
);
|
|
|
|
const clientSkObj = crypto.createPrivateKey({
|
|
key: orgKmsDecryptor({ cipherTextBlob: orgGatewayConfig.encryptedClientPrivateKey }),
|
|
format: "der",
|
|
type: "pkcs8"
|
|
});
|
|
|
|
return {
|
|
relayAddress: orgKmsDecryptor({ cipherTextBlob: gateway.relayAddress }).toString(),
|
|
privateKey: clientSkObj.export({ type: "pkcs8", format: "pem" }),
|
|
certificate: clientCert.toString("pem"),
|
|
certChain: `${gatewayCaCert.toString("pem")}\n${rootCaCert.toString("pem")}`.trim(),
|
|
identityId: gateway.identityId,
|
|
orgId: orgGatewayConfig.orgId
|
|
};
|
|
};
|
|
|
|
return {
|
|
getGatewayRelayDetails,
|
|
exchangeAllocatedRelayAddress,
|
|
listGateways,
|
|
getGatewayById,
|
|
updateGatewayById,
|
|
deleteGatewayById,
|
|
fnGetGatewayClientTlsByGatewayId,
|
|
heartbeat
|
|
};
|
|
};
|