mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 15:27:27 +00:00
Begin developing pki subscriber
This commit is contained in:
Vendored
+3
-1
@@ -72,11 +72,13 @@ import { TIdentityTokenAuthServiceFactory } from "@app/services/identity-token-a
|
|||||||
import { TIdentityUaServiceFactory } from "@app/services/identity-ua/identity-ua-service";
|
import { TIdentityUaServiceFactory } from "@app/services/identity-ua/identity-ua-service";
|
||||||
import { TIntegrationServiceFactory } from "@app/services/integration/integration-service";
|
import { TIntegrationServiceFactory } from "@app/services/integration/integration-service";
|
||||||
import { TIntegrationAuthServiceFactory } from "@app/services/integration-auth/integration-auth-service";
|
import { TIntegrationAuthServiceFactory } from "@app/services/integration-auth/integration-auth-service";
|
||||||
|
import { TMicrosoftTeamsServiceFactory } from "@app/services/microsoft-teams/microsoft-teams-service";
|
||||||
import { TOrgRoleServiceFactory } from "@app/services/org/org-role-service";
|
import { TOrgRoleServiceFactory } from "@app/services/org/org-role-service";
|
||||||
import { TOrgServiceFactory } from "@app/services/org/org-service";
|
import { TOrgServiceFactory } from "@app/services/org/org-service";
|
||||||
import { TOrgAdminServiceFactory } from "@app/services/org-admin/org-admin-service";
|
import { TOrgAdminServiceFactory } from "@app/services/org-admin/org-admin-service";
|
||||||
import { TPkiAlertServiceFactory } from "@app/services/pki-alert/pki-alert-service";
|
import { TPkiAlertServiceFactory } from "@app/services/pki-alert/pki-alert-service";
|
||||||
import { TPkiCollectionServiceFactory } from "@app/services/pki-collection/pki-collection-service";
|
import { TPkiCollectionServiceFactory } from "@app/services/pki-collection/pki-collection-service";
|
||||||
|
import { TPkiSubscriberServiceFactory } from "@app/services/pki-subscriber/pki-subscriber-service";
|
||||||
import { TProjectServiceFactory } from "@app/services/project/project-service";
|
import { TProjectServiceFactory } from "@app/services/project/project-service";
|
||||||
import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service";
|
import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service";
|
||||||
import { TProjectEnvServiceFactory } from "@app/services/project-env/project-env-service";
|
import { TProjectEnvServiceFactory } from "@app/services/project-env/project-env-service";
|
||||||
@@ -101,7 +103,6 @@ import { TUserServiceFactory } from "@app/services/user/user-service";
|
|||||||
import { TUserEngagementServiceFactory } from "@app/services/user-engagement/user-engagement-service";
|
import { TUserEngagementServiceFactory } from "@app/services/user-engagement/user-engagement-service";
|
||||||
import { TWebhookServiceFactory } from "@app/services/webhook/webhook-service";
|
import { TWebhookServiceFactory } from "@app/services/webhook/webhook-service";
|
||||||
import { TWorkflowIntegrationServiceFactory } from "@app/services/workflow-integration/workflow-integration-service";
|
import { TWorkflowIntegrationServiceFactory } from "@app/services/workflow-integration/workflow-integration-service";
|
||||||
import { TMicrosoftTeamsServiceFactory } from "@app/services/microsoft-teams/microsoft-teams-service";
|
|
||||||
|
|
||||||
declare module "@fastify/request-context" {
|
declare module "@fastify/request-context" {
|
||||||
interface RequestContextData {
|
interface RequestContextData {
|
||||||
@@ -220,6 +221,7 @@ declare module "fastify" {
|
|||||||
certificateAuthorityCrl: TCertificateAuthorityCrlServiceFactory;
|
certificateAuthorityCrl: TCertificateAuthorityCrlServiceFactory;
|
||||||
certificateEst: TCertificateEstServiceFactory;
|
certificateEst: TCertificateEstServiceFactory;
|
||||||
pkiCollection: TPkiCollectionServiceFactory;
|
pkiCollection: TPkiCollectionServiceFactory;
|
||||||
|
pkiSubscriber: TPkiSubscriberServiceFactory;
|
||||||
secretScanning: TSecretScanningServiceFactory;
|
secretScanning: TSecretScanningServiceFactory;
|
||||||
license: TLicenseServiceFactory;
|
license: TLicenseServiceFactory;
|
||||||
trustedIp: TTrustedIpServiceFactory;
|
trustedIp: TTrustedIpServiceFactory;
|
||||||
|
|||||||
Vendored
+8
@@ -209,6 +209,9 @@ import {
|
|||||||
TPkiCollections,
|
TPkiCollections,
|
||||||
TPkiCollectionsInsert,
|
TPkiCollectionsInsert,
|
||||||
TPkiCollectionsUpdate,
|
TPkiCollectionsUpdate,
|
||||||
|
TPkiSubscribers,
|
||||||
|
TPkiSubscribersInsert,
|
||||||
|
TPkiSubscribersUpdate,
|
||||||
TProjectBots,
|
TProjectBots,
|
||||||
TProjectBotsInsert,
|
TProjectBotsInsert,
|
||||||
TProjectBotsUpdate,
|
TProjectBotsUpdate,
|
||||||
@@ -559,6 +562,11 @@ declare module "knex/types/tables" {
|
|||||||
TPkiCollectionItemsInsert,
|
TPkiCollectionItemsInsert,
|
||||||
TPkiCollectionItemsUpdate
|
TPkiCollectionItemsUpdate
|
||||||
>;
|
>;
|
||||||
|
[TableName.PkiSubscriber]: KnexOriginal.CompositeTableType<
|
||||||
|
TPkiSubscribers,
|
||||||
|
TPkiSubscribersInsert,
|
||||||
|
TPkiSubscribersUpdate
|
||||||
|
>;
|
||||||
[TableName.UserGroupMembership]: KnexOriginal.CompositeTableType<
|
[TableName.UserGroupMembership]: KnexOriginal.CompositeTableType<
|
||||||
TUserGroupMembership,
|
TUserGroupMembership,
|
||||||
TUserGroupMembershipInsert,
|
TUserGroupMembershipInsert,
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
if (!(await knex.schema.hasTable(TableName.PkiSubscriber))) {
|
||||||
|
await knex.schema.createTable(TableName.PkiSubscriber, (t) => {
|
||||||
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
|
t.timestamps(true, true, true);
|
||||||
|
t.string("projectId").notNullable();
|
||||||
|
t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
|
||||||
|
t.uuid("caId").notNullable();
|
||||||
|
t.foreign("caId").references("id").inTable(TableName.CertificateAuthority).onDelete("CASCADE");
|
||||||
|
t.string("name").notNullable();
|
||||||
|
t.string("commonName").notNullable();
|
||||||
|
t.specificType("subjectAlternativeNames", "text[]").notNullable();
|
||||||
|
t.string("ttl").notNullable();
|
||||||
|
t.specificType("keyUsages", "text[]").notNullable();
|
||||||
|
t.specificType("extendedKeyUsages", "text[]").notNullable();
|
||||||
|
t.unique(["projectId", "name"]);
|
||||||
|
});
|
||||||
|
await createOnUpdateTrigger(knex, TableName.PkiSubscriber);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
await knex.schema.dropTableIfExists(TableName.PkiSubscriber);
|
||||||
|
await dropOnUpdateTrigger(knex, TableName.PkiSubscriber);
|
||||||
|
}
|
||||||
@@ -69,6 +69,7 @@ export * from "./organizations";
|
|||||||
export * from "./pki-alerts";
|
export * from "./pki-alerts";
|
||||||
export * from "./pki-collection-items";
|
export * from "./pki-collection-items";
|
||||||
export * from "./pki-collections";
|
export * from "./pki-collections";
|
||||||
|
export * from "./pki-subscribers";
|
||||||
export * from "./project-bots";
|
export * from "./project-bots";
|
||||||
export * from "./project-environments";
|
export * from "./project-environments";
|
||||||
export * from "./project-gateways";
|
export * from "./project-gateways";
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ export enum TableName {
|
|||||||
CertificateBody = "certificate_bodies",
|
CertificateBody = "certificate_bodies",
|
||||||
CertificateSecret = "certificate_secrets",
|
CertificateSecret = "certificate_secrets",
|
||||||
CertificateTemplate = "certificate_templates",
|
CertificateTemplate = "certificate_templates",
|
||||||
|
PkiSubscriber = "pki_subscribers",
|
||||||
PkiAlert = "pki_alerts",
|
PkiAlert = "pki_alerts",
|
||||||
PkiCollection = "pki_collections",
|
PkiCollection = "pki_collections",
|
||||||
PkiCollectionItem = "pki_collection_items",
|
PkiCollectionItem = "pki_collection_items",
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
// Code generated by automation script, DO NOT EDIT.
|
||||||
|
// Automated by pulling database and generating zod schema
|
||||||
|
// To update. Just run npm run generate:schema
|
||||||
|
// Written by akhilmhdh.
|
||||||
|
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { TImmutableDBKeys } from "./models";
|
||||||
|
|
||||||
|
export const PkiSubscribersSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date(),
|
||||||
|
projectId: z.string(),
|
||||||
|
caId: z.string().uuid(),
|
||||||
|
name: z.string(),
|
||||||
|
commonName: z.string(),
|
||||||
|
subjectAlternativeNames: z.string().array(),
|
||||||
|
ttl: z.string(),
|
||||||
|
keyUsages: z.string().array(),
|
||||||
|
extendedKeyUsages: z.string().array()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TPkiSubscribers = z.infer<typeof PkiSubscribersSchema>;
|
||||||
|
export type TPkiSubscribersInsert = Omit<z.input<typeof PkiSubscribersSchema>, TImmutableDBKeys>;
|
||||||
|
export type TPkiSubscribersUpdate = Partial<Omit<z.input<typeof PkiSubscribersSchema>, TImmutableDBKeys>>;
|
||||||
@@ -27,7 +27,7 @@ export const ProjectsSchema = z.object({
|
|||||||
description: z.string().nullable().optional(),
|
description: z.string().nullable().optional(),
|
||||||
type: z.string(),
|
type: z.string(),
|
||||||
enforceCapitalization: z.boolean().default(false),
|
enforceCapitalization: z.boolean().default(false),
|
||||||
hasDeleteProtection: z.boolean().default(true).nullable().optional()
|
hasDeleteProtection: z.boolean().default(false).nullable().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TProjects = z.infer<typeof ProjectsSchema>;
|
export type TProjects = z.infer<typeof ProjectsSchema>;
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ export const registerSshHostRouter = async (server: FastifyZodProvider) => {
|
|||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const host = await server.services.sshHost.getSshHost({
|
const host = await server.services.sshHost.getSshHostById({
|
||||||
sshHostId: req.params.sshHostId,
|
sshHostId: req.params.sshHostId,
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import {
|
|||||||
TUpdateSecretSyncDTO
|
TUpdateSecretSyncDTO
|
||||||
} from "@app/services/secret-sync/secret-sync-types";
|
} from "@app/services/secret-sync/secret-sync-types";
|
||||||
import { WorkflowIntegration } from "@app/services/workflow-integration/workflow-integration-types";
|
import { WorkflowIntegration } from "@app/services/workflow-integration/workflow-integration-types";
|
||||||
|
import { CertKeyUsage, CertExtendedKeyUsage } from "@app/services/certificate/certificate-types";
|
||||||
|
|
||||||
import { KmipPermission } from "../kmip/kmip-enum";
|
import { KmipPermission } from "../kmip/kmip-enum";
|
||||||
import { ApprovalStatus } from "../secret-approval-request/secret-approval-request-types";
|
import { ApprovalStatus } from "../secret-approval-request/secret-approval-request-types";
|
||||||
@@ -235,6 +236,10 @@ export enum EventType {
|
|||||||
GET_PKI_COLLECTION_ITEMS = "get-pki-collection-items",
|
GET_PKI_COLLECTION_ITEMS = "get-pki-collection-items",
|
||||||
ADD_PKI_COLLECTION_ITEM = "add-pki-collection-item",
|
ADD_PKI_COLLECTION_ITEM = "add-pki-collection-item",
|
||||||
DELETE_PKI_COLLECTION_ITEM = "delete-pki-collection-item",
|
DELETE_PKI_COLLECTION_ITEM = "delete-pki-collection-item",
|
||||||
|
CREATE_PKI_SUBSCRIBER = "create-pki-subscriber",
|
||||||
|
UPDATE_PKI_SUBSCRIBER = "update-pki-subscriber",
|
||||||
|
DELETE_PKI_SUBSCRIBER = "delete-pki-subscriber",
|
||||||
|
GET_PKI_SUBSCRIBER = "get-pki-subscriber",
|
||||||
CREATE_KMS = "create-kms",
|
CREATE_KMS = "create-kms",
|
||||||
UPDATE_KMS = "update-kms",
|
UPDATE_KMS = "update-kms",
|
||||||
DELETE_KMS = "delete-kms",
|
DELETE_KMS = "delete-kms",
|
||||||
@@ -1879,6 +1884,48 @@ interface DeletePkiCollectionItem {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface CreatePkiSubscriber {
|
||||||
|
type: EventType.CREATE_PKI_SUBSCRIBER;
|
||||||
|
metadata: {
|
||||||
|
pkiSubscriberId: string;
|
||||||
|
caId: string;
|
||||||
|
name: string;
|
||||||
|
commonName: string;
|
||||||
|
ttl: string;
|
||||||
|
subjectAlternativeNames: string[];
|
||||||
|
keyUsages: CertKeyUsage[];
|
||||||
|
extendedKeyUsages: CertExtendedKeyUsage[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UpdatePkiSubscriber {
|
||||||
|
type: EventType.UPDATE_PKI_SUBSCRIBER;
|
||||||
|
metadata: {
|
||||||
|
pkiSubscriberId: string;
|
||||||
|
caId?: string;
|
||||||
|
name?: string;
|
||||||
|
commonName?: string;
|
||||||
|
ttl?: string;
|
||||||
|
subjectAlternativeNames?: string[];
|
||||||
|
keyUsages?: CertKeyUsage[];
|
||||||
|
extendedKeyUsages?: CertExtendedKeyUsage[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DeletePkiSubscriber {
|
||||||
|
type: EventType.DELETE_PKI_SUBSCRIBER;
|
||||||
|
metadata: {
|
||||||
|
pkiSubscriberId: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GetPkiSubscriber {
|
||||||
|
type: EventType.GET_PKI_SUBSCRIBER;
|
||||||
|
metadata: {
|
||||||
|
pkiSubscriberId: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
interface CreateKmsEvent {
|
interface CreateKmsEvent {
|
||||||
type: EventType.CREATE_KMS;
|
type: EventType.CREATE_KMS;
|
||||||
metadata: {
|
metadata: {
|
||||||
@@ -2835,6 +2882,10 @@ export type Event =
|
|||||||
| GetPkiCollectionItems
|
| GetPkiCollectionItems
|
||||||
| AddPkiCollectionItem
|
| AddPkiCollectionItem
|
||||||
| DeletePkiCollectionItem
|
| DeletePkiCollectionItem
|
||||||
|
| CreatePkiSubscriber
|
||||||
|
| UpdatePkiSubscriber
|
||||||
|
| DeletePkiSubscriber
|
||||||
|
| GetPkiSubscriber
|
||||||
| CreateKmsEvent
|
| CreateKmsEvent
|
||||||
| UpdateKmsEvent
|
| UpdateKmsEvent
|
||||||
| DeleteKmsEvent
|
| DeleteKmsEvent
|
||||||
|
|||||||
@@ -79,6 +79,15 @@ export enum ProjectPermissionSshHostActions {
|
|||||||
IssueHostCert = "issue-host-cert"
|
IssueHostCert = "issue-host-cert"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum ProjectPermissionPkiSubscriberActions {
|
||||||
|
Read = "read",
|
||||||
|
Create = "create",
|
||||||
|
Edit = "edit",
|
||||||
|
Delete = "delete",
|
||||||
|
IssueCert = "issue-cert",
|
||||||
|
SignCert = "sign-cert"
|
||||||
|
}
|
||||||
|
|
||||||
export enum ProjectPermissionSecretSyncActions {
|
export enum ProjectPermissionSecretSyncActions {
|
||||||
Read = "read",
|
Read = "read",
|
||||||
Create = "create",
|
Create = "create",
|
||||||
@@ -135,6 +144,7 @@ export enum ProjectPermissionSub {
|
|||||||
SshCertificateTemplates = "ssh-certificate-templates",
|
SshCertificateTemplates = "ssh-certificate-templates",
|
||||||
SshHosts = "ssh-hosts",
|
SshHosts = "ssh-hosts",
|
||||||
SshHostGroups = "ssh-host-groups",
|
SshHostGroups = "ssh-host-groups",
|
||||||
|
PkiSubscribers = "pki-subscribers",
|
||||||
PkiAlerts = "pki-alerts",
|
PkiAlerts = "pki-alerts",
|
||||||
PkiCollections = "pki-collections",
|
PkiCollections = "pki-collections",
|
||||||
Kms = "kms",
|
Kms = "kms",
|
||||||
@@ -241,6 +251,7 @@ export type ProjectPermissionSet =
|
|||||||
ProjectPermissionSshHostActions,
|
ProjectPermissionSshHostActions,
|
||||||
ProjectPermissionSub.SshHosts | (ForcedSubject<ProjectPermissionSub.SshHosts> & SshHostSubjectFields)
|
ProjectPermissionSub.SshHosts | (ForcedSubject<ProjectPermissionSub.SshHosts> & SshHostSubjectFields)
|
||||||
]
|
]
|
||||||
|
| [ProjectPermissionPkiSubscriberActions, ProjectPermissionSub.PkiSubscribers] // (dangtony98): TODO: update
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.SshHostGroups]
|
| [ProjectPermissionActions, ProjectPermissionSub.SshHostGroups]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.PkiAlerts]
|
| [ProjectPermissionActions, ProjectPermissionSub.PkiAlerts]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.PkiCollections]
|
| [ProjectPermissionActions, ProjectPermissionSub.PkiCollections]
|
||||||
@@ -719,6 +730,17 @@ const buildAdminPermissionRules = () => {
|
|||||||
ProjectPermissionSub.SshHosts
|
ProjectPermissionSub.SshHosts
|
||||||
);
|
);
|
||||||
|
|
||||||
|
can(
|
||||||
|
[
|
||||||
|
ProjectPermissionPkiSubscriberActions.Read,
|
||||||
|
ProjectPermissionPkiSubscriberActions.Create,
|
||||||
|
ProjectPermissionPkiSubscriberActions.Edit,
|
||||||
|
ProjectPermissionPkiSubscriberActions.Delete,
|
||||||
|
ProjectPermissionPkiSubscriberActions.IssueCert
|
||||||
|
],
|
||||||
|
ProjectPermissionSub.PkiSubscribers
|
||||||
|
);
|
||||||
|
|
||||||
can(
|
can(
|
||||||
[
|
[
|
||||||
ProjectPermissionMemberActions.Create,
|
ProjectPermissionMemberActions.Create,
|
||||||
@@ -977,6 +999,7 @@ const buildMemberPermissionRules = () => {
|
|||||||
|
|
||||||
can([ProjectPermissionActions.Read], ProjectPermissionSub.PkiAlerts);
|
can([ProjectPermissionActions.Read], ProjectPermissionSub.PkiAlerts);
|
||||||
can([ProjectPermissionActions.Read], ProjectPermissionSub.PkiCollections);
|
can([ProjectPermissionActions.Read], ProjectPermissionSub.PkiCollections);
|
||||||
|
can([ProjectPermissionPkiSubscriberActions.Read], ProjectPermissionSub.PkiSubscribers);
|
||||||
|
|
||||||
can([ProjectPermissionActions.Read], ProjectPermissionSub.SshCertificates);
|
can([ProjectPermissionActions.Read], ProjectPermissionSub.SshCertificates);
|
||||||
can([ProjectPermissionActions.Create], ProjectPermissionSub.SshCertificates);
|
can([ProjectPermissionActions.Create], ProjectPermissionSub.SshCertificates);
|
||||||
|
|||||||
@@ -324,7 +324,7 @@ export const sshHostServiceFactory = ({
|
|||||||
return host;
|
return host;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getSshHost = async ({ sshHostId, actorId, actorAuthMethod, actor, actorOrgId }: TGetSshHostDTO) => {
|
const getSshHostById = async ({ sshHostId, actorId, actorAuthMethod, actor, actorOrgId }: TGetSshHostDTO) => {
|
||||||
const host = await sshHostDAL.findSshHostByIdWithLoginMappings(sshHostId);
|
const host = await sshHostDAL.findSshHostByIdWithLoginMappings(sshHostId);
|
||||||
if (!host) {
|
if (!host) {
|
||||||
throw new NotFoundError({
|
throw new NotFoundError({
|
||||||
@@ -616,7 +616,7 @@ export const sshHostServiceFactory = ({
|
|||||||
createSshHost,
|
createSshHost,
|
||||||
updateSshHost,
|
updateSshHost,
|
||||||
deleteSshHost,
|
deleteSshHost,
|
||||||
getSshHost,
|
getSshHostById,
|
||||||
issueSshHostUserCert,
|
issueSshHostUserCert,
|
||||||
issueSshHostHostCert,
|
issueSshHostHostCert,
|
||||||
getSshHostUserCaPk,
|
getSshHostUserCaPk,
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ export enum ApiDocsTags {
|
|||||||
PkiCertificateTemplates = "PKI Certificate Templates",
|
PkiCertificateTemplates = "PKI Certificate Templates",
|
||||||
PkiCertificateCollections = "PKI Certificate Collections",
|
PkiCertificateCollections = "PKI Certificate Collections",
|
||||||
PkiAlerting = "PKI Alerting",
|
PkiAlerting = "PKI Alerting",
|
||||||
|
PkiSubscribers = "PKI Subscribers",
|
||||||
SshCertificates = "SSH Certificates",
|
SshCertificates = "SSH Certificates",
|
||||||
SshCertificateAuthorities = "SSH Certificate Authorities",
|
SshCertificateAuthorities = "SSH Certificate Authorities",
|
||||||
SshCertificateTemplates = "SSH Certificate Templates",
|
SshCertificateTemplates = "SSH Certificate Templates",
|
||||||
@@ -595,6 +596,9 @@ export const PROJECTS = {
|
|||||||
commonName: "The common name of the certificate to filter by.",
|
commonName: "The common name of the certificate to filter by.",
|
||||||
offset: "The offset to start from. If you enter 10, it will start from the 10th certificate.",
|
offset: "The offset to start from. If you enter 10, it will start from the 10th certificate.",
|
||||||
limit: "The number of certificates to return."
|
limit: "The number of certificates to return."
|
||||||
|
},
|
||||||
|
LIST_PKI_SUBSCRIBERS: {
|
||||||
|
projectId: "The ID of the project to list PKI subscribers for."
|
||||||
}
|
}
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
@@ -1686,6 +1690,46 @@ export const ALERTS = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const PKI_SUBSCRIBERS = {
|
||||||
|
GET: {
|
||||||
|
subscriberId: "The ID of the PKI subscriber to get."
|
||||||
|
},
|
||||||
|
CREATE: {
|
||||||
|
projectId: "The ID of the project to create the PKI subscriber in.",
|
||||||
|
caId: "The ID of the CA that will issue certificates for the PKI subscriber.",
|
||||||
|
name: "The name of the PKI subscriber.",
|
||||||
|
commonName: "The common name (CN) to be used on certificates issued for this subscriber.",
|
||||||
|
ttl: "The time to live for the certificates issued for this subscriber such as 1m, 1h, 1d, 1y, ...",
|
||||||
|
subjectAlternativeNames:
|
||||||
|
"A list of Subject Alternative Names (SANs) to be used on certificates issued for this subscriber; these can be host names or email addresses.",
|
||||||
|
keyUsages: "The key usage extension to be used on certificates issued for this subscriber.",
|
||||||
|
extendedKeyUsages: "The extended key usage extension to be used on certificates issued for this subscriber."
|
||||||
|
},
|
||||||
|
UPDATE: {
|
||||||
|
subscriberId: "The ID of the PKI subscriber to update.",
|
||||||
|
caId: "The ID of the CA that will issue certificates for the PKI subscriber to update to.",
|
||||||
|
name: "The name of the PKI subscriber to update to.",
|
||||||
|
commonName: "The common name (CN) to be used on certificates issued for this subscriber to update to.",
|
||||||
|
ttl: "The time to live for the certificates issued for this subscriber such as 1m, 1h, 1d, 1y, ...",
|
||||||
|
subjectAlternativeNames:
|
||||||
|
"A comma-delimited list of Subject Alternative Names (SANs) to be used on certificates issued for this subscriber; these can be host names or email addresses.",
|
||||||
|
keyUsages: "The key usage extension to be used on certificates issued for this subscriber to update to.",
|
||||||
|
extendedKeyUsages:
|
||||||
|
"The extended key usage extension to be used on certificates issued for this subscriber to update to."
|
||||||
|
},
|
||||||
|
DELETE: {
|
||||||
|
subscriberId: "The ID of the PKI subscriber to delete."
|
||||||
|
},
|
||||||
|
ISSUE_CERT: {
|
||||||
|
subscriberId: "The ID of the PKI subscriber to issue the certificate for.",
|
||||||
|
certificate: "The issued certificate.",
|
||||||
|
issuingCaCertificate: "The certificate of the issuing CA.",
|
||||||
|
certificateChain: "The certificate chain of the issued certificate.",
|
||||||
|
privateKey: "The private key of the issued certificate.",
|
||||||
|
serialNumber: "The serial number of the issued certificate."
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const PKI_COLLECTIONS = {
|
export const PKI_COLLECTIONS = {
|
||||||
CREATE: {
|
CREATE: {
|
||||||
projectId: "The ID of the project to create the PKI collection in.",
|
projectId: "The ID of the project to create the PKI collection in.",
|
||||||
|
|||||||
@@ -194,6 +194,8 @@ import { pkiAlertServiceFactory } from "@app/services/pki-alert/pki-alert-servic
|
|||||||
import { pkiCollectionDALFactory } from "@app/services/pki-collection/pki-collection-dal";
|
import { pkiCollectionDALFactory } from "@app/services/pki-collection/pki-collection-dal";
|
||||||
import { pkiCollectionItemDALFactory } from "@app/services/pki-collection/pki-collection-item-dal";
|
import { pkiCollectionItemDALFactory } from "@app/services/pki-collection/pki-collection-item-dal";
|
||||||
import { pkiCollectionServiceFactory } from "@app/services/pki-collection/pki-collection-service";
|
import { pkiCollectionServiceFactory } from "@app/services/pki-collection/pki-collection-service";
|
||||||
|
import { pkiSubscriberDALFactory } from "@app/services/pki-subscriber/pki-subscriber-dal";
|
||||||
|
import { pkiSubscriberServiceFactory } from "@app/services/pki-subscriber/pki-subscriber-service";
|
||||||
import { projectDALFactory } from "@app/services/project/project-dal";
|
import { projectDALFactory } from "@app/services/project/project-dal";
|
||||||
import { projectQueueFactory } from "@app/services/project/project-queue";
|
import { projectQueueFactory } from "@app/services/project/project-queue";
|
||||||
import { projectServiceFactory } from "@app/services/project/project-service";
|
import { projectServiceFactory } from "@app/services/project/project-service";
|
||||||
@@ -816,6 +818,7 @@ export const registerRoutes = async (
|
|||||||
const pkiAlertDAL = pkiAlertDALFactory(db);
|
const pkiAlertDAL = pkiAlertDALFactory(db);
|
||||||
const pkiCollectionDAL = pkiCollectionDALFactory(db);
|
const pkiCollectionDAL = pkiCollectionDALFactory(db);
|
||||||
const pkiCollectionItemDAL = pkiCollectionItemDALFactory(db);
|
const pkiCollectionItemDAL = pkiCollectionItemDALFactory(db);
|
||||||
|
const pkiSubscriberDAL = pkiSubscriberDALFactory(db);
|
||||||
|
|
||||||
const certificateService = certificateServiceFactory({
|
const certificateService = certificateServiceFactory({
|
||||||
certificateDAL,
|
certificateDAL,
|
||||||
@@ -945,6 +948,11 @@ export const registerRoutes = async (
|
|||||||
projectDAL
|
projectDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const pkiSubscriberService = pkiSubscriberServiceFactory({
|
||||||
|
pkiSubscriberDAL,
|
||||||
|
permissionService
|
||||||
|
});
|
||||||
|
|
||||||
const projectTemplateService = projectTemplateServiceFactory({
|
const projectTemplateService = projectTemplateServiceFactory({
|
||||||
licenseService,
|
licenseService,
|
||||||
permissionService,
|
permissionService,
|
||||||
@@ -1042,6 +1050,7 @@ export const registerRoutes = async (
|
|||||||
projectRoleDAL,
|
projectRoleDAL,
|
||||||
folderDAL,
|
folderDAL,
|
||||||
licenseService,
|
licenseService,
|
||||||
|
pkiSubscriberDAL,
|
||||||
certificateAuthorityDAL,
|
certificateAuthorityDAL,
|
||||||
certificateDAL,
|
certificateDAL,
|
||||||
pkiAlertDAL,
|
pkiAlertDAL,
|
||||||
@@ -1717,6 +1726,7 @@ export const registerRoutes = async (
|
|||||||
certificateEst: certificateEstService,
|
certificateEst: certificateEstService,
|
||||||
pkiAlert: pkiAlertService,
|
pkiAlert: pkiAlertService,
|
||||||
pkiCollection: pkiCollectionService,
|
pkiCollection: pkiCollectionService,
|
||||||
|
pkiSubscriber: pkiSubscriberService,
|
||||||
secretScanning: secretScanningService,
|
secretScanning: secretScanningService,
|
||||||
license: licenseService,
|
license: licenseService,
|
||||||
trustedIp: trustedIpService,
|
trustedIp: trustedIpService,
|
||||||
|
|||||||
@@ -26,11 +26,13 @@ import { registerIdentityUaRouter } from "./identity-universal-auth-router";
|
|||||||
import { registerIntegrationAuthRouter } from "./integration-auth-router";
|
import { registerIntegrationAuthRouter } from "./integration-auth-router";
|
||||||
import { registerIntegrationRouter } from "./integration-router";
|
import { registerIntegrationRouter } from "./integration-router";
|
||||||
import { registerInviteOrgRouter } from "./invite-org-router";
|
import { registerInviteOrgRouter } from "./invite-org-router";
|
||||||
|
import { registerMicrosoftTeamsRouter } from "./microsoft-teams-router";
|
||||||
import { registerOrgAdminRouter } from "./org-admin-router";
|
import { registerOrgAdminRouter } from "./org-admin-router";
|
||||||
import { registerOrgRouter } from "./organization-router";
|
import { registerOrgRouter } from "./organization-router";
|
||||||
import { registerPasswordRouter } from "./password-router";
|
import { registerPasswordRouter } from "./password-router";
|
||||||
import { registerPkiAlertRouter } from "./pki-alert-router";
|
import { registerPkiAlertRouter } from "./pki-alert-router";
|
||||||
import { registerPkiCollectionRouter } from "./pki-collection-router";
|
import { registerPkiCollectionRouter } from "./pki-collection-router";
|
||||||
|
import { registerPkiSubscriberRouter } from "./pki-subscriber-router";
|
||||||
import { registerProjectEnvRouter } from "./project-env-router";
|
import { registerProjectEnvRouter } from "./project-env-router";
|
||||||
import { registerProjectKeyRouter } from "./project-key-router";
|
import { registerProjectKeyRouter } from "./project-key-router";
|
||||||
import { registerProjectMembershipRouter } from "./project-membership-router";
|
import { registerProjectMembershipRouter } from "./project-membership-router";
|
||||||
@@ -47,7 +49,6 @@ import { registerUserEngagementRouter } from "./user-engagement-router";
|
|||||||
import { registerUserRouter } from "./user-router";
|
import { registerUserRouter } from "./user-router";
|
||||||
import { registerWebhookRouter } from "./webhook-router";
|
import { registerWebhookRouter } from "./webhook-router";
|
||||||
import { registerWorkflowIntegrationRouter } from "./workflow-integration-router";
|
import { registerWorkflowIntegrationRouter } from "./workflow-integration-router";
|
||||||
import { registerMicrosoftTeamsRouter } from "./microsoft-teams-router";
|
|
||||||
|
|
||||||
export const registerV1Routes = async (server: FastifyZodProvider) => {
|
export const registerV1Routes = async (server: FastifyZodProvider) => {
|
||||||
await server.register(registerSsoRouter, { prefix: "/sso" });
|
await server.register(registerSsoRouter, { prefix: "/sso" });
|
||||||
@@ -103,6 +104,7 @@ export const registerV1Routes = async (server: FastifyZodProvider) => {
|
|||||||
await pkiRouter.register(registerCertificateTemplateRouter, { prefix: "/certificate-templates" });
|
await pkiRouter.register(registerCertificateTemplateRouter, { prefix: "/certificate-templates" });
|
||||||
await pkiRouter.register(registerPkiAlertRouter, { prefix: "/alerts" });
|
await pkiRouter.register(registerPkiAlertRouter, { prefix: "/alerts" });
|
||||||
await pkiRouter.register(registerPkiCollectionRouter, { prefix: "/collections" });
|
await pkiRouter.register(registerPkiCollectionRouter, { prefix: "/collections" });
|
||||||
|
await pkiRouter.register(registerPkiSubscriberRouter, { prefix: "/subscribers" });
|
||||||
},
|
},
|
||||||
{ prefix: "/pki" }
|
{ prefix: "/pki" }
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,381 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
|
import { ApiDocsTags, PKI_SUBSCRIBERS } from "@app/lib/api-docs";
|
||||||
|
import { ms } from "@app/lib/ms";
|
||||||
|
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";
|
||||||
|
import { CertExtendedKeyUsage, CertKeyUsage } from "@app/services/certificate/certificate-types";
|
||||||
|
import { validateAltNameField } from "@app/services/certificate-authority/certificate-authority-validators";
|
||||||
|
import { sanitizedPkiSubscriber } from "@app/services/pki-subscriber/pki-subscriber-schema";
|
||||||
|
|
||||||
|
export const registerPkiSubscriberRouter = async (server: FastifyZodProvider) => {
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/:subscriberId",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
tags: [ApiDocsTags.PkiSubscribers],
|
||||||
|
description: "Get PKI Subscriber",
|
||||||
|
params: z.object({
|
||||||
|
subscriberId: z.string().describe(PKI_SUBSCRIBERS.GET.subscriberId)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: sanitizedPkiSubscriber
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const subscriber = await server.services.pkiSubscriber.getPkiSubscriberById({
|
||||||
|
subscriberId: req.params.subscriberId,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
projectId: subscriber.projectId,
|
||||||
|
event: {
|
||||||
|
type: EventType.GET_PKI_SUBSCRIBER,
|
||||||
|
metadata: {
|
||||||
|
pkiSubscriberId: subscriber.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return subscriber;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "POST",
|
||||||
|
url: "/",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
tags: [ApiDocsTags.PkiSubscribers],
|
||||||
|
description: "Create PKI Subscriber",
|
||||||
|
body: z.object({
|
||||||
|
projectId: z.string().trim().describe(PKI_SUBSCRIBERS.CREATE.projectId),
|
||||||
|
caId: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.uuid("CA ID must be a valid UUID")
|
||||||
|
.min(1, "CA ID is required")
|
||||||
|
.describe(PKI_SUBSCRIBERS.CREATE.caId),
|
||||||
|
name: slugSchema({ min: 1, max: 64, field: "name" }).describe(PKI_SUBSCRIBERS.CREATE.name),
|
||||||
|
commonName: z.string().trim().min(1).describe(PKI_SUBSCRIBERS.CREATE.commonName),
|
||||||
|
ttl: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.refine((val) => ms(val) > 0, "TTL must be a positive number")
|
||||||
|
.describe(PKI_SUBSCRIBERS.CREATE.ttl),
|
||||||
|
subjectAlternativeNames: validateAltNameField
|
||||||
|
.array()
|
||||||
|
.default([])
|
||||||
|
.transform((arr) => Array.from(new Set(arr)))
|
||||||
|
.describe(PKI_SUBSCRIBERS.CREATE.subjectAlternativeNames),
|
||||||
|
keyUsages: z
|
||||||
|
.nativeEnum(CertKeyUsage)
|
||||||
|
.array()
|
||||||
|
.default([CertKeyUsage.DIGITAL_SIGNATURE, CertKeyUsage.KEY_ENCIPHERMENT])
|
||||||
|
.transform((arr) => Array.from(new Set(arr)))
|
||||||
|
.describe(PKI_SUBSCRIBERS.CREATE.keyUsages),
|
||||||
|
extendedKeyUsages: z
|
||||||
|
.nativeEnum(CertExtendedKeyUsage)
|
||||||
|
.array()
|
||||||
|
.default([])
|
||||||
|
.transform((arr) => Array.from(new Set(arr)))
|
||||||
|
.describe(PKI_SUBSCRIBERS.CREATE.extendedKeyUsages)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: sanitizedPkiSubscriber
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const subscriber = await server.services.pkiSubscriber.createPkiSubscriber({
|
||||||
|
...req.body,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
projectId: subscriber.projectId,
|
||||||
|
event: {
|
||||||
|
type: EventType.CREATE_PKI_SUBSCRIBER,
|
||||||
|
metadata: {
|
||||||
|
pkiSubscriberId: subscriber.id,
|
||||||
|
caId: subscriber.caId,
|
||||||
|
name: subscriber.name,
|
||||||
|
commonName: subscriber.commonName,
|
||||||
|
ttl: subscriber.ttl,
|
||||||
|
subjectAlternativeNames: subscriber.subjectAlternativeNames,
|
||||||
|
keyUsages: subscriber.keyUsages as CertKeyUsage[],
|
||||||
|
extendedKeyUsages: subscriber.extendedKeyUsages as CertExtendedKeyUsage[]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return subscriber;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "PATCH",
|
||||||
|
url: "/:subscriberId",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
tags: [ApiDocsTags.PkiSubscribers],
|
||||||
|
description: "Update PKI Subscriber",
|
||||||
|
params: z.object({
|
||||||
|
subscriberId: z.string().trim().describe(PKI_SUBSCRIBERS.UPDATE.subscriberId)
|
||||||
|
}),
|
||||||
|
body: z.object({
|
||||||
|
caId: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.uuid("CA ID must be a valid UUID")
|
||||||
|
.min(1, "CA ID is required")
|
||||||
|
.optional()
|
||||||
|
.describe(PKI_SUBSCRIBERS.UPDATE.caId),
|
||||||
|
name: slugSchema({ min: 1, max: 64, field: "name" }).describe(PKI_SUBSCRIBERS.UPDATE.name).optional(),
|
||||||
|
commonName: z.string().trim().min(1).describe(PKI_SUBSCRIBERS.UPDATE.commonName).optional(),
|
||||||
|
subjectAlternativeNames: validateAltNameField
|
||||||
|
.array()
|
||||||
|
.optional()
|
||||||
|
.describe(PKI_SUBSCRIBERS.UPDATE.subjectAlternativeNames),
|
||||||
|
ttl: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.refine((val) => ms(val) > 0, "TTL must be a positive number")
|
||||||
|
.optional()
|
||||||
|
.describe(PKI_SUBSCRIBERS.UPDATE.ttl),
|
||||||
|
keyUsages: z
|
||||||
|
.nativeEnum(CertKeyUsage)
|
||||||
|
.array()
|
||||||
|
.transform((arr) => Array.from(new Set(arr)))
|
||||||
|
.optional()
|
||||||
|
.describe(PKI_SUBSCRIBERS.UPDATE.keyUsages),
|
||||||
|
extendedKeyUsages: z
|
||||||
|
.nativeEnum(CertExtendedKeyUsage)
|
||||||
|
.array()
|
||||||
|
.transform((arr) => Array.from(new Set(arr)))
|
||||||
|
.optional()
|
||||||
|
.describe(PKI_SUBSCRIBERS.UPDATE.extendedKeyUsages)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: sanitizedPkiSubscriber
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const subscriber = await server.services.pkiSubscriber.updatePkiSubscriber({
|
||||||
|
subscriberId: req.params.subscriberId,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
...req.body
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
projectId: subscriber.projectId,
|
||||||
|
event: {
|
||||||
|
type: EventType.UPDATE_PKI_SUBSCRIBER,
|
||||||
|
metadata: {
|
||||||
|
pkiSubscriberId: subscriber.id,
|
||||||
|
caId: subscriber.caId,
|
||||||
|
name: subscriber.name,
|
||||||
|
commonName: subscriber.commonName,
|
||||||
|
ttl: subscriber.ttl,
|
||||||
|
subjectAlternativeNames: subscriber.subjectAlternativeNames,
|
||||||
|
keyUsages: subscriber.keyUsages as CertKeyUsage[],
|
||||||
|
extendedKeyUsages: subscriber.extendedKeyUsages as CertExtendedKeyUsage[]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return subscriber;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "DELETE",
|
||||||
|
url: "/:subscriberId",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
tags: [ApiDocsTags.PkiSubscribers],
|
||||||
|
description: "Delete PKI Subscriber",
|
||||||
|
params: z.object({
|
||||||
|
subscriberId: z.string().describe(PKI_SUBSCRIBERS.DELETE.subscriberId)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: sanitizedPkiSubscriber
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const subscriber = await server.services.pkiSubscriber.deletePkiSubscriber({
|
||||||
|
subscriberId: req.params.subscriberId,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
projectId: subscriber.projectId,
|
||||||
|
event: {
|
||||||
|
type: EventType.DELETE_PKI_SUBSCRIBER,
|
||||||
|
metadata: {
|
||||||
|
pkiSubscriberId: subscriber.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return subscriber;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "POST",
|
||||||
|
url: "/:subscriberId/issue-certificate",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
tags: [ApiDocsTags.PkiSubscribers],
|
||||||
|
description: "Issue certificate",
|
||||||
|
params: z.object({
|
||||||
|
subscriberId: z.string().describe(PKI_SUBSCRIBERS.ISSUE_CERT.subscriberId)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
certificate: z.string().trim().describe(PKI_SUBSCRIBERS.ISSUE_CERT.certificate),
|
||||||
|
issuingCaCertificate: z.string().trim().describe(PKI_SUBSCRIBERS.ISSUE_CERT.issuingCaCertificate),
|
||||||
|
certificateChain: z.string().trim().describe(PKI_SUBSCRIBERS.ISSUE_CERT.certificateChain),
|
||||||
|
privateKey: z.string().trim().describe(PKI_SUBSCRIBERS.ISSUE_CERT.privateKey),
|
||||||
|
serialNumber: z.string().trim().describe(PKI_SUBSCRIBERS.ISSUE_CERT.serialNumber)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
// TODO: reuse issueCertFromCa fn (or not since we are adding support for external CAs?)
|
||||||
|
// const { serialNumber, signedPublicKey, privateKey, publicKey, keyAlgorithm, host, principals } =
|
||||||
|
// await server.services.pkiSubscriber.issuePkiSubscriberCertificate({
|
||||||
|
// subscriberId: req.params.subscriberId,
|
||||||
|
// actor: req.permission.type,
|
||||||
|
// actorId: req.permission.id,
|
||||||
|
// actorAuthMethod: req.permission.authMethod,
|
||||||
|
// actorOrgId: req.permission.orgId
|
||||||
|
// });
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
orgId: req.permission.orgId,
|
||||||
|
event: {
|
||||||
|
type: EventType.ISSUE_SSH_HOST_USER_CERT,
|
||||||
|
metadata: {
|
||||||
|
sshHostId: req.params.sshHostId,
|
||||||
|
hostname: host.hostname,
|
||||||
|
loginUser: req.body.loginUser,
|
||||||
|
principals,
|
||||||
|
ttl: host.userCertTtl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
serialNumber,
|
||||||
|
signedKey: signedPublicKey,
|
||||||
|
privateKey,
|
||||||
|
publicKey,
|
||||||
|
keyAlgorithm
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "POST",
|
||||||
|
url: "/:subscriberId/sign-certificate",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
tags: [ApiDocsTags.PkiSubscribers],
|
||||||
|
description: "Sign certificate",
|
||||||
|
params: z.object({
|
||||||
|
subscriberId: z.string().describe(PKI_SUBSCRIBERS.ISSUE_CERT.subscriberId)
|
||||||
|
}),
|
||||||
|
body: z.object({
|
||||||
|
csr: z.string().trim().min(1).
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
certificate: z.string().trim().describe(PKI_SUBSCRIBERS.ISSUE_CERT.certificate),
|
||||||
|
issuingCaCertificate: z.string().trim().describe(PKI_SUBSCRIBERS.ISSUE_CERT.issuingCaCertificate),
|
||||||
|
certificateChain: z.string().trim().describe(PKI_SUBSCRIBERS.ISSUE_CERT.certificateChain),
|
||||||
|
serialNumber: z.string().trim().describe(PKI_SUBSCRIBERS.ISSUE_CERT.serialNumber)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
// TODO: reuse issueCertFromCa fn (or not since we are adding support for external CAs?)
|
||||||
|
const { serialNumber, signedPublicKey, privateKey, publicKey, keyAlgorithm, host, principals } =
|
||||||
|
await server.services.pkiSubscriber.issuePkiSubscriberCertificate({
|
||||||
|
subscriberId: req.params.subscriberId,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
// await server.services.auditLog.createAuditLog({
|
||||||
|
// ...req.auditLogInfo,
|
||||||
|
// orgId: req.permission.orgId,
|
||||||
|
// event: {
|
||||||
|
// type: EventType.ISSUE_SSH_HOST_USER_CERT,
|
||||||
|
// metadata: {
|
||||||
|
// sshHostId: req.params.sshHostId,
|
||||||
|
// hostname: host.hostname,
|
||||||
|
// loginUser: req.body.loginUser,
|
||||||
|
// principals,
|
||||||
|
// ttl: host.userCertTtl
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
return {
|
||||||
|
serialNumber,
|
||||||
|
signedKey: signedPublicKey,
|
||||||
|
publicKey,
|
||||||
|
keyAlgorithm
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -16,6 +16,7 @@ import { sanitizedSshCertificateTemplate } from "@app/ee/services/ssh-certificat
|
|||||||
import { loginMappingSchema, sanitizedSshHost } from "@app/ee/services/ssh-host/ssh-host-schema";
|
import { loginMappingSchema, sanitizedSshHost } from "@app/ee/services/ssh-host/ssh-host-schema";
|
||||||
import { LoginMappingSource } from "@app/ee/services/ssh-host/ssh-host-types";
|
import { LoginMappingSource } from "@app/ee/services/ssh-host/ssh-host-types";
|
||||||
import { sanitizedSshHostGroup } from "@app/ee/services/ssh-host-group/ssh-host-group-schema";
|
import { sanitizedSshHostGroup } from "@app/ee/services/ssh-host-group/ssh-host-group-schema";
|
||||||
|
import { sanitizedPkiSubscriber } from "@app/services/pki-subscriber/pki-subscriber-schema";
|
||||||
import { ApiDocsTags, PROJECTS } from "@app/lib/api-docs";
|
import { ApiDocsTags, PROJECTS } from "@app/lib/api-docs";
|
||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { slugSchema } from "@app/server/lib/schemas";
|
import { slugSchema } from "@app/server/lib/schemas";
|
||||||
@@ -488,6 +489,36 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/:projectId/pki-subscribers",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
projectId: z.string().trim().describe(PROJECTS.LIST_PKI_SUBSCRIBERS.projectId)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
subscribers: z.array(sanitizedPkiSubscriber)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const subscribers = await server.services.project.listProjectPkiSubscribers({
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actor: req.permission.type,
|
||||||
|
projectId: req.params.projectId
|
||||||
|
});
|
||||||
|
|
||||||
|
return { subscribers };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/:projectId/certificate-templates",
|
url: "/:projectId/certificate-templates",
|
||||||
|
|||||||
@@ -10,6 +10,18 @@ const isValidDate = (dateString: string) => {
|
|||||||
|
|
||||||
export const validateCaDateField = z.string().trim().refine(isValidDate, { message: "Invalid date format" });
|
export const validateCaDateField = z.string().trim().refine(isValidDate, { message: "Invalid date format" });
|
||||||
|
|
||||||
|
export const validateAltNameField = z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.refine(
|
||||||
|
(name) => {
|
||||||
|
return isFQDN(name) || z.string().email().safeParse(name).success || isValidIp(name);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: "SAN must be a valid hostname, email address, or IP address"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
export const validateAltNamesField = z
|
export const validateAltNamesField = z
|
||||||
.string()
|
.string()
|
||||||
.trim()
|
.trim()
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { TDbClient } from "@app/db";
|
||||||
|
import { TableName } from "@app/db/schemas";
|
||||||
|
import { ormify } from "@app/lib/knex";
|
||||||
|
|
||||||
|
export type TPkiSubscriberDALFactory = ReturnType<typeof pkiSubscriberDALFactory>;
|
||||||
|
|
||||||
|
export const pkiSubscriberDALFactory = (db: TDbClient) => {
|
||||||
|
const pkiSubscriberOrm = ormify(db, TableName.PkiSubscriber);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...pkiSubscriberOrm
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { PkiSubscribersSchema } from "@app/db/schemas";
|
||||||
|
|
||||||
|
export const sanitizedPkiSubscriber = PkiSubscribersSchema.pick({
|
||||||
|
id: true,
|
||||||
|
projectId: true,
|
||||||
|
caId: true,
|
||||||
|
name: true,
|
||||||
|
commonName: true,
|
||||||
|
subjectAlternativeNames: true,
|
||||||
|
ttl: true,
|
||||||
|
keyUsages: true,
|
||||||
|
extendedKeyUsages: true
|
||||||
|
});
|
||||||
@@ -0,0 +1,341 @@
|
|||||||
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
import * as x509 from "@peculiar/x509";
|
||||||
|
|
||||||
|
import { ActionProjectType } from "@app/db/schemas";
|
||||||
|
import { TCertificateAuthorityCrlDALFactory } from "@app/ee/services/certificate-authority-crl/certificate-authority-crl-dal";
|
||||||
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
|
import {
|
||||||
|
ProjectPermissionPkiSubscriberActions,
|
||||||
|
ProjectPermissionSub
|
||||||
|
} from "@app/ee/services/permission/project-permission";
|
||||||
|
import { getConfig } from "@app/lib/config/env";
|
||||||
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
|
import { ms } from "@app/lib/ms";
|
||||||
|
import { CertKeyAlgorithm } from "@app/services/certificate/certificate-types";
|
||||||
|
import { TCertificateAuthorityCertDALFactory } from "@app/services/certificate-authority/certificate-authority-cert-dal";
|
||||||
|
import { TCertificateAuthorityDALFactory } from "@app/services/certificate-authority/certificate-authority-dal";
|
||||||
|
import { getCaCredentials, keyAlgorithmToAlgCfg } from "@app/services/certificate-authority/certificate-authority-fns";
|
||||||
|
import { CaStatus } from "@app/services/certificate-authority/certificate-authority-types";
|
||||||
|
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||||
|
import { TPkiSubscriberDALFactory } from "@app/services/pki-subscriber/pki-subscriber-dal";
|
||||||
|
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||||
|
import { getProjectKmsCertificateKeyId } from "@app/services/project/project-fns";
|
||||||
|
|
||||||
|
import {
|
||||||
|
TCreatePkiSubscriberDTO,
|
||||||
|
TDeletePkiSubscriberDTO,
|
||||||
|
TGetPkiSubscriberByIdDTO,
|
||||||
|
TIssuePkiSubscriberCertDTO,
|
||||||
|
TSignPkiSubscriberCertDTO,
|
||||||
|
TUpdatePkiSubscriberDTO
|
||||||
|
} from "./pki-subscriber-types";
|
||||||
|
|
||||||
|
type TPkiSubscriberServiceFactoryDep = {
|
||||||
|
pkiSubscriberDAL: Pick<TPkiSubscriberDALFactory, "create" | "findById" | "updateById" | "deleteById">;
|
||||||
|
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "findById">;
|
||||||
|
certificateAuthorityCertDAL: Pick<TCertificateAuthorityCertDALFactory, "findById">;
|
||||||
|
certificateAuthorityCrlDAL: Pick<TCertificateAuthorityCrlDALFactory, "findOne">;
|
||||||
|
projectDAL: Pick<TProjectDALFactory, "findOne" | "updateById" | "transaction">;
|
||||||
|
kmsService: Pick<TKmsServiceFactory, "generateKmsKey" | "decryptWithKmsKey">;
|
||||||
|
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TPkiSubscriberServiceFactory = ReturnType<typeof pkiSubscriberServiceFactory>;
|
||||||
|
|
||||||
|
// TODO: bind subscribers to CA
|
||||||
|
|
||||||
|
export const pkiSubscriberServiceFactory = ({
|
||||||
|
pkiSubscriberDAL,
|
||||||
|
certificateAuthorityDAL,
|
||||||
|
certificateAuthorityCertDAL,
|
||||||
|
certificateAuthorityCrlDAL,
|
||||||
|
projectDAL,
|
||||||
|
kmsService,
|
||||||
|
permissionService
|
||||||
|
}: TPkiSubscriberServiceFactoryDep) => {
|
||||||
|
const createPkiSubscriber = async ({
|
||||||
|
name,
|
||||||
|
commonName,
|
||||||
|
caId, // (dangtony98) consider by CA name instead (newly-introduced field)
|
||||||
|
ttl,
|
||||||
|
subjectAlternativeNames,
|
||||||
|
keyUsages,
|
||||||
|
extendedKeyUsages,
|
||||||
|
projectId,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actor,
|
||||||
|
actorOrgId
|
||||||
|
}: TCreatePkiSubscriberDTO) => {
|
||||||
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
actionProjectType: ActionProjectType.CertificateManager
|
||||||
|
});
|
||||||
|
|
||||||
|
// (dangtony98): TODO: make permission more granular
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionPkiSubscriberActions.Read,
|
||||||
|
ProjectPermissionSub.PkiSubscribers
|
||||||
|
);
|
||||||
|
|
||||||
|
const newSubscriber = await pkiSubscriberDAL.create({
|
||||||
|
caId,
|
||||||
|
projectId,
|
||||||
|
name,
|
||||||
|
commonName,
|
||||||
|
ttl,
|
||||||
|
subjectAlternativeNames,
|
||||||
|
keyUsages,
|
||||||
|
extendedKeyUsages
|
||||||
|
});
|
||||||
|
|
||||||
|
return newSubscriber;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPkiSubscriberById = async ({
|
||||||
|
subscriberId,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actor,
|
||||||
|
actorOrgId
|
||||||
|
}: TGetPkiSubscriberByIdDTO) => {
|
||||||
|
const subscriber = await pkiSubscriberDAL.findById(subscriberId);
|
||||||
|
if (!subscriber) throw new NotFoundError({ message: `PKI subscriber with ID '${subscriberId}' not found` });
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId: subscriber.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
actionProjectType: ActionProjectType.CertificateManager
|
||||||
|
});
|
||||||
|
|
||||||
|
// (dangtony98): TODO: make permission more granular
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionPkiSubscriberActions.Read,
|
||||||
|
ProjectPermissionSub.PkiSubscribers
|
||||||
|
);
|
||||||
|
|
||||||
|
return subscriber;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updatePkiSubscriber = async ({
|
||||||
|
subscriberId,
|
||||||
|
name,
|
||||||
|
commonName,
|
||||||
|
caId,
|
||||||
|
ttl,
|
||||||
|
subjectAlternativeNames,
|
||||||
|
keyUsages,
|
||||||
|
extendedKeyUsages,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actor,
|
||||||
|
actorOrgId
|
||||||
|
}: TUpdatePkiSubscriberDTO) => {
|
||||||
|
const subscriber = await pkiSubscriberDAL.findById(subscriberId);
|
||||||
|
if (!subscriber) throw new NotFoundError({ message: `PKI subscriber with ID '${subscriberId}' not found` });
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId: subscriber.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
actionProjectType: ActionProjectType.CertificateManager
|
||||||
|
});
|
||||||
|
|
||||||
|
// (dangtony98): TODO: make permission more granular
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionPkiSubscriberActions.Edit,
|
||||||
|
ProjectPermissionSub.PkiSubscribers
|
||||||
|
);
|
||||||
|
|
||||||
|
const updatedSubscriber = await pkiSubscriberDAL.updateById(subscriberId, {
|
||||||
|
caId,
|
||||||
|
name,
|
||||||
|
commonName,
|
||||||
|
ttl,
|
||||||
|
subjectAlternativeNames,
|
||||||
|
keyUsages,
|
||||||
|
extendedKeyUsages
|
||||||
|
});
|
||||||
|
|
||||||
|
return updatedSubscriber;
|
||||||
|
};
|
||||||
|
|
||||||
|
const deletePkiSubscriber = async ({
|
||||||
|
subscriberId,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actor,
|
||||||
|
actorOrgId
|
||||||
|
}: TDeletePkiSubscriberDTO) => {
|
||||||
|
const subscriber = await pkiSubscriberDAL.findById(subscriberId);
|
||||||
|
if (!subscriber) throw new NotFoundError({ message: `PKI subscriber with ID '${subscriberId}' not found` });
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId: subscriber.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
actionProjectType: ActionProjectType.CertificateManager
|
||||||
|
});
|
||||||
|
|
||||||
|
// (dangtony98): TODO: make permission more granular
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionPkiSubscriberActions.Delete,
|
||||||
|
ProjectPermissionSub.PkiSubscribers
|
||||||
|
);
|
||||||
|
|
||||||
|
await pkiSubscriberDAL.deleteById(subscriberId);
|
||||||
|
|
||||||
|
return subscriber;
|
||||||
|
};
|
||||||
|
|
||||||
|
const issuePkiSubscriberCert = async ({
|
||||||
|
subscriberId,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actor,
|
||||||
|
actorOrgId
|
||||||
|
}: TIssuePkiSubscriberCertDTO) => {
|
||||||
|
const subscriber = await pkiSubscriberDAL.findById(subscriberId);
|
||||||
|
if (!subscriber) throw new NotFoundError({ message: `PKI subscriber with ID '${subscriberId}' not found` });
|
||||||
|
const ca = await certificateAuthorityDAL.findById(subscriber.caId);
|
||||||
|
if (!ca) throw new NotFoundError({ message: `CA with ID '${subscriber.caId}' not found` });
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId: ca.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
actionProjectType: ActionProjectType.CertificateManager
|
||||||
|
});
|
||||||
|
|
||||||
|
// (dangtony98): TODO: make permission more granular
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionPkiSubscriberActions.IssueCert,
|
||||||
|
ProjectPermissionSub.PkiSubscribers
|
||||||
|
);
|
||||||
|
|
||||||
|
if (ca.status === CaStatus.DISABLED) throw new BadRequestError({ message: "CA is disabled" });
|
||||||
|
if (!ca.activeCaCertId) throw new BadRequestError({ message: "CA does not have a certificate installed" });
|
||||||
|
if (ca.requireTemplateForIssuance) {
|
||||||
|
throw new BadRequestError({ message: "Certificate template is required for issuance" });
|
||||||
|
}
|
||||||
|
const caCert = await certificateAuthorityCertDAL.findById(ca.activeCaCertId);
|
||||||
|
|
||||||
|
const certificateManagerKmsId = await getProjectKmsCertificateKeyId({
|
||||||
|
projectId: ca.projectId,
|
||||||
|
projectDAL,
|
||||||
|
kmsService
|
||||||
|
});
|
||||||
|
const kmsDecryptor = await kmsService.decryptWithKmsKey({
|
||||||
|
kmsId: certificateManagerKmsId
|
||||||
|
});
|
||||||
|
|
||||||
|
const decryptedCaCert = await kmsDecryptor({
|
||||||
|
cipherTextBlob: caCert.encryptedCertificate
|
||||||
|
});
|
||||||
|
|
||||||
|
const caCertObj = new x509.X509Certificate(decryptedCaCert);
|
||||||
|
const notBeforeDate = new Date();
|
||||||
|
const notAfterDate = new Date(new Date().getTime() + ms(subscriber.ttl));
|
||||||
|
const caCertNotBeforeDate = new Date(caCertObj.notBefore);
|
||||||
|
const caCertNotAfterDate = new Date(caCertObj.notAfter);
|
||||||
|
|
||||||
|
// check not before constraint
|
||||||
|
if (notBeforeDate < caCertNotBeforeDate) {
|
||||||
|
throw new BadRequestError({ message: "notBefore date is before CA certificate's notBefore date" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// check not after constraint
|
||||||
|
if (notAfterDate > caCertNotAfterDate) {
|
||||||
|
throw new BadRequestError({ message: "notAfter date is after CA certificate's notAfter date" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const alg = keyAlgorithmToAlgCfg(ca.keyAlgorithm as CertKeyAlgorithm);
|
||||||
|
const leafKeys = await crypto.subtle.generateKey(alg, true, ["sign", "verify"]);
|
||||||
|
|
||||||
|
const csrObj = await x509.Pkcs10CertificateRequestGenerator.create({
|
||||||
|
name: `CN=${subscriber.commonName}`,
|
||||||
|
keys: leafKeys,
|
||||||
|
signingAlgorithm: alg,
|
||||||
|
extensions: [
|
||||||
|
// eslint-disable-next-line no-bitwise
|
||||||
|
new x509.KeyUsagesExtension(x509.KeyUsageFlags.digitalSignature | x509.KeyUsageFlags.keyEncipherment)
|
||||||
|
],
|
||||||
|
attributes: [new x509.ChallengePasswordAttribute("password")]
|
||||||
|
});
|
||||||
|
|
||||||
|
const { caPrivateKey, caSecret } = await getCaCredentials({
|
||||||
|
caId: ca.id,
|
||||||
|
certificateAuthorityDAL,
|
||||||
|
certificateAuthoritySecretDAL,
|
||||||
|
projectDAL,
|
||||||
|
kmsService
|
||||||
|
});
|
||||||
|
|
||||||
|
const caCrl = await certificateAuthorityCrlDAL.findOne({ caSecretId: caSecret.id });
|
||||||
|
const appCfg = getConfig();
|
||||||
|
|
||||||
|
const distributionPointUrl = `${appCfg.SITE_URL}/api/v1/pki/crl/${caCrl.id}/der`;
|
||||||
|
const caIssuerUrl = `${appCfg.SITE_URL}/api/v1/pki/ca/${ca.id}/certificates/${caCert.id}/der`;
|
||||||
|
|
||||||
|
const extensions: x509.Extension[] = [
|
||||||
|
new x509.BasicConstraintsExtension(false),
|
||||||
|
new x509.CRLDistributionPointsExtension([distributionPointUrl]),
|
||||||
|
await x509.AuthorityKeyIdentifierExtension.create(caCertObj, false),
|
||||||
|
await x509.SubjectKeyIdentifierExtension.create(csrObj.publicKey),
|
||||||
|
new x509.AuthorityInfoAccessExtension({
|
||||||
|
caIssuers: new x509.GeneralName("url", caIssuerUrl)
|
||||||
|
}),
|
||||||
|
new x509.CertificatePolicyExtension(["2.5.29.32.0"]) // anyPolicy
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
const signPkiSubscriberCert = async ({
|
||||||
|
subscriberId,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actor,
|
||||||
|
actorOrgId
|
||||||
|
}: TSignPkiSubscriberCertDTO) => {
|
||||||
|
const subscriber = await pkiSubscriberDAL.findById(subscriberId);
|
||||||
|
if (!subscriber) throw new NotFoundError({ message: `PKI subscriber with ID '${subscriberId}' not found` });
|
||||||
|
const ca = await certificateAuthorityDAL.findById(subscriber.caId);
|
||||||
|
if (!ca) throw new NotFoundError({ message: `CA with ID '${subscriber.caId}' not found` });
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId: ca.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
actionProjectType: ActionProjectType.CertificateManager
|
||||||
|
});
|
||||||
|
|
||||||
|
// (dangtony98): TODO: make permission more granular
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionPkiSubscriberActions.SignCert,
|
||||||
|
ProjectPermissionSub.PkiSubscribers
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
createPkiSubscriber,
|
||||||
|
getPkiSubscriberById,
|
||||||
|
updatePkiSubscriber,
|
||||||
|
deletePkiSubscriber,
|
||||||
|
issuePkiSubscriberCert,
|
||||||
|
signPkiSubscriberCert
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import { TProjectPermission } from "@app/lib/types";
|
||||||
|
|
||||||
|
import { CertExtendedKeyUsage, CertKeyUsage } from "../certificate/certificate-types";
|
||||||
|
|
||||||
|
export type TCreatePkiSubscriberDTO = {
|
||||||
|
caId: string;
|
||||||
|
name: string;
|
||||||
|
commonName: string;
|
||||||
|
ttl: string;
|
||||||
|
subjectAlternativeNames: string[];
|
||||||
|
keyUsages: CertKeyUsage[];
|
||||||
|
extendedKeyUsages: CertExtendedKeyUsage[];
|
||||||
|
} & TProjectPermission;
|
||||||
|
|
||||||
|
export type TGetPkiSubscriberByIdDTO = {
|
||||||
|
subscriberId: string;
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
export type TUpdatePkiSubscriberDTO = {
|
||||||
|
subscriberId: string;
|
||||||
|
caId?: string;
|
||||||
|
name?: string;
|
||||||
|
commonName?: string;
|
||||||
|
ttl?: string;
|
||||||
|
subjectAlternativeNames?: string[];
|
||||||
|
keyUsages?: CertKeyUsage[];
|
||||||
|
extendedKeyUsages?: CertExtendedKeyUsage[];
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
export type TDeletePkiSubscriberDTO = {
|
||||||
|
subscriberId: string;
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
export type TIssuePkiSubscriberCertDTO = {
|
||||||
|
subscriberId: string;
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
export type TSignPkiSubscriberCertDTO = {
|
||||||
|
subscriberId: string;
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
@@ -14,6 +14,7 @@ import { throwIfMissingSecretReadValueOrDescribePermission } from "@app/ee/servi
|
|||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import {
|
import {
|
||||||
ProjectPermissionActions,
|
ProjectPermissionActions,
|
||||||
|
ProjectPermissionPkiSubscriberActions,
|
||||||
ProjectPermissionSecretActions,
|
ProjectPermissionSecretActions,
|
||||||
ProjectPermissionSshHostActions,
|
ProjectPermissionSshHostActions,
|
||||||
ProjectPermissionSub
|
ProjectPermissionSub
|
||||||
@@ -34,6 +35,7 @@ import { groupBy } from "@app/lib/fn";
|
|||||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
import { TProjectPermission } from "@app/lib/types";
|
import { TProjectPermission } from "@app/lib/types";
|
||||||
import { TQueueServiceFactory } from "@app/queue";
|
import { TQueueServiceFactory } from "@app/queue";
|
||||||
|
import { TPkiSubscriberDALFactory } from "@app/services/pki-subscriber/pki-subscriber-dal";
|
||||||
|
|
||||||
import { ActorType } from "../auth/auth-type";
|
import { ActorType } from "../auth/auth-type";
|
||||||
import { TCertificateDALFactory } from "../certificate/certificate-dal";
|
import { TCertificateDALFactory } from "../certificate/certificate-dal";
|
||||||
@@ -85,6 +87,7 @@ import {
|
|||||||
TListProjectCasDTO,
|
TListProjectCasDTO,
|
||||||
TListProjectCertificateTemplatesDTO,
|
TListProjectCertificateTemplatesDTO,
|
||||||
TListProjectCertsDTO,
|
TListProjectCertsDTO,
|
||||||
|
TListProjectPkiSubscribersDTO,
|
||||||
TListProjectsDTO,
|
TListProjectsDTO,
|
||||||
TListProjectSshCasDTO,
|
TListProjectSshCasDTO,
|
||||||
TListProjectSshCertificatesDTO,
|
TListProjectSshCertificatesDTO,
|
||||||
@@ -144,6 +147,7 @@ type TProjectServiceFactoryDep = {
|
|||||||
"findById" | "findByIdWithWorkflowIntegrationDetails"
|
"findById" | "findByIdWithWorkflowIntegrationDetails"
|
||||||
>;
|
>;
|
||||||
projectUserMembershipRoleDAL: Pick<TProjectUserMembershipRoleDALFactory, "create">;
|
projectUserMembershipRoleDAL: Pick<TProjectUserMembershipRoleDALFactory, "create">;
|
||||||
|
pkiSubscriberDAL: Pick<TPkiSubscriberDALFactory, "find">;
|
||||||
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "find">;
|
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "find">;
|
||||||
certificateDAL: Pick<TCertificateDALFactory, "find" | "countCertificatesInProject">;
|
certificateDAL: Pick<TCertificateDALFactory, "find" | "countCertificatesInProject">;
|
||||||
certificateTemplateDAL: Pick<TCertificateTemplateDALFactory, "getCertTemplatesByProjectId">;
|
certificateTemplateDAL: Pick<TCertificateTemplateDALFactory, "getCertTemplatesByProjectId">;
|
||||||
@@ -206,6 +210,7 @@ export const projectServiceFactory = ({
|
|||||||
certificateTemplateDAL,
|
certificateTemplateDAL,
|
||||||
pkiCollectionDAL,
|
pkiCollectionDAL,
|
||||||
pkiAlertDAL,
|
pkiAlertDAL,
|
||||||
|
pkiSubscriberDAL,
|
||||||
sshCertificateAuthorityDAL,
|
sshCertificateAuthorityDAL,
|
||||||
sshCertificateAuthoritySecretDAL,
|
sshCertificateAuthoritySecretDAL,
|
||||||
sshCertificateDAL,
|
sshCertificateDAL,
|
||||||
@@ -1048,6 +1053,54 @@ export const projectServiceFactory = ({
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return list of PKI subscribers for project
|
||||||
|
*/
|
||||||
|
const listProjectPkiSubscribers = async ({
|
||||||
|
actorId,
|
||||||
|
actorOrgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actor,
|
||||||
|
projectId
|
||||||
|
}: TListProjectPkiSubscribersDTO) => {
|
||||||
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
actionProjectType: ActionProjectType.CertificateManager
|
||||||
|
});
|
||||||
|
|
||||||
|
const allowedSubscribers = [];
|
||||||
|
|
||||||
|
// (dangtony98): room to optimize
|
||||||
|
const subscribers = await pkiSubscriberDAL.find({ projectId });
|
||||||
|
|
||||||
|
for (const subscriber of subscribers) {
|
||||||
|
try {
|
||||||
|
// (dangtony98): Add more granular permissions
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionPkiSubscriberActions.Read,
|
||||||
|
ProjectPermissionSub.PkiSubscribers
|
||||||
|
);
|
||||||
|
|
||||||
|
// ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
// ProjectPermissionSshHostActions.Read,
|
||||||
|
// subject(ProjectPermissionSub.SshHosts, {
|
||||||
|
// hostname: host.hostname
|
||||||
|
// })
|
||||||
|
// );
|
||||||
|
|
||||||
|
allowedSubscribers.push(subscriber);
|
||||||
|
} catch {
|
||||||
|
// intentionally ignore subscribers where user lacks access
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return allowedSubscribers;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return list of certificate templates for project
|
* Return list of certificate templates for project
|
||||||
*/
|
*/
|
||||||
@@ -1921,6 +1974,7 @@ export const projectServiceFactory = ({
|
|||||||
listProjectSshCas,
|
listProjectSshCas,
|
||||||
listProjectSshHosts,
|
listProjectSshHosts,
|
||||||
listProjectSshHostGroups,
|
listProjectSshHostGroups,
|
||||||
|
listProjectPkiSubscribers,
|
||||||
listProjectSshCertificates,
|
listProjectSshCertificates,
|
||||||
listProjectSshCertificateTemplates,
|
listProjectSshCertificateTemplates,
|
||||||
updateVersionLimit,
|
updateVersionLimit,
|
||||||
|
|||||||
@@ -155,6 +155,7 @@ export type TListProjectCertificateTemplatesDTO = TProjectPermission;
|
|||||||
export type TListProjectSshCasDTO = TProjectPermission;
|
export type TListProjectSshCasDTO = TProjectPermission;
|
||||||
export type TListProjectSshHostsDTO = TProjectPermission;
|
export type TListProjectSshHostsDTO = TProjectPermission;
|
||||||
export type TListProjectSshCertificateTemplatesDTO = TProjectPermission;
|
export type TListProjectSshCertificateTemplatesDTO = TProjectPermission;
|
||||||
|
export type TListProjectPkiSubscribersDTO = TProjectPermission;
|
||||||
export type TListProjectSshCertificatesDTO = {
|
export type TListProjectSshCertificatesDTO = {
|
||||||
offset: number;
|
offset: number;
|
||||||
limit: number;
|
limit: number;
|
||||||
|
|||||||
@@ -8,5 +8,6 @@ export {
|
|||||||
ProjectPermissionIdentityActions,
|
ProjectPermissionIdentityActions,
|
||||||
ProjectPermissionKmipActions,
|
ProjectPermissionKmipActions,
|
||||||
ProjectPermissionMemberActions,
|
ProjectPermissionMemberActions,
|
||||||
|
ProjectPermissionPkiSubscriberActions,
|
||||||
ProjectPermissionSub
|
ProjectPermissionSub
|
||||||
} from "./types";
|
} from "./types";
|
||||||
|
|||||||
@@ -87,6 +87,14 @@ export enum ProjectPermissionSshHostActions {
|
|||||||
IssueHostCert = "issue-host-cert"
|
IssueHostCert = "issue-host-cert"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum ProjectPermissionPkiSubscriberActions {
|
||||||
|
Read = "read",
|
||||||
|
Create = "create",
|
||||||
|
Edit = "edit",
|
||||||
|
Delete = "delete",
|
||||||
|
IssueCert = "issue-cert"
|
||||||
|
}
|
||||||
|
|
||||||
export enum ProjectPermissionSecretRotationActions {
|
export enum ProjectPermissionSecretRotationActions {
|
||||||
Read = "read",
|
Read = "read",
|
||||||
ReadGeneratedCredentials = "read-generated-credentials",
|
ReadGeneratedCredentials = "read-generated-credentials",
|
||||||
@@ -178,6 +186,7 @@ export enum ProjectPermissionSub {
|
|||||||
SshHostGroups = "ssh-host-groups",
|
SshHostGroups = "ssh-host-groups",
|
||||||
PkiAlerts = "pki-alerts",
|
PkiAlerts = "pki-alerts",
|
||||||
PkiCollections = "pki-collections",
|
PkiCollections = "pki-collections",
|
||||||
|
PkiSubscribers = "pki-subscribers",
|
||||||
Kms = "kms",
|
Kms = "kms",
|
||||||
Cmek = "cmek",
|
Cmek = "cmek",
|
||||||
SecretSyncs = "secret-syncs",
|
SecretSyncs = "secret-syncs",
|
||||||
@@ -277,6 +286,7 @@ export type ProjectPermissionSet =
|
|||||||
| [ProjectPermissionSshHostActions, ProjectPermissionSub.SshHosts]
|
| [ProjectPermissionSshHostActions, ProjectPermissionSub.SshHosts]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.PkiAlerts]
|
| [ProjectPermissionActions, ProjectPermissionSub.PkiAlerts]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.PkiCollections]
|
| [ProjectPermissionActions, ProjectPermissionSub.PkiCollections]
|
||||||
|
| [ProjectPermissionPkiSubscriberActions, ProjectPermissionSub.PkiSubscribers]
|
||||||
| [ProjectPermissionSecretSyncActions, ProjectPermissionSub.SecretSyncs]
|
| [ProjectPermissionSecretSyncActions, ProjectPermissionSub.SecretSyncs]
|
||||||
| [ProjectPermissionActions.Delete, ProjectPermissionSub.Project]
|
| [ProjectPermissionActions.Delete, ProjectPermissionSub.Project]
|
||||||
| [ProjectPermissionActions.Edit, ProjectPermissionSub.Project]
|
| [ProjectPermissionActions.Edit, ProjectPermissionSub.Project]
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export {
|
|||||||
ProjectPermissionIdentityActions,
|
ProjectPermissionIdentityActions,
|
||||||
ProjectPermissionKmipActions,
|
ProjectPermissionKmipActions,
|
||||||
ProjectPermissionMemberActions,
|
ProjectPermissionMemberActions,
|
||||||
|
ProjectPermissionPkiSubscriberActions,
|
||||||
ProjectPermissionSub,
|
ProjectPermissionSub,
|
||||||
useProjectPermission
|
useProjectPermission
|
||||||
} from "./ProjectPermissionContext";
|
} from "./ProjectPermissionContext";
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ export * from "./orgAdmin";
|
|||||||
export * from "./organization";
|
export * from "./organization";
|
||||||
export * from "./pkiAlerts";
|
export * from "./pkiAlerts";
|
||||||
export * from "./pkiCollections";
|
export * from "./pkiCollections";
|
||||||
|
export * from "./pkiSubscriber";
|
||||||
export * from "./projectUserAdditionalPrivilege";
|
export * from "./projectUserAdditionalPrivilege";
|
||||||
export * from "./rateLimit";
|
export * from "./rateLimit";
|
||||||
export * from "./roles";
|
export * from "./roles";
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
export {
|
||||||
|
useCreatePkiSubscriber,
|
||||||
|
useDeletePkiSubscriber,
|
||||||
|
useUpdatePkiSubscriber
|
||||||
|
} from "./mutations";
|
||||||
|
export { useGetPkiSubscriberById } from "./queries";
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
|
|
||||||
|
import { apiRequest } from "@app/config/request";
|
||||||
|
|
||||||
|
import { workspaceKeys } from "../workspace/query-keys";
|
||||||
|
import {
|
||||||
|
TCreatePkiSubscriberDTO,
|
||||||
|
TDeletePkiSubscriberDTO,
|
||||||
|
TPkiSubscriber,
|
||||||
|
TUpdatePkiSubscriberDTO
|
||||||
|
} from "./types";
|
||||||
|
|
||||||
|
export const useCreatePkiSubscriber = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useMutation<TPkiSubscriber, object, TCreatePkiSubscriberDTO>({
|
||||||
|
mutationFn: async (body) => {
|
||||||
|
const { data: subscriber } = await apiRequest.post("/api/v1/pki/subscribers", body);
|
||||||
|
return subscriber;
|
||||||
|
},
|
||||||
|
onSuccess: ({ projectId }) => {
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: workspaceKeys.getWorkspacePkiSubscribers(projectId)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useUpdatePkiSubscriber = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useMutation<TPkiSubscriber, object, TUpdatePkiSubscriberDTO>({
|
||||||
|
mutationFn: async ({ subscriberId, ...body }) => {
|
||||||
|
const { data: subscriber } = await apiRequest.patch(
|
||||||
|
`/api/v1/pki/subscribers/${subscriberId}`,
|
||||||
|
body
|
||||||
|
);
|
||||||
|
return subscriber;
|
||||||
|
},
|
||||||
|
onSuccess: ({ projectId }) => {
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: workspaceKeys.getWorkspacePkiSubscribers(projectId)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useDeletePkiSubscriber = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useMutation<TPkiSubscriber, object, TDeletePkiSubscriberDTO>({
|
||||||
|
mutationFn: async ({ subscriberId }) => {
|
||||||
|
const { data: subscriber } = await apiRequest.delete(
|
||||||
|
`/api/v1/pki/subscribers/${subscriberId}`
|
||||||
|
);
|
||||||
|
return subscriber;
|
||||||
|
},
|
||||||
|
onSuccess: ({ projectId }) => {
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: workspaceKeys.getWorkspacePkiSubscribers(projectId)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
|
||||||
|
import { apiRequest } from "@app/config/request";
|
||||||
|
|
||||||
|
import { TPkiSubscriber } from "./types";
|
||||||
|
|
||||||
|
export const pkiSubscriberKeys = {
|
||||||
|
getPkiSubscriberById: (subscriberId: string) => [{ subscriberId }, "pki-subscriber"] as const
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useGetPkiSubscriberById = (subscriberId: string) => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: pkiSubscriberKeys.getPkiSubscriberById(subscriberId),
|
||||||
|
queryFn: async () => {
|
||||||
|
const { data: pkiSubscriber } = await apiRequest.get<TPkiSubscriber>(
|
||||||
|
`/api/v1/pki/subscribers/${subscriberId}`
|
||||||
|
);
|
||||||
|
return pkiSubscriber;
|
||||||
|
},
|
||||||
|
enabled: Boolean(subscriberId)
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import { CertExtendedKeyUsage, CertKeyUsage } from "../certificates/enums";
|
||||||
|
|
||||||
|
export type TPkiSubscriber = {
|
||||||
|
id: string;
|
||||||
|
projectId: string;
|
||||||
|
caId: string;
|
||||||
|
name: string;
|
||||||
|
commonName: string;
|
||||||
|
ttl: string;
|
||||||
|
subjectAlternativeNames: string[];
|
||||||
|
keyUsages: CertKeyUsage[];
|
||||||
|
extendedKeyUsages: CertExtendedKeyUsage[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TCreatePkiSubscriberDTO = {
|
||||||
|
projectId: string;
|
||||||
|
caId: string;
|
||||||
|
name: string;
|
||||||
|
commonName: string;
|
||||||
|
ttl: string;
|
||||||
|
subjectAlternativeNames: string[];
|
||||||
|
keyUsages: CertKeyUsage[];
|
||||||
|
extendedKeyUsages: CertExtendedKeyUsage[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TUpdatePkiSubscriberDTO = {
|
||||||
|
subscriberId: string;
|
||||||
|
caId?: string;
|
||||||
|
name?: string;
|
||||||
|
commonName?: string;
|
||||||
|
ttl?: string;
|
||||||
|
subjectAlternativeNames?: string[];
|
||||||
|
keyUsages?: CertKeyUsage[];
|
||||||
|
extendedKeyUsages?: CertExtendedKeyUsage[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TDeletePkiSubscriberDTO = {
|
||||||
|
subscriberId: string;
|
||||||
|
};
|
||||||
@@ -20,6 +20,7 @@ export type TSshHost = {
|
|||||||
hostCertTtl: string;
|
hostCertTtl: string;
|
||||||
loginMappings: TLoginMapping[];
|
loginMappings: TLoginMapping[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TCreateSshHostDTO = {
|
export type TCreateSshHostDTO = {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
hostname: string;
|
hostname: string;
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ export {
|
|||||||
useListWorkspaceGroups,
|
useListWorkspaceGroups,
|
||||||
useListWorkspacePkiAlerts,
|
useListWorkspacePkiAlerts,
|
||||||
useListWorkspacePkiCollections,
|
useListWorkspacePkiCollections,
|
||||||
|
useListWorkspacePkiSubscribers,
|
||||||
useListWorkspaceSshCas,
|
useListWorkspaceSshCas,
|
||||||
useListWorkspaceSshCertificates,
|
useListWorkspaceSshCertificates,
|
||||||
useListWorkspaceSshCertificateTemplates,
|
useListWorkspaceSshCertificateTemplates,
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { IntegrationAuth } from "../integrationAuth/types";
|
|||||||
import { TIntegration } from "../integrations/types";
|
import { TIntegration } from "../integrations/types";
|
||||||
import { TPkiAlert } from "../pkiAlerts/types";
|
import { TPkiAlert } from "../pkiAlerts/types";
|
||||||
import { TPkiCollection } from "../pkiCollections/types";
|
import { TPkiCollection } from "../pkiCollections/types";
|
||||||
|
import { TPkiSubscriber } from "../pkiSubscriber/types";
|
||||||
import { EncryptedSecret } from "../secrets/types";
|
import { EncryptedSecret } from "../secrets/types";
|
||||||
import { TSshCertificate, TSshCertificateAuthority } from "../sshCa/types";
|
import { TSshCertificate, TSshCertificateAuthority } from "../sshCa/types";
|
||||||
import { TSshCertificateTemplate } from "../sshCertificateTemplates/types";
|
import { TSshCertificateTemplate } from "../sshCertificateTemplates/types";
|
||||||
@@ -874,6 +875,21 @@ export const useListWorkspaceSshHosts = (projectId: string) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useListWorkspacePkiSubscribers = (projectId: string) => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: workspaceKeys.getWorkspacePkiSubscribers(projectId),
|
||||||
|
queryFn: async () => {
|
||||||
|
const {
|
||||||
|
data: { subscribers }
|
||||||
|
} = await apiRequest.get<{ subscribers: TPkiSubscriber[] }>(
|
||||||
|
`/api/v2/workspace/${projectId}/pki-subscribers`
|
||||||
|
);
|
||||||
|
return subscribers;
|
||||||
|
},
|
||||||
|
enabled: Boolean(projectId)
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export const useListWorkspaceSshHostGroups = (projectId: string) => {
|
export const useListWorkspaceSshHostGroups = (projectId: string) => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: workspaceKeys.getWorkspaceSshHostGroups(projectId),
|
queryKey: workspaceKeys.getWorkspaceSshHostGroups(projectId),
|
||||||
|
|||||||
@@ -51,6 +51,8 @@ export const workspaceKeys = {
|
|||||||
}) => [...workspaceKeys.forWorkspaceCertificates(slug), { offset, limit }] as const,
|
}) => [...workspaceKeys.forWorkspaceCertificates(slug), { offset, limit }] as const,
|
||||||
getWorkspacePkiAlerts: (workspaceId: string) =>
|
getWorkspacePkiAlerts: (workspaceId: string) =>
|
||||||
[{ workspaceId }, "workspace-pki-alerts"] as const,
|
[{ workspaceId }, "workspace-pki-alerts"] as const,
|
||||||
|
getWorkspacePkiSubscribers: (projectId: string) =>
|
||||||
|
[{ projectId }, "workspace-pki-subscribers"] as const,
|
||||||
getWorkspacePkiCollections: (workspaceId: string) =>
|
getWorkspacePkiCollections: (workspaceId: string) =>
|
||||||
[{ workspaceId }, "workspace-pki-collections"] as const,
|
[{ workspaceId }, "workspace-pki-collections"] as const,
|
||||||
getWorkspaceCertificateTemplates: (workspaceId: string) =>
|
getWorkspaceCertificateTemplates: (workspaceId: string) =>
|
||||||
|
|||||||
@@ -103,6 +103,20 @@ export const ProjectLayout = () => {
|
|||||||
)}
|
)}
|
||||||
{isCertManager && (
|
{isCertManager && (
|
||||||
<>
|
<>
|
||||||
|
<Link
|
||||||
|
to={
|
||||||
|
`/${ProjectType.CertificateManager}/$projectId/subscribers` as const
|
||||||
|
}
|
||||||
|
params={{
|
||||||
|
projectId: currentWorkspace.id
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{({ isActive }) => (
|
||||||
|
<MenuItem isSelected={isActive} icon="certificate-authority">
|
||||||
|
Subscribers
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
to={`/${ProjectType.CertificateManager}/$projectId/overview` as const}
|
to={`/${ProjectType.CertificateManager}/$projectId/overview` as const}
|
||||||
params={{
|
params={{
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { Helmet } from "react-helmet";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
import { PageHeader } from "@app/components/v2";
|
||||||
|
|
||||||
|
import { PkiSubscriberSection } from "./components";
|
||||||
|
|
||||||
|
export const PkiSubscribersPage = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Helmet>
|
||||||
|
<title>{t("common.head-title", { title: "PKI Subscribers" })}</title>
|
||||||
|
</Helmet>
|
||||||
|
<div className="h-full bg-bunker-800">
|
||||||
|
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
|
||||||
|
<div className="mx-auto mb-6 w-full max-w-7xl">
|
||||||
|
<PageHeader title="Subscribers" description="Manage your PKI subscribers." />
|
||||||
|
<PkiSubscriberSection />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,435 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { createNotification } from "@app/components/notifications";
|
||||||
|
import {
|
||||||
|
Accordion,
|
||||||
|
AccordionContent,
|
||||||
|
AccordionItem,
|
||||||
|
AccordionTrigger,
|
||||||
|
Button,
|
||||||
|
Checkbox,
|
||||||
|
FormControl,
|
||||||
|
Input,
|
||||||
|
Modal,
|
||||||
|
ModalContent,
|
||||||
|
Select,
|
||||||
|
SelectItem
|
||||||
|
} from "@app/components/v2";
|
||||||
|
import { useWorkspace } from "@app/context";
|
||||||
|
import {
|
||||||
|
CaStatus,
|
||||||
|
useCreatePkiSubscriber,
|
||||||
|
useGetPkiSubscriberById,
|
||||||
|
useListWorkspaceCas,
|
||||||
|
useListWorkspacePkiSubscribers,
|
||||||
|
useUpdatePkiSubscriber
|
||||||
|
} from "@app/hooks/api";
|
||||||
|
import {
|
||||||
|
EXTENDED_KEY_USAGES_OPTIONS,
|
||||||
|
KEY_USAGES_OPTIONS
|
||||||
|
} from "@app/hooks/api/certificates/constants";
|
||||||
|
import { CertExtendedKeyUsage, CertKeyUsage } from "@app/hooks/api/certificates/enums";
|
||||||
|
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
popUp: UsePopUpState<["pkiSubscriber"]>;
|
||||||
|
handlePopUpToggle: (popUpName: keyof UsePopUpState<["pkiSubscriber"]>, state?: boolean) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const schema = z
|
||||||
|
.object({
|
||||||
|
name: z.string().trim().min(1, "Name is required"),
|
||||||
|
caId: z.string().min(1, "Issuing CA is required"),
|
||||||
|
commonName: z.string().trim().min(1, "Common Name is required"),
|
||||||
|
subjectAlternativeNames: z.string(),
|
||||||
|
ttl: z.string().trim(),
|
||||||
|
keyUsages: z.object({
|
||||||
|
[CertKeyUsage.DIGITAL_SIGNATURE]: z.boolean().optional(),
|
||||||
|
[CertKeyUsage.KEY_ENCIPHERMENT]: z.boolean().optional(),
|
||||||
|
[CertKeyUsage.NON_REPUDIATION]: z.boolean().optional(),
|
||||||
|
[CertKeyUsage.DATA_ENCIPHERMENT]: z.boolean().optional(),
|
||||||
|
[CertKeyUsage.KEY_AGREEMENT]: z.boolean().optional(),
|
||||||
|
[CertKeyUsage.KEY_CERT_SIGN]: z.boolean().optional(),
|
||||||
|
[CertKeyUsage.CRL_SIGN]: z.boolean().optional(),
|
||||||
|
[CertKeyUsage.ENCIPHER_ONLY]: z.boolean().optional(),
|
||||||
|
[CertKeyUsage.DECIPHER_ONLY]: z.boolean().optional()
|
||||||
|
}),
|
||||||
|
extendedKeyUsages: z.object({
|
||||||
|
[CertExtendedKeyUsage.CLIENT_AUTH]: z.boolean().optional(),
|
||||||
|
[CertExtendedKeyUsage.CODE_SIGNING]: z.boolean().optional(),
|
||||||
|
[CertExtendedKeyUsage.EMAIL_PROTECTION]: z.boolean().optional(),
|
||||||
|
[CertExtendedKeyUsage.OCSP_SIGNING]: z.boolean().optional(),
|
||||||
|
[CertExtendedKeyUsage.SERVER_AUTH]: z.boolean().optional(),
|
||||||
|
[CertExtendedKeyUsage.TIMESTAMPING]: z.boolean().optional()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.required();
|
||||||
|
|
||||||
|
export type FormData = z.infer<typeof schema>;
|
||||||
|
|
||||||
|
export const PkiSubscriberModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||||
|
const { currentWorkspace } = useWorkspace();
|
||||||
|
const projectId = currentWorkspace?.id || "";
|
||||||
|
const { data: subscribers } = useListWorkspacePkiSubscribers(projectId);
|
||||||
|
const { data: cas } = useListWorkspaceCas({
|
||||||
|
projectSlug: currentWorkspace?.slug ?? "",
|
||||||
|
status: CaStatus.ACTIVE
|
||||||
|
});
|
||||||
|
|
||||||
|
const { data: pkiSubscriber } = useGetPkiSubscriberById(
|
||||||
|
(popUp?.pkiSubscriber?.data as { subscriberId: string })?.subscriberId || ""
|
||||||
|
);
|
||||||
|
|
||||||
|
const { mutateAsync: createMutateAsync } = useCreatePkiSubscriber();
|
||||||
|
const { mutateAsync: updateMutateAsync } = useUpdatePkiSubscriber();
|
||||||
|
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
handleSubmit,
|
||||||
|
reset,
|
||||||
|
setValue,
|
||||||
|
formState: { isSubmitting }
|
||||||
|
} = useForm<FormData>({
|
||||||
|
resolver: zodResolver(schema),
|
||||||
|
defaultValues: {
|
||||||
|
name: "",
|
||||||
|
caId: "",
|
||||||
|
commonName: "",
|
||||||
|
subjectAlternativeNames: "",
|
||||||
|
ttl: "",
|
||||||
|
keyUsages: {
|
||||||
|
[CertKeyUsage.DIGITAL_SIGNATURE]: true,
|
||||||
|
[CertKeyUsage.KEY_ENCIPHERMENT]: true
|
||||||
|
},
|
||||||
|
extendedKeyUsages: {}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (pkiSubscriber) {
|
||||||
|
reset({
|
||||||
|
name: pkiSubscriber.name,
|
||||||
|
caId: pkiSubscriber.caId || "",
|
||||||
|
commonName: pkiSubscriber.commonName,
|
||||||
|
subjectAlternativeNames: pkiSubscriber.subjectAlternativeNames.join(", ") || "",
|
||||||
|
ttl: pkiSubscriber.ttl || "",
|
||||||
|
keyUsages: Object.fromEntries((pkiSubscriber.keyUsages || []).map((name) => [name, true])),
|
||||||
|
extendedKeyUsages: Object.fromEntries(
|
||||||
|
(pkiSubscriber.extendedKeyUsages || []).map((name) => [name, true])
|
||||||
|
)
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
reset({
|
||||||
|
name: "",
|
||||||
|
caId: "",
|
||||||
|
commonName: "",
|
||||||
|
subjectAlternativeNames: "",
|
||||||
|
ttl: "",
|
||||||
|
keyUsages: {
|
||||||
|
[CertKeyUsage.DIGITAL_SIGNATURE]: true,
|
||||||
|
[CertKeyUsage.KEY_ENCIPHERMENT]: true
|
||||||
|
},
|
||||||
|
extendedKeyUsages: {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [pkiSubscriber, reset]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (cas?.length) {
|
||||||
|
setValue("caId", cas[0].id);
|
||||||
|
}
|
||||||
|
}, [cas, setValue]);
|
||||||
|
|
||||||
|
const onFormSubmit = async ({
|
||||||
|
name,
|
||||||
|
caId,
|
||||||
|
commonName,
|
||||||
|
subjectAlternativeNames,
|
||||||
|
ttl,
|
||||||
|
keyUsages,
|
||||||
|
extendedKeyUsages
|
||||||
|
}: FormData) => {
|
||||||
|
try {
|
||||||
|
if (!projectId) return;
|
||||||
|
|
||||||
|
if (!caId) {
|
||||||
|
createNotification({
|
||||||
|
text: "Please select an Issuing CA",
|
||||||
|
type: "error"
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("onFormSubmitArgs: ", {
|
||||||
|
name,
|
||||||
|
caId,
|
||||||
|
commonName,
|
||||||
|
subjectAlternativeNames,
|
||||||
|
ttl,
|
||||||
|
keyUsages,
|
||||||
|
extendedKeyUsages
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check if there is already a different subscriber with the same name
|
||||||
|
const existingNames =
|
||||||
|
subscribers?.filter((s) => s.id !== pkiSubscriber?.id).map((s) => s.name) || [];
|
||||||
|
|
||||||
|
if (existingNames.includes(name.trim())) {
|
||||||
|
createNotification({
|
||||||
|
text: "A subscriber with this name already exists.",
|
||||||
|
type: "error"
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const keyUsagesList = Object.entries(keyUsages)
|
||||||
|
.filter(([, value]) => value)
|
||||||
|
.map(([key]) => key as CertKeyUsage);
|
||||||
|
|
||||||
|
const extendedKeyUsagesList = Object.entries(extendedKeyUsages)
|
||||||
|
.filter(([, value]) => value)
|
||||||
|
.map(([key]) => key as CertExtendedKeyUsage);
|
||||||
|
|
||||||
|
const subjectAlternativeNamesList = subjectAlternativeNames
|
||||||
|
.split(",")
|
||||||
|
.map((san) => san.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
|
if (pkiSubscriber) {
|
||||||
|
await updateMutateAsync({
|
||||||
|
subscriberId: pkiSubscriber.id,
|
||||||
|
name,
|
||||||
|
caId,
|
||||||
|
commonName,
|
||||||
|
subjectAlternativeNames: subjectAlternativeNamesList,
|
||||||
|
ttl,
|
||||||
|
keyUsages: keyUsagesList,
|
||||||
|
extendedKeyUsages: extendedKeyUsagesList
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await createMutateAsync({
|
||||||
|
projectId,
|
||||||
|
name,
|
||||||
|
caId,
|
||||||
|
commonName,
|
||||||
|
subjectAlternativeNames: subjectAlternativeNamesList,
|
||||||
|
ttl,
|
||||||
|
keyUsages: keyUsagesList,
|
||||||
|
extendedKeyUsages: extendedKeyUsagesList
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
reset();
|
||||||
|
handlePopUpToggle("pkiSubscriber", false);
|
||||||
|
|
||||||
|
createNotification({
|
||||||
|
text: `Successfully ${pkiSubscriber ? "updated" : "added"} PKI subscriber`,
|
||||||
|
type: "success"
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
createNotification({
|
||||||
|
text: `Failed to ${pkiSubscriber ? "update" : "add"} PKI subscriber`,
|
||||||
|
type: "error"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
isOpen={popUp?.pkiSubscriber?.isOpen}
|
||||||
|
onOpenChange={(isOpen) => {
|
||||||
|
reset();
|
||||||
|
handlePopUpToggle("pkiSubscriber", isOpen);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ModalContent title={`${pkiSubscriber ? "Update" : "Add"} PKI Subscriber`}>
|
||||||
|
<form onSubmit={handleSubmit(onFormSubmit)}>
|
||||||
|
{pkiSubscriber && (
|
||||||
|
<FormControl label="Subscriber ID">
|
||||||
|
<Input value={pkiSubscriber.id} isDisabled className="bg-white/[0.07]" />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="name"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Name"
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
isRequired
|
||||||
|
>
|
||||||
|
<Input {...field} placeholder="web-service" />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="caId"
|
||||||
|
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Issuing CA"
|
||||||
|
errorText={error?.message}
|
||||||
|
isError={Boolean(error)}
|
||||||
|
isRequired
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
defaultValue={field.value}
|
||||||
|
{...field}
|
||||||
|
onValueChange={(e) => onChange(e)}
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
{(cas || []).map(({ id, dn }) => (
|
||||||
|
<SelectItem value={id} key={`ca-${id}`}>
|
||||||
|
{dn}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="commonName"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Common Name"
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
isRequired
|
||||||
|
>
|
||||||
|
<Input {...field} placeholder="web.example.com" />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="subjectAlternativeNames"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Subject Alternative Names (SANs)"
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
>
|
||||||
|
<Input {...field} placeholder="app1.example.com, app2.example.com, ..." />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="ttl"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="TTL"
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
isRequired
|
||||||
|
>
|
||||||
|
<Input {...field} placeholder="2 days, 1d, 2h, 1y, ..." />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Accordion type="single" collapsible className="w-full">
|
||||||
|
<AccordionItem value="key-usages" className="data-[state=open]:border-none">
|
||||||
|
<AccordionTrigger className="h-fit flex-none pl-1 text-sm">
|
||||||
|
<div className="order-1 ml-3">Key Usage</div>
|
||||||
|
</AccordionTrigger>
|
||||||
|
<AccordionContent>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="keyUsages"
|
||||||
|
render={({ field: { onChange, value }, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<FormControl
|
||||||
|
label="Key Usage"
|
||||||
|
errorText={error?.message}
|
||||||
|
isError={Boolean(error)}
|
||||||
|
>
|
||||||
|
<div className="mb-7 mt-2 grid grid-cols-2 gap-2">
|
||||||
|
{KEY_USAGES_OPTIONS.map(({ label, value: optionValue }) => {
|
||||||
|
return (
|
||||||
|
<Checkbox
|
||||||
|
id={optionValue}
|
||||||
|
key={optionValue}
|
||||||
|
className="data-[state=checked]:bg-primary"
|
||||||
|
isChecked={value[optionValue]}
|
||||||
|
onCheckedChange={(state) => {
|
||||||
|
onChange({
|
||||||
|
...value,
|
||||||
|
[optionValue]: state
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</Checkbox>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="extendedKeyUsages"
|
||||||
|
render={({ field: { onChange, value }, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<FormControl
|
||||||
|
label="Extended Key Usage"
|
||||||
|
errorText={error?.message}
|
||||||
|
isError={Boolean(error)}
|
||||||
|
>
|
||||||
|
<div className="mb-7 mt-2 grid grid-cols-2 gap-2">
|
||||||
|
{EXTENDED_KEY_USAGES_OPTIONS.map(({ label, value: optionValue }) => {
|
||||||
|
return (
|
||||||
|
<Checkbox
|
||||||
|
id={optionValue}
|
||||||
|
key={optionValue}
|
||||||
|
className="data-[state=checked]:bg-primary"
|
||||||
|
isChecked={value[optionValue]}
|
||||||
|
onCheckedChange={(state) => {
|
||||||
|
onChange({
|
||||||
|
...value,
|
||||||
|
[optionValue]: state
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</Checkbox>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
</Accordion>
|
||||||
|
<div className="mt-4 flex items-center">
|
||||||
|
<Button
|
||||||
|
className="mr-4"
|
||||||
|
size="sm"
|
||||||
|
type="submit"
|
||||||
|
isLoading={isSubmitting}
|
||||||
|
isDisabled={isSubmitting}
|
||||||
|
>
|
||||||
|
{pkiSubscriber ? "Update" : "Add"}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
colorSchema="secondary"
|
||||||
|
variant="plain"
|
||||||
|
onClick={() => handlePopUpToggle("pkiSubscriber", false)}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
+93
@@ -0,0 +1,93 @@
|
|||||||
|
import { faArrowUpRightFromSquare, faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
|
import { createNotification } from "@app/components/notifications";
|
||||||
|
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||||
|
import { Button, DeleteActionModal } from "@app/components/v2";
|
||||||
|
import { ProjectPermissionPkiSubscriberActions, ProjectPermissionSub } from "@app/context";
|
||||||
|
import { useDeletePkiSubscriber } from "@app/hooks/api";
|
||||||
|
import { usePopUp } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
|
import { PkiSubscriberModal } from "./PkiSubscriberModal";
|
||||||
|
import { PkiSubscribersTable } from "./PkiSubscribersTable";
|
||||||
|
|
||||||
|
export const PkiSubscriberSection = () => {
|
||||||
|
const { mutateAsync: deletePkiSubscriber } = useDeletePkiSubscriber();
|
||||||
|
|
||||||
|
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
||||||
|
"pkiSubscriber",
|
||||||
|
"deletePkiSubscriber"
|
||||||
|
] as const);
|
||||||
|
|
||||||
|
const onRemovePkiSubscriberSubmit = async (subscriberId: string) => {
|
||||||
|
try {
|
||||||
|
const subscriber = await deletePkiSubscriber({ subscriberId });
|
||||||
|
|
||||||
|
createNotification({
|
||||||
|
text: `Successfully deleted PKI subscriber: ${subscriber.name}`,
|
||||||
|
type: "success"
|
||||||
|
});
|
||||||
|
|
||||||
|
handlePopUpClose("deletePkiSubscriber");
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
createNotification({
|
||||||
|
text: "Failed to delete PKI subscriber",
|
||||||
|
type: "error"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||||
|
<div className="mb-4 flex justify-between">
|
||||||
|
<p className="text-xl font-semibold text-mineshaft-100">Subscribers</p>
|
||||||
|
<div className="flex w-full justify-end">
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
href="https://infisical.com/docs/documentation/platform/pki"
|
||||||
|
>
|
||||||
|
<span className="flex w-max cursor-pointer items-center rounded-md border border-mineshaft-500 bg-mineshaft-600 px-4 py-2 text-mineshaft-200 duration-200 hover:border-primary/40 hover:bg-primary/10 hover:text-white">
|
||||||
|
Documentation{" "}
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={faArrowUpRightFromSquare}
|
||||||
|
className="mb-[0.06rem] ml-1 text-xs"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
<ProjectPermissionCan
|
||||||
|
I={ProjectPermissionPkiSubscriberActions.Create}
|
||||||
|
a={ProjectPermissionSub.PkiSubscribers}
|
||||||
|
>
|
||||||
|
{(isAllowed) => (
|
||||||
|
<Button
|
||||||
|
colorSchema="primary"
|
||||||
|
type="submit"
|
||||||
|
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||||
|
onClick={() => handlePopUpOpen("pkiSubscriber")}
|
||||||
|
isDisabled={!isAllowed}
|
||||||
|
className="ml-4"
|
||||||
|
>
|
||||||
|
Add Subscriber
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</ProjectPermissionCan>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<PkiSubscribersTable handlePopUpOpen={handlePopUpOpen} />
|
||||||
|
<PkiSubscriberModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
|
||||||
|
<DeleteActionModal
|
||||||
|
isOpen={popUp.deletePkiSubscriber.isOpen}
|
||||||
|
title="Are you sure you want to remove the PKI subscriber?"
|
||||||
|
onChange={(isOpen) => handlePopUpToggle("deletePkiSubscriber", isOpen)}
|
||||||
|
deleteKey="confirm"
|
||||||
|
onDeleteApproved={() =>
|
||||||
|
onRemovePkiSubscriberSubmit(
|
||||||
|
(popUp?.deletePkiSubscriber?.data as { subscriberId: string })?.subscriberId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
+129
@@ -0,0 +1,129 @@
|
|||||||
|
import { faEllipsis, faPencil, faServer, faTrash } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
|
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
EmptyState,
|
||||||
|
Table,
|
||||||
|
TableContainer,
|
||||||
|
TableSkeleton,
|
||||||
|
TBody,
|
||||||
|
Td,
|
||||||
|
Th,
|
||||||
|
THead,
|
||||||
|
Tooltip,
|
||||||
|
Tr
|
||||||
|
} from "@app/components/v2";
|
||||||
|
import {
|
||||||
|
ProjectPermissionPkiSubscriberActions,
|
||||||
|
ProjectPermissionSub,
|
||||||
|
useWorkspace
|
||||||
|
} from "@app/context";
|
||||||
|
import { useListWorkspacePkiSubscribers } from "@app/hooks/api";
|
||||||
|
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
handlePopUpOpen: (
|
||||||
|
popUpName: keyof UsePopUpState<["deletePkiSubscriber", "pkiSubscriber"]>,
|
||||||
|
data?: object
|
||||||
|
) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PkiSubscribersTable = ({ handlePopUpOpen }: Props) => {
|
||||||
|
const { currentWorkspace } = useWorkspace();
|
||||||
|
const { data, isPending } = useListWorkspacePkiSubscribers(currentWorkspace?.id || "");
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<TableContainer>
|
||||||
|
<Table className="w-full table-fixed">
|
||||||
|
<THead>
|
||||||
|
<Tr>
|
||||||
|
<Th>Name</Th>
|
||||||
|
<Th>Common Name</Th>
|
||||||
|
<Th />
|
||||||
|
</Tr>
|
||||||
|
</THead>
|
||||||
|
<TBody>
|
||||||
|
{isPending && <TableSkeleton columns={3} innerKey="pki-subscribers" />}
|
||||||
|
{!isPending &&
|
||||||
|
data &&
|
||||||
|
data.length > 0 &&
|
||||||
|
data.map((subscriber) => {
|
||||||
|
return (
|
||||||
|
<Tr className="h-10" key={`pki-subscriber-${subscriber.id}`}>
|
||||||
|
<Td>{subscriber.name}</Td>
|
||||||
|
<Td>{subscriber.commonName}</Td>
|
||||||
|
<Td className="text-right align-middle">
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild className="rounded-lg">
|
||||||
|
<div className="hover:text-primary-400 data-[state=open]:text-primary-400">
|
||||||
|
<Tooltip content="More options">
|
||||||
|
<FontAwesomeIcon size="lg" icon={faEllipsis} />
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="start" className="p-1">
|
||||||
|
<ProjectPermissionCan
|
||||||
|
I={ProjectPermissionPkiSubscriberActions.Edit}
|
||||||
|
a={ProjectPermissionSub.PkiSubscribers}
|
||||||
|
>
|
||||||
|
{(isAllowed) => (
|
||||||
|
<DropdownMenuItem
|
||||||
|
className={twMerge(
|
||||||
|
!isAllowed && "pointer-events-none cursor-not-allowed opacity-50"
|
||||||
|
)}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
handlePopUpOpen("pkiSubscriber", {
|
||||||
|
subscriberId: subscriber.id
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
disabled={!isAllowed}
|
||||||
|
icon={<FontAwesomeIcon icon={faPencil} />}
|
||||||
|
>
|
||||||
|
Edit Subscriber
|
||||||
|
</DropdownMenuItem>
|
||||||
|
)}
|
||||||
|
</ProjectPermissionCan>
|
||||||
|
<ProjectPermissionCan
|
||||||
|
I={ProjectPermissionPkiSubscriberActions.Delete}
|
||||||
|
a={ProjectPermissionSub.PkiSubscribers}
|
||||||
|
>
|
||||||
|
{(isAllowed) => (
|
||||||
|
<DropdownMenuItem
|
||||||
|
className={twMerge(
|
||||||
|
!isAllowed && "pointer-events-none cursor-not-allowed opacity-50"
|
||||||
|
)}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
handlePopUpOpen("deletePkiSubscriber", {
|
||||||
|
subscriberId: subscriber.id
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
disabled={!isAllowed}
|
||||||
|
icon={<FontAwesomeIcon icon={faTrash} />}
|
||||||
|
>
|
||||||
|
Delete Subscriber
|
||||||
|
</DropdownMenuItem>
|
||||||
|
)}
|
||||||
|
</ProjectPermissionCan>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</TBody>
|
||||||
|
</Table>
|
||||||
|
{!isPending && data?.length === 0 && (
|
||||||
|
<EmptyState title="No PKI subscribers have been added" icon={faServer} />
|
||||||
|
)}
|
||||||
|
</TableContainer>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { PkiSubscriberSection } from "./PkiSubscriberSection";
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
|
import { PkiSubscribersPage } from "./PkiSubscribersPage";
|
||||||
|
|
||||||
|
export const Route = createFileRoute(
|
||||||
|
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers"
|
||||||
|
)({
|
||||||
|
component: PkiSubscribersPage
|
||||||
|
});
|
||||||
|
|
||||||
|
function RouteComponent() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
Hello
|
||||||
|
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers"!
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -91,6 +91,7 @@ import { Route as organizationSettingsPageOauthCallbackPageRouteImport } from '.
|
|||||||
import { Route as kmsSettingsPageRouteImport } from './pages/kms/SettingsPage/route'
|
import { Route as kmsSettingsPageRouteImport } from './pages/kms/SettingsPage/route'
|
||||||
import { Route as kmsOverviewPageRouteImport } from './pages/kms/OverviewPage/route'
|
import { Route as kmsOverviewPageRouteImport } from './pages/kms/OverviewPage/route'
|
||||||
import { Route as kmsKmipPageRouteImport } from './pages/kms/KmipPage/route'
|
import { Route as kmsKmipPageRouteImport } from './pages/kms/KmipPage/route'
|
||||||
|
import { Route as certManagerPkiSubscribersPageRouteImport } from './pages/cert-manager/PkiSubscribersPage/route'
|
||||||
import { Route as certManagerSettingsPageRouteImport } from './pages/cert-manager/SettingsPage/route'
|
import { Route as certManagerSettingsPageRouteImport } from './pages/cert-manager/SettingsPage/route'
|
||||||
import { Route as certManagerCertificatesPageRouteImport } from './pages/cert-manager/CertificatesPage/route'
|
import { Route as certManagerCertificatesPageRouteImport } from './pages/cert-manager/CertificatesPage/route'
|
||||||
import { Route as certManagerCertificateAuthoritiesPageRouteImport } from './pages/cert-manager/CertificateAuthoritiesPage/route'
|
import { Route as certManagerCertificateAuthoritiesPageRouteImport } from './pages/cert-manager/CertificateAuthoritiesPage/route'
|
||||||
@@ -904,6 +905,13 @@ const kmsKmipPageRouteRoute = kmsKmipPageRouteImport.update({
|
|||||||
getParentRoute: () => kmsLayoutRoute,
|
getParentRoute: () => kmsLayoutRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
|
||||||
|
const certManagerPkiSubscribersPageRouteRoute =
|
||||||
|
certManagerPkiSubscribersPageRouteImport.update({
|
||||||
|
id: '/subscribers',
|
||||||
|
path: '/subscribers',
|
||||||
|
getParentRoute: () => certManagerLayoutRoute,
|
||||||
|
} as any)
|
||||||
|
|
||||||
const certManagerSettingsPageRouteRoute =
|
const certManagerSettingsPageRouteRoute =
|
||||||
certManagerSettingsPageRouteImport.update({
|
certManagerSettingsPageRouteImport.update({
|
||||||
id: '/settings',
|
id: '/settings',
|
||||||
@@ -2184,6 +2192,13 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof certManagerSettingsPageRouteImport
|
preLoaderRoute: typeof certManagerSettingsPageRouteImport
|
||||||
parentRoute: typeof certManagerLayoutImport
|
parentRoute: typeof certManagerLayoutImport
|
||||||
}
|
}
|
||||||
|
'/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers': {
|
||||||
|
id: '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers'
|
||||||
|
path: '/subscribers'
|
||||||
|
fullPath: '/cert-manager/$projectId/subscribers'
|
||||||
|
preLoaderRoute: typeof certManagerPkiSubscribersPageRouteImport
|
||||||
|
parentRoute: typeof certManagerLayoutImport
|
||||||
|
}
|
||||||
'/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout/kmip': {
|
'/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout/kmip': {
|
||||||
id: '/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout/kmip'
|
id: '/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout/kmip'
|
||||||
path: '/kmip'
|
path: '/kmip'
|
||||||
@@ -3221,6 +3236,7 @@ interface certManagerLayoutRouteChildren {
|
|||||||
certManagerCertificateAuthoritiesPageRouteRoute: typeof certManagerCertificateAuthoritiesPageRouteRoute
|
certManagerCertificateAuthoritiesPageRouteRoute: typeof certManagerCertificateAuthoritiesPageRouteRoute
|
||||||
certManagerCertificatesPageRouteRoute: typeof certManagerCertificatesPageRouteRoute
|
certManagerCertificatesPageRouteRoute: typeof certManagerCertificatesPageRouteRoute
|
||||||
certManagerSettingsPageRouteRoute: typeof certManagerSettingsPageRouteRoute
|
certManagerSettingsPageRouteRoute: typeof certManagerSettingsPageRouteRoute
|
||||||
|
certManagerPkiSubscribersPageRouteRoute: typeof certManagerPkiSubscribersPageRouteRoute
|
||||||
projectAccessControlPageRouteCertManagerRoute: typeof projectAccessControlPageRouteCertManagerRoute
|
projectAccessControlPageRouteCertManagerRoute: typeof projectAccessControlPageRouteCertManagerRoute
|
||||||
certManagerCertAuthDetailsByIDPageRouteRoute: typeof certManagerCertAuthDetailsByIDPageRouteRoute
|
certManagerCertAuthDetailsByIDPageRouteRoute: typeof certManagerCertAuthDetailsByIDPageRouteRoute
|
||||||
projectIdentityDetailsByIDPageRouteCertManagerRoute: typeof projectIdentityDetailsByIDPageRouteCertManagerRoute
|
projectIdentityDetailsByIDPageRouteCertManagerRoute: typeof projectIdentityDetailsByIDPageRouteCertManagerRoute
|
||||||
@@ -3235,6 +3251,8 @@ const certManagerLayoutRouteChildren: certManagerLayoutRouteChildren = {
|
|||||||
certManagerCertificateAuthoritiesPageRouteRoute,
|
certManagerCertificateAuthoritiesPageRouteRoute,
|
||||||
certManagerCertificatesPageRouteRoute: certManagerCertificatesPageRouteRoute,
|
certManagerCertificatesPageRouteRoute: certManagerCertificatesPageRouteRoute,
|
||||||
certManagerSettingsPageRouteRoute: certManagerSettingsPageRouteRoute,
|
certManagerSettingsPageRouteRoute: certManagerSettingsPageRouteRoute,
|
||||||
|
certManagerPkiSubscribersPageRouteRoute:
|
||||||
|
certManagerPkiSubscribersPageRouteRoute,
|
||||||
projectAccessControlPageRouteCertManagerRoute:
|
projectAccessControlPageRouteCertManagerRoute:
|
||||||
projectAccessControlPageRouteCertManagerRoute,
|
projectAccessControlPageRouteCertManagerRoute,
|
||||||
certManagerCertAuthDetailsByIDPageRouteRoute:
|
certManagerCertAuthDetailsByIDPageRouteRoute:
|
||||||
@@ -3905,6 +3923,7 @@ export interface FileRoutesByFullPath {
|
|||||||
'/cert-manager/$projectId/certificate-authorities': typeof certManagerCertificateAuthoritiesPageRouteRoute
|
'/cert-manager/$projectId/certificate-authorities': typeof certManagerCertificateAuthoritiesPageRouteRoute
|
||||||
'/cert-manager/$projectId/overview': typeof certManagerCertificatesPageRouteRoute
|
'/cert-manager/$projectId/overview': typeof certManagerCertificatesPageRouteRoute
|
||||||
'/cert-manager/$projectId/settings': typeof certManagerSettingsPageRouteRoute
|
'/cert-manager/$projectId/settings': typeof certManagerSettingsPageRouteRoute
|
||||||
|
'/cert-manager/$projectId/subscribers': typeof certManagerPkiSubscribersPageRouteRoute
|
||||||
'/kms/$projectId/kmip': typeof kmsKmipPageRouteRoute
|
'/kms/$projectId/kmip': typeof kmsKmipPageRouteRoute
|
||||||
'/kms/$projectId/overview': typeof kmsOverviewPageRouteRoute
|
'/kms/$projectId/overview': typeof kmsOverviewPageRouteRoute
|
||||||
'/kms/$projectId/settings': typeof kmsSettingsPageRouteRoute
|
'/kms/$projectId/settings': typeof kmsSettingsPageRouteRoute
|
||||||
@@ -4084,6 +4103,7 @@ export interface FileRoutesByTo {
|
|||||||
'/cert-manager/$projectId/certificate-authorities': typeof certManagerCertificateAuthoritiesPageRouteRoute
|
'/cert-manager/$projectId/certificate-authorities': typeof certManagerCertificateAuthoritiesPageRouteRoute
|
||||||
'/cert-manager/$projectId/overview': typeof certManagerCertificatesPageRouteRoute
|
'/cert-manager/$projectId/overview': typeof certManagerCertificatesPageRouteRoute
|
||||||
'/cert-manager/$projectId/settings': typeof certManagerSettingsPageRouteRoute
|
'/cert-manager/$projectId/settings': typeof certManagerSettingsPageRouteRoute
|
||||||
|
'/cert-manager/$projectId/subscribers': typeof certManagerPkiSubscribersPageRouteRoute
|
||||||
'/kms/$projectId/kmip': typeof kmsKmipPageRouteRoute
|
'/kms/$projectId/kmip': typeof kmsKmipPageRouteRoute
|
||||||
'/kms/$projectId/overview': typeof kmsOverviewPageRouteRoute
|
'/kms/$projectId/overview': typeof kmsOverviewPageRouteRoute
|
||||||
'/kms/$projectId/settings': typeof kmsSettingsPageRouteRoute
|
'/kms/$projectId/settings': typeof kmsSettingsPageRouteRoute
|
||||||
@@ -4280,6 +4300,7 @@ export interface FileRoutesById {
|
|||||||
'/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/certificate-authorities': typeof certManagerCertificateAuthoritiesPageRouteRoute
|
'/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/certificate-authorities': typeof certManagerCertificateAuthoritiesPageRouteRoute
|
||||||
'/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/overview': typeof certManagerCertificatesPageRouteRoute
|
'/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/overview': typeof certManagerCertificatesPageRouteRoute
|
||||||
'/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/settings': typeof certManagerSettingsPageRouteRoute
|
'/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/settings': typeof certManagerSettingsPageRouteRoute
|
||||||
|
'/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers': typeof certManagerPkiSubscribersPageRouteRoute
|
||||||
'/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout/kmip': typeof kmsKmipPageRouteRoute
|
'/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout/kmip': typeof kmsKmipPageRouteRoute
|
||||||
'/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout/overview': typeof kmsOverviewPageRouteRoute
|
'/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout/overview': typeof kmsOverviewPageRouteRoute
|
||||||
'/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout/settings': typeof kmsSettingsPageRouteRoute
|
'/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout/settings': typeof kmsSettingsPageRouteRoute
|
||||||
@@ -4469,6 +4490,7 @@ export interface FileRouteTypes {
|
|||||||
| '/cert-manager/$projectId/certificate-authorities'
|
| '/cert-manager/$projectId/certificate-authorities'
|
||||||
| '/cert-manager/$projectId/overview'
|
| '/cert-manager/$projectId/overview'
|
||||||
| '/cert-manager/$projectId/settings'
|
| '/cert-manager/$projectId/settings'
|
||||||
|
| '/cert-manager/$projectId/subscribers'
|
||||||
| '/kms/$projectId/kmip'
|
| '/kms/$projectId/kmip'
|
||||||
| '/kms/$projectId/overview'
|
| '/kms/$projectId/overview'
|
||||||
| '/kms/$projectId/settings'
|
| '/kms/$projectId/settings'
|
||||||
@@ -4647,6 +4669,7 @@ export interface FileRouteTypes {
|
|||||||
| '/cert-manager/$projectId/certificate-authorities'
|
| '/cert-manager/$projectId/certificate-authorities'
|
||||||
| '/cert-manager/$projectId/overview'
|
| '/cert-manager/$projectId/overview'
|
||||||
| '/cert-manager/$projectId/settings'
|
| '/cert-manager/$projectId/settings'
|
||||||
|
| '/cert-manager/$projectId/subscribers'
|
||||||
| '/kms/$projectId/kmip'
|
| '/kms/$projectId/kmip'
|
||||||
| '/kms/$projectId/overview'
|
| '/kms/$projectId/overview'
|
||||||
| '/kms/$projectId/settings'
|
| '/kms/$projectId/settings'
|
||||||
@@ -4841,6 +4864,7 @@ export interface FileRouteTypes {
|
|||||||
| '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/certificate-authorities'
|
| '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/certificate-authorities'
|
||||||
| '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/overview'
|
| '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/overview'
|
||||||
| '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/settings'
|
| '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/settings'
|
||||||
|
| '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers'
|
||||||
| '/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout/kmip'
|
| '/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout/kmip'
|
||||||
| '/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout/overview'
|
| '/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout/overview'
|
||||||
| '/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout/settings'
|
| '/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout/settings'
|
||||||
@@ -5368,6 +5392,7 @@ export const routeTree = rootRoute
|
|||||||
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/certificate-authorities",
|
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/certificate-authorities",
|
||||||
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/overview",
|
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/overview",
|
||||||
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/settings",
|
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/settings",
|
||||||
|
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers",
|
||||||
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/access-management",
|
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/access-management",
|
||||||
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/ca/$caId",
|
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/ca/$caId",
|
||||||
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/identities/$identityId",
|
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/identities/$identityId",
|
||||||
@@ -5438,6 +5463,10 @@ export const routeTree = rootRoute
|
|||||||
"filePath": "cert-manager/SettingsPage/route.tsx",
|
"filePath": "cert-manager/SettingsPage/route.tsx",
|
||||||
"parent": "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout"
|
"parent": "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout"
|
||||||
},
|
},
|
||||||
|
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers": {
|
||||||
|
"filePath": "cert-manager/PkiSubscribersPage/route.tsx",
|
||||||
|
"parent": "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout"
|
||||||
|
},
|
||||||
"/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout/kmip": {
|
"/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout/kmip": {
|
||||||
"filePath": "kms/KmipPage/route.tsx",
|
"filePath": "kms/KmipPage/route.tsx",
|
||||||
"parent": "/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout"
|
"parent": "/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout"
|
||||||
|
|||||||
@@ -284,6 +284,7 @@ const secretManagerIntegrationsRedirect = route("/integrations", [
|
|||||||
|
|
||||||
const certManagerRoutes = route("/cert-manager/$projectId", [
|
const certManagerRoutes = route("/cert-manager/$projectId", [
|
||||||
layout("cert-manager-layout", "cert-manager/layout.tsx", [
|
layout("cert-manager-layout", "cert-manager/layout.tsx", [
|
||||||
|
route("/subscribers", "cert-manager/PkiSubscribersPage/route.tsx"),
|
||||||
route("/overview", "cert-manager/CertificatesPage/route.tsx"),
|
route("/overview", "cert-manager/CertificatesPage/route.tsx"),
|
||||||
route("/certificate-authorities", "cert-manager/CertificateAuthoritiesPage/route.tsx"),
|
route("/certificate-authorities", "cert-manager/CertificateAuthoritiesPage/route.tsx"),
|
||||||
route("/alerting", "cert-manager/AlertingPage/route.tsx"),
|
route("/alerting", "cert-manager/AlertingPage/route.tsx"),
|
||||||
|
|||||||
Reference in New Issue
Block a user