mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-03 22:26:07 +00:00
Merge pull request #4553 from Infisical/feat/add-support-for-org-relay-registration
feat: add support for org relay registration
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { RelaysSchema } from "@app/db/schemas";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { crypto } from "@app/lib/crypto/cryptography";
|
||||
import { BadRequestError, UnauthorizedError } from "@app/lib/errors";
|
||||
import { writeLimit } from "@app/server/config/rateLimiter";
|
||||
import { UnauthorizedError } from "@app/lib/errors";
|
||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||
import { slugSchema } from "@app/server/lib/schemas";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { AuthMode } from "@app/services/auth/auth-type";
|
||||
@@ -89,14 +90,59 @@ export const registerRelayRouter = async (server: FastifyZodProvider) => {
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
handler: async (req) => {
|
||||
throw new BadRequestError({
|
||||
message: "Org relay registration is not yet supported"
|
||||
});
|
||||
|
||||
return server.services.relay.registerRelay({
|
||||
...req.body,
|
||||
identityId: req.permission.id,
|
||||
orgId: req.permission.orgId
|
||||
orgId: req.permission.orgId,
|
||||
actorAuthMethod: req.permission.authMethod
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "GET",
|
||||
url: "/",
|
||||
schema: {
|
||||
response: {
|
||||
200: RelaysSchema.array()
|
||||
}
|
||||
},
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
handler: async (req) => {
|
||||
return server.services.relay.getRelays({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "DELETE",
|
||||
url: "/:id",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({
|
||||
id: z.string()
|
||||
}),
|
||||
response: {
|
||||
200: RelaysSchema
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
handler: async (req) => {
|
||||
return server.services.relay.deleteRelay({
|
||||
id: req.params.id,
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -395,7 +395,8 @@ export const gatewayV2ServiceFactory = ({
|
||||
relayId: gateway.relayId,
|
||||
orgId: gateway.orgId,
|
||||
orgName: gateway.orgName,
|
||||
gatewayId
|
||||
gatewayId,
|
||||
gatewayName: gateway.name
|
||||
});
|
||||
|
||||
return {
|
||||
@@ -508,7 +509,8 @@ export const gatewayV2ServiceFactory = ({
|
||||
const relayCredentials = await relayService.getCredentialsForGateway({
|
||||
relayName,
|
||||
orgId,
|
||||
gatewayId: gateway.id
|
||||
gatewayId: gateway.id,
|
||||
gatewayName: gateway.name
|
||||
});
|
||||
|
||||
return {
|
||||
|
||||
@@ -58,6 +58,13 @@ export enum OrgPermissionGatewayActions {
|
||||
AttachGateways = "attach-gateways"
|
||||
}
|
||||
|
||||
export enum OrgPermissionRelayActions {
|
||||
CreateRelays = "create-relays",
|
||||
ListRelays = "list-relays",
|
||||
EditRelays = "edit-relays",
|
||||
DeleteRelays = "delete-relays"
|
||||
}
|
||||
|
||||
export enum OrgPermissionIdentityActions {
|
||||
Read = "read",
|
||||
Create = "create",
|
||||
@@ -109,6 +116,7 @@ export enum OrgPermissionSubjects {
|
||||
AppConnections = "app-connections",
|
||||
Kmip = "kmip",
|
||||
Gateway = "gateway",
|
||||
Relay = "relay",
|
||||
SecretShare = "secret-share"
|
||||
}
|
||||
|
||||
@@ -136,6 +144,7 @@ export type OrgPermissionSet =
|
||||
| [OrgPermissionAuditLogsActions, OrgPermissionSubjects.AuditLogs]
|
||||
| [OrgPermissionActions, OrgPermissionSubjects.ProjectTemplates]
|
||||
| [OrgPermissionGatewayActions, OrgPermissionSubjects.Gateway]
|
||||
| [OrgPermissionRelayActions, OrgPermissionSubjects.Relay]
|
||||
| [
|
||||
OrgPermissionAppConnectionActions,
|
||||
(
|
||||
@@ -279,6 +288,12 @@ export const OrgPermissionSchema = z.discriminatedUnion("subject", [
|
||||
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionGatewayActions).describe(
|
||||
"Describe what action an entity can take."
|
||||
)
|
||||
}),
|
||||
z.object({
|
||||
subject: z.literal(OrgPermissionSubjects.Relay).describe("The entity this permission pertains to."),
|
||||
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionRelayActions).describe(
|
||||
"Describe what action an entity can take."
|
||||
)
|
||||
})
|
||||
]);
|
||||
|
||||
@@ -383,6 +398,11 @@ const buildAdminPermission = () => {
|
||||
can(OrgPermissionGatewayActions.DeleteGateways, OrgPermissionSubjects.Gateway);
|
||||
can(OrgPermissionGatewayActions.AttachGateways, OrgPermissionSubjects.Gateway);
|
||||
|
||||
can(OrgPermissionRelayActions.ListRelays, OrgPermissionSubjects.Relay);
|
||||
can(OrgPermissionRelayActions.CreateRelays, OrgPermissionSubjects.Relay);
|
||||
can(OrgPermissionRelayActions.EditRelays, OrgPermissionSubjects.Relay);
|
||||
can(OrgPermissionRelayActions.DeleteRelays, OrgPermissionSubjects.Relay);
|
||||
|
||||
can(OrgPermissionAdminConsoleAction.AccessAllProjects, OrgPermissionSubjects.AdminConsole);
|
||||
|
||||
can(OrgPermissionKmipActions.Setup, OrgPermissionSubjects.Kmip);
|
||||
@@ -445,6 +465,10 @@ const buildMemberPermission = () => {
|
||||
can(OrgPermissionGatewayActions.CreateGateways, OrgPermissionSubjects.Gateway);
|
||||
can(OrgPermissionGatewayActions.AttachGateways, OrgPermissionSubjects.Gateway);
|
||||
|
||||
can(OrgPermissionRelayActions.ListRelays, OrgPermissionSubjects.Relay);
|
||||
can(OrgPermissionRelayActions.CreateRelays, OrgPermissionSubjects.Relay);
|
||||
can(OrgPermissionRelayActions.EditRelays, OrgPermissionSubjects.Relay);
|
||||
|
||||
can(OrgPermissionMachineIdentityAuthTemplateActions.ListTemplates, OrgPermissionSubjects.MachineIdentityAuthTemplate);
|
||||
can(
|
||||
OrgPermissionMachineIdentityAuthTemplateActions.UnlinkTemplates,
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export const RELAY_CONNECTING_GATEWAY_INFO = "1.3.6.1.4.1.12345.100.3";
|
||||
@@ -1,9 +1,13 @@
|
||||
import { isIP } from "node:net";
|
||||
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
import * as x509 from "@peculiar/x509";
|
||||
|
||||
import { TRelays } from "@app/db/schemas";
|
||||
import { PgSqlLock } from "@app/keystore/keystore";
|
||||
import { crypto } from "@app/lib/crypto";
|
||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||
import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type";
|
||||
import { constructPemChainFromCerts, prependCertToPemChain } from "@app/services/certificate/certificate-fns";
|
||||
import { CertExtendedKeyUsage, CertKeyAlgorithm, CertKeyUsage } from "@app/services/certificate/certificate-types";
|
||||
import {
|
||||
@@ -14,11 +18,15 @@ import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||
import { KmsDataKey } from "@app/services/kms/kms-types";
|
||||
|
||||
import { verifyHostInputValidity } from "../dynamic-secret/dynamic-secret-fns";
|
||||
import { TLicenseServiceFactory } from "../license/license-service";
|
||||
import { OrgPermissionRelayActions, OrgPermissionSubjects } from "../permission/org-permission";
|
||||
import { TPermissionServiceFactory } from "../permission/permission-service-types";
|
||||
import { createSshCert, createSshKeyPair } from "../ssh/ssh-certificate-authority-fns";
|
||||
import { SshCertType } from "../ssh/ssh-certificate-authority-types";
|
||||
import { SshCertKeyAlgorithm } from "../ssh-certificate/ssh-certificate-types";
|
||||
import { TInstanceRelayConfigDALFactory } from "./instance-relay-config-dal";
|
||||
import { TOrgRelayConfigDALFactory } from "./org-relay-config-dal";
|
||||
import { RELAY_CONNECTING_GATEWAY_INFO } from "./relay-constants";
|
||||
import { TRelayDALFactory } from "./relay-dal";
|
||||
|
||||
export type TRelayServiceFactory = ReturnType<typeof relayServiceFactory>;
|
||||
@@ -29,12 +37,16 @@ export const relayServiceFactory = ({
|
||||
instanceRelayConfigDAL,
|
||||
orgRelayConfigDAL,
|
||||
relayDAL,
|
||||
kmsService
|
||||
kmsService,
|
||||
licenseService,
|
||||
permissionService
|
||||
}: {
|
||||
instanceRelayConfigDAL: TInstanceRelayConfigDALFactory;
|
||||
orgRelayConfigDAL: TOrgRelayConfigDALFactory;
|
||||
relayDAL: TRelayDALFactory;
|
||||
kmsService: TKmsServiceFactory;
|
||||
licenseService: TLicenseServiceFactory;
|
||||
permissionService: TPermissionServiceFactory;
|
||||
}) => {
|
||||
const $getInstanceCAs = async () => {
|
||||
const instanceConfig = await instanceRelayConfigDAL.transaction(async (tx) => {
|
||||
@@ -639,8 +651,9 @@ export const relayServiceFactory = ({
|
||||
true
|
||||
),
|
||||
new x509.ExtendedKeyUsageExtension([x509.ExtendedKeyUsage[CertExtendedKeyUsage.SERVER_AUTH]], true),
|
||||
|
||||
// san
|
||||
new x509.SubjectAlternativeNameExtension([{ type: "ip", value: host }], false)
|
||||
new x509.SubjectAlternativeNameExtension([{ type: isIP(host) ? "ip" : "dns", value: host }], false)
|
||||
];
|
||||
|
||||
const relayServerSerialNumber = createSerialNumber();
|
||||
@@ -689,6 +702,7 @@ export const relayServiceFactory = ({
|
||||
|
||||
const $generateRelayClientCredentials = async ({
|
||||
gatewayId,
|
||||
gatewayName,
|
||||
orgId,
|
||||
orgName,
|
||||
relayPkiClientCaCertificate,
|
||||
@@ -697,6 +711,7 @@ export const relayServiceFactory = ({
|
||||
relayPkiServerCaCertificateChain
|
||||
}: {
|
||||
gatewayId: string;
|
||||
gatewayName: string;
|
||||
orgId: string;
|
||||
orgName: string;
|
||||
relayPkiClientCaCertificate: Buffer;
|
||||
@@ -727,6 +742,16 @@ export const relayServiceFactory = ({
|
||||
const clientCertPrivateKey = crypto.nativeCrypto.KeyObject.from(clientKeys.privateKey);
|
||||
const clientCertSerialNumber = createSerialNumber();
|
||||
|
||||
const connectingGatewayInfoExtension = new x509.Extension(
|
||||
RELAY_CONNECTING_GATEWAY_INFO,
|
||||
false,
|
||||
Buffer.from(
|
||||
JSON.stringify({
|
||||
name: gatewayName
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
// Build standard extensions
|
||||
const extensions: x509.Extension[] = [
|
||||
new x509.BasicConstraintsExtension(false),
|
||||
@@ -740,7 +765,8 @@ export const relayServiceFactory = ({
|
||||
x509.KeyUsageFlags[CertKeyUsage.KEY_AGREEMENT],
|
||||
true
|
||||
),
|
||||
new x509.ExtendedKeyUsageExtension([x509.ExtendedKeyUsage[CertExtendedKeyUsage.CLIENT_AUTH]], true)
|
||||
new x509.ExtendedKeyUsageExtension([x509.ExtendedKeyUsage[CertExtendedKeyUsage.CLIENT_AUTH]], true),
|
||||
connectingGatewayInfoExtension
|
||||
];
|
||||
|
||||
const clientCert = await x509.X509CertificateGenerator.create({
|
||||
@@ -768,11 +794,13 @@ export const relayServiceFactory = ({
|
||||
const getCredentialsForGateway = async ({
|
||||
relayName,
|
||||
orgId,
|
||||
gatewayId
|
||||
gatewayId,
|
||||
gatewayName
|
||||
}: {
|
||||
relayName: string;
|
||||
orgId: string;
|
||||
gatewayId: string;
|
||||
gatewayName: string;
|
||||
}) => {
|
||||
let relay: TRelays | null = await relayDAL.findOne({
|
||||
orgId,
|
||||
@@ -819,10 +847,10 @@ export const relayServiceFactory = ({
|
||||
const relayClientSshCert = await createSshCert({
|
||||
caPrivateKey: orgCAs.relaySshClientCaPrivateKey.toString("utf8"),
|
||||
clientPublicKey: relayClientSshPublicKey,
|
||||
keyId: `relay-client-${relay.id}`,
|
||||
principals: [gatewayId],
|
||||
keyId: `client-${relayName}`,
|
||||
principals: [gatewayId, gatewayName],
|
||||
certType: SshCertType.USER,
|
||||
requestedTtl: "30d"
|
||||
requestedTtl: "1d"
|
||||
});
|
||||
|
||||
return {
|
||||
@@ -837,12 +865,14 @@ export const relayServiceFactory = ({
|
||||
relayId,
|
||||
orgId,
|
||||
orgName,
|
||||
gatewayId
|
||||
gatewayId,
|
||||
gatewayName
|
||||
}: {
|
||||
relayId: string;
|
||||
orgId: string;
|
||||
orgName: string;
|
||||
gatewayId: string;
|
||||
gatewayName: string;
|
||||
}) => {
|
||||
const relay = await relayDAL.findOne({
|
||||
id: relayId
|
||||
@@ -860,6 +890,7 @@ export const relayServiceFactory = ({
|
||||
const instanceCAs = await $getInstanceCAs();
|
||||
const relayCertificateCredentials = await $generateRelayClientCredentials({
|
||||
gatewayId,
|
||||
gatewayName,
|
||||
orgId,
|
||||
orgName,
|
||||
relayPkiClientCaCertificate: instanceCAs.instanceRelayPkiClientCaCertificate,
|
||||
@@ -877,6 +908,7 @@ export const relayServiceFactory = ({
|
||||
const orgCAs = await $getOrgCAs(orgId);
|
||||
const relayCertificateCredentials = await $generateRelayClientCredentials({
|
||||
gatewayId,
|
||||
gatewayName,
|
||||
orgId,
|
||||
orgName,
|
||||
relayPkiClientCaCertificate: orgCAs.relayPkiClientCaCertificate,
|
||||
@@ -895,11 +927,13 @@ export const relayServiceFactory = ({
|
||||
host,
|
||||
name,
|
||||
identityId,
|
||||
actorAuthMethod,
|
||||
orgId
|
||||
}: {
|
||||
host: string;
|
||||
name: string;
|
||||
identityId?: string;
|
||||
actorAuthMethod?: ActorAuthMethod;
|
||||
orgId?: string;
|
||||
}) => {
|
||||
let relay: TRelays;
|
||||
@@ -908,6 +942,27 @@ export const relayServiceFactory = ({
|
||||
await verifyHostInputValidity(host);
|
||||
|
||||
if (isOrgRelay) {
|
||||
const orgLicensePlan = await licenseService.getPlan(orgId);
|
||||
if (!orgLicensePlan.gateway) {
|
||||
throw new BadRequestError({
|
||||
message:
|
||||
"Relay registration failed due to organization plan restrictions. Please upgrade your instance to Infisical's Enterprise plan."
|
||||
});
|
||||
}
|
||||
|
||||
const { permission } = await permissionService.getOrgPermission(
|
||||
ActorType.IDENTITY,
|
||||
identityId,
|
||||
orgId,
|
||||
actorAuthMethod!,
|
||||
orgId
|
||||
);
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
OrgPermissionRelayActions.CreateRelays,
|
||||
OrgPermissionSubjects.Relay
|
||||
);
|
||||
|
||||
relay = await relayDAL.transaction(async (tx) => {
|
||||
const existingRelay = await relayDAL.findOne(
|
||||
{
|
||||
@@ -995,9 +1050,75 @@ export const relayServiceFactory = ({
|
||||
});
|
||||
};
|
||||
|
||||
const getRelays = async ({
|
||||
actorId,
|
||||
actor,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
}: {
|
||||
actorId: string;
|
||||
actor: ActorType;
|
||||
actorAuthMethod: ActorAuthMethod;
|
||||
actorOrgId: string;
|
||||
}) => {
|
||||
const { permission } = await permissionService.getOrgPermission(
|
||||
actor,
|
||||
actorId,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionRelayActions.ListRelays, OrgPermissionSubjects.Relay);
|
||||
|
||||
const instanceRelays = await relayDAL.find({
|
||||
orgId: null
|
||||
});
|
||||
|
||||
const orgRelays = await relayDAL.find({
|
||||
orgId: actorOrgId
|
||||
});
|
||||
|
||||
return [...instanceRelays, ...orgRelays];
|
||||
};
|
||||
|
||||
const deleteRelay = async ({
|
||||
id,
|
||||
actorId,
|
||||
actor,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
}: {
|
||||
id: string;
|
||||
actorId: string;
|
||||
actor: ActorType;
|
||||
actorAuthMethod: ActorAuthMethod;
|
||||
actorOrgId: string;
|
||||
}) => {
|
||||
const { permission } = await permissionService.getOrgPermission(
|
||||
actor,
|
||||
actorId,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionRelayActions.DeleteRelays, OrgPermissionSubjects.Relay);
|
||||
|
||||
const relay = await relayDAL.findById(id);
|
||||
if (!relay || relay.orgId !== actorOrgId || relay.orgId === null) {
|
||||
throw new NotFoundError({ message: "Relay not found" });
|
||||
}
|
||||
|
||||
const deletedRelay = await relayDAL.deleteById(id);
|
||||
return deletedRelay;
|
||||
};
|
||||
|
||||
return {
|
||||
registerRelay,
|
||||
getCredentialsForGateway,
|
||||
getCredentialsForClient
|
||||
getCredentialsForClient,
|
||||
getRelays,
|
||||
deleteRelay
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1110,7 +1110,9 @@ export const registerRoutes = async (
|
||||
instanceRelayConfigDAL,
|
||||
orgRelayConfigDAL,
|
||||
relayDAL,
|
||||
kmsService
|
||||
kmsService,
|
||||
licenseService,
|
||||
permissionService
|
||||
});
|
||||
|
||||
const gatewayV2Service = gatewayV2ServiceFactory({
|
||||
|
||||
Reference in New Issue
Block a user