diff --git a/backend/src/db/migrations/20250504145538_pki-v2.ts b/backend/src/db/migrations/20250504145538_pki-v2.ts index eca76643f..a7fbf0047 100644 --- a/backend/src/db/migrations/20250504145538_pki-v2.ts +++ b/backend/src/db/migrations/20250504145538_pki-v2.ts @@ -18,13 +18,29 @@ export async function up(knex: Knex): Promise { t.string("ttl").notNullable(); t.specificType("keyUsages", "text[]").notNullable(); t.specificType("extendedKeyUsages", "text[]").notNullable(); + t.string("status").notNullable(); // active / disabled t.unique(["projectId", "name"]); }); await createOnUpdateTrigger(knex, TableName.PkiSubscriber); } + + const hasSubscriberCol = await knex.schema.hasColumn(TableName.Certificate, "pkiSubscriberId"); + if (!hasSubscriberCol) { + await knex.schema.alterTable(TableName.Certificate, (t) => { + t.uuid("pkiSubscriberId").nullable(); + t.foreign("pkiSubscriberId").references("id").inTable(TableName.PkiSubscriber).onDelete("SET NULL"); + }); + } } export async function down(knex: Knex): Promise { + const hasSubscriberCol = await knex.schema.hasColumn(TableName.Certificate, "pkiSubscriberId"); + if (hasSubscriberCol) { + await knex.schema.alterTable(TableName.Certificate, (t) => { + t.dropColumn("pkiSubscriberId"); + }); + } + await knex.schema.dropTableIfExists(TableName.PkiSubscriber); await dropOnUpdateTrigger(knex, TableName.PkiSubscriber); } diff --git a/backend/src/db/schemas/certificates.ts b/backend/src/db/schemas/certificates.ts index 533f9b898..cbd4f64f9 100644 --- a/backend/src/db/schemas/certificates.ts +++ b/backend/src/db/schemas/certificates.ts @@ -24,7 +24,8 @@ export const CertificatesSchema = z.object({ caCertId: z.string().uuid(), certificateTemplateId: z.string().uuid().nullable().optional(), keyUsages: z.string().array().nullable().optional(), - extendedKeyUsages: z.string().array().nullable().optional() + extendedKeyUsages: z.string().array().nullable().optional(), + pkiSubscriberId: z.string().uuid().nullable().optional() }); export type TCertificates = z.infer; diff --git a/backend/src/db/schemas/pki-subscribers.ts b/backend/src/db/schemas/pki-subscribers.ts index 85f3d0d24..3ce7ec648 100644 --- a/backend/src/db/schemas/pki-subscribers.ts +++ b/backend/src/db/schemas/pki-subscribers.ts @@ -18,7 +18,8 @@ export const PkiSubscribersSchema = z.object({ subjectAlternativeNames: z.string().array(), ttl: z.string(), keyUsages: z.string().array(), - extendedKeyUsages: z.string().array() + extendedKeyUsages: z.string().array(), + status: z.string() }); export type TPkiSubscribers = z.infer; diff --git a/backend/src/ee/services/audit-log/audit-log-types.ts b/backend/src/ee/services/audit-log/audit-log-types.ts index 74c7a7072..7b43aa43b 100644 --- a/backend/src/ee/services/audit-log/audit-log-types.ts +++ b/backend/src/ee/services/audit-log/audit-log-types.ts @@ -19,8 +19,7 @@ import { TProjectPermission } from "@app/lib/types"; import { AppConnection } from "@app/services/app-connection/app-connection-enums"; import { TCreateAppConnectionDTO, TUpdateAppConnectionDTO } from "@app/services/app-connection/app-connection-types"; import { ActorType } from "@app/services/auth/auth-type"; -import { CertKeyAlgorithm } from "@app/services/certificate/certificate-types"; -import { CertExtendedKeyUsage, CertKeyUsage } from "@app/services/certificate/certificate-types"; +import { CertExtendedKeyUsage, CertKeyAlgorithm, CertKeyUsage } from "@app/services/certificate/certificate-types"; import { CaStatus } from "@app/services/certificate-authority/certificate-authority-types"; import { TIdentityTrustedIp } from "@app/services/identity/identity-types"; import { PkiItemType } from "@app/services/pki-collection/pki-collection-types"; @@ -244,6 +243,7 @@ export enum EventType { GET_PKI_SUBSCRIBER = "get-pki-subscriber", ISSUE_PKI_SUBSCRIBER_CERT = "issue-pki-subscriber-cert", SIGN_PKI_SUBSCRIBER_CERT = "sign-pki-subscriber-cert", + LIST_PKI_SUBSCRIBER_CERTS = "list-pki-subscriber-certs", CREATE_KMS = "create-kms", UPDATE_KMS = "update-kms", DELETE_KMS = "delete-kms", @@ -1938,6 +1938,7 @@ interface DeletePkiSubscriber { type: EventType.DELETE_PKI_SUBSCRIBER; metadata: { pkiSubscriberId: string; + name: string; }; } @@ -1945,6 +1946,7 @@ interface GetPkiSubscriber { type: EventType.GET_PKI_SUBSCRIBER; metadata: { pkiSubscriberId: string; + name: string; }; } @@ -1952,6 +1954,7 @@ interface IssuePkiSubscriberCert { type: EventType.ISSUE_PKI_SUBSCRIBER_CERT; metadata: { subscriberId: string; + name: string; serialNumber: string; }; } @@ -1960,10 +1963,20 @@ interface SignPkiSubscriberCert { type: EventType.SIGN_PKI_SUBSCRIBER_CERT; metadata: { subscriberId: string; + name: string; serialNumber: string; }; } +interface ListPkiSubscriberCerts { + type: EventType.LIST_PKI_SUBSCRIBER_CERTS; + metadata: { + subscriberId: string; + name: string; + projectId: string; + }; +} + interface CreateKmsEvent { type: EventType.CREATE_KMS; metadata: { @@ -2928,6 +2941,7 @@ export type Event = | GetPkiSubscriber | IssuePkiSubscriberCert | SignPkiSubscriberCert + | ListPkiSubscriberCerts | CreateKmsEvent | UpdateKmsEvent | DeleteKmsEvent diff --git a/backend/src/ee/services/license/license-fns.ts b/backend/src/ee/services/license/license-fns.ts index 04bbcec2d..b7ae6f7ee 100644 --- a/backend/src/ee/services/license/license-fns.ts +++ b/backend/src/ee/services/license/license-fns.ts @@ -54,7 +54,7 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({ projectTemplates: false, kmip: false, gateway: false, - sshHostGroups: true + sshHostGroups: false }); export const setupLicenseRequestWithStore = (baseURL: string, refreshUrl: string, licenseKey: string) => { diff --git a/backend/src/ee/services/permission/project-permission.ts b/backend/src/ee/services/permission/project-permission.ts index a2e10100f..1c0e9db21 100644 --- a/backend/src/ee/services/permission/project-permission.ts +++ b/backend/src/ee/services/permission/project-permission.ts @@ -93,7 +93,7 @@ export enum ProjectPermissionPkiSubscriberActions { Edit = "edit", Delete = "delete", IssueCert = "issue-cert", - SignCert = "sign-cert" + ListCerts = "list-certs" } export enum ProjectPermissionSecretSyncActions { @@ -200,6 +200,11 @@ export type SshHostSubjectFields = { hostname: string; }; +export type PkiSubscriberSubjectFields = { + name: string; + // (dangtony98): consider adding [commonName] as a subject field in the future +}; + export type ProjectPermissionSet = | [ ProjectPermissionSecretActions, @@ -259,7 +264,13 @@ export type ProjectPermissionSet = ProjectPermissionSshHostActions, ProjectPermissionSub.SshHosts | (ForcedSubject & SshHostSubjectFields) ] - | [ProjectPermissionPkiSubscriberActions, ProjectPermissionSub.PkiSubscribers] // (dangtony98): TODO: update + | [ + ProjectPermissionPkiSubscriberActions, + ( + | ProjectPermissionSub.PkiSubscribers + | (ForcedSubject & PkiSubscriberSubjectFields) + ) + ] | [ProjectPermissionActions, ProjectPermissionSub.SshHostGroups] | [ProjectPermissionActions, ProjectPermissionSub.PkiAlerts] | [ProjectPermissionActions, ProjectPermissionSub.PkiCollections] @@ -410,6 +421,21 @@ const SshHostConditionSchema = z }) .partial(); +const PkiSubscriberConditionSchema = z + .object({ + name: z.union([ + z.string(), + z + .object({ + [PermissionConditionOperators.$EQ]: PermissionConditionSchema[PermissionConditionOperators.$EQ], + [PermissionConditionOperators.$GLOB]: PermissionConditionSchema[PermissionConditionOperators.$GLOB], + [PermissionConditionOperators.$IN]: PermissionConditionSchema[PermissionConditionOperators.$IN] + }) + .partial() + ]) + }) + .partial(); + const GeneralPermissionSchema = [ z.object({ subject: z.literal(ProjectPermissionSub.SecretApproval).describe("The entity this permission pertains to."), @@ -674,6 +700,16 @@ export const ProjectPermissionV2Schema = z.discriminatedUnion("subject", [ "When specified, only matching conditions will be allowed to access given resource." ).optional() }), + z.object({ + subject: z.literal(ProjectPermissionSub.PkiSubscribers).describe("The entity this permission pertains to."), + action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionPkiSubscriberActions).describe( + "Describe what action an entity can take." + ), + inverted: z.boolean().optional().describe("Whether rule allows or forbids."), + conditions: PkiSubscriberConditionSchema.describe( + "When specified, only matching conditions will be allowed to access given resource." + ).optional() + }), z.object({ subject: z.literal(ProjectPermissionSub.SecretRotation).describe("The entity this permission pertains to."), inverted: z.boolean().optional().describe("Whether rule allows or forbids."), @@ -754,7 +790,8 @@ const buildAdminPermissionRules = () => { ProjectPermissionPkiSubscriberActions.Create, ProjectPermissionPkiSubscriberActions.Edit, ProjectPermissionPkiSubscriberActions.Delete, - ProjectPermissionPkiSubscriberActions.IssueCert + ProjectPermissionPkiSubscriberActions.IssueCert, + ProjectPermissionPkiSubscriberActions.ListCerts ], ProjectPermissionSub.PkiSubscribers ); diff --git a/backend/src/ee/services/ssh-host-group/ssh-host-group-service.ts b/backend/src/ee/services/ssh-host-group/ssh-host-group-service.ts index 97fea1ce8..b56037b69 100644 --- a/backend/src/ee/services/ssh-host-group/ssh-host-group-service.ts +++ b/backend/src/ee/services/ssh-host-group/ssh-host-group-service.ts @@ -179,7 +179,7 @@ export const sshHostGroupServiceFactory = ({ }); const updatedSshHostGroup = await sshHostGroupDAL.transaction(async (tx) => { - if (name) { + if (name && name !== sshHostGroup.name) { // (dangtony98): room to optimize check to ensure that // the SSH host group name is unique across the whole org const project = await projectDAL.findById(sshHostGroup.projectId, tx); @@ -214,6 +214,7 @@ export const sshHostGroupServiceFactory = ({ tx ); } + if (loginMappings) { await sshHostLoginUserDAL.delete({ sshHostGroupId: sshHostGroup.id }, tx); if (loginMappings.length) { diff --git a/backend/src/ee/services/ssh-host/ssh-host-schema.ts b/backend/src/ee/services/ssh-host/ssh-host-schema.ts index a9b674991..1b4c01885 100644 --- a/backend/src/ee/services/ssh-host/ssh-host-schema.ts +++ b/backend/src/ee/services/ssh-host/ssh-host-schema.ts @@ -16,6 +16,8 @@ export const sanitizedSshHost = SshHostsSchema.pick({ export const loginMappingSchema = z.object({ loginUser: z.string().trim(), allowedPrincipals: z.object({ - usernames: z.array(z.string().trim()).transform((usernames) => Array.from(new Set(usernames))) + usernames: z + .array(z.string().trim()) + .transform((usernames) => Array.from(new Set(usernames.filter((username) => username !== "")))) }) }); diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts index e5faa9fb4..8b8b7abf6 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -1693,13 +1693,15 @@ export const ALERTS = { export const PKI_SUBSCRIBERS = { GET: { - subscriberId: "The ID of the PKI subscriber to get." + subscriberName: "The name of the PKI subscriber to get.", + projectId: "The ID of the project to get the PKI subscriber for." }, 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.", + status: "The status of the PKI subscriber. This can be one of active or disabled.", 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.", @@ -1707,10 +1709,12 @@ export const PKI_SUBSCRIBERS = { 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.", + projectId: "The ID of the project to update the PKI subscriber in.", + subscriberName: "The name 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.", + status: "The status of the PKI subscriber to update to. This can be one of active or disabled.", 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.", @@ -1719,15 +1723,23 @@ export const PKI_SUBSCRIBERS = { "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." + subscriberName: "The name of the PKI subscriber to delete.", + projectId: "The ID of the project of the PKI subscriber to delete." }, ISSUE_CERT: { - subscriberId: "The ID of the PKI subscriber to issue the certificate for.", + subscriberName: "The name of the PKI subscriber to issue the certificate for.", + projectId: "The ID of the project 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." + }, + LIST_CERTS: { + subscriberName: "The name of the PKI subscriber to list the certificates for.", + projectId: "The ID of the project of the PKI subscriber to list the certificates for.", + offset: "The offset to start from.", + limit: "The number of certificates to return." } }; diff --git a/backend/src/server/routes/v1/pki-subscriber-router.ts b/backend/src/server/routes/v1/pki-subscriber-router.ts index 02a1bc681..4434fb13d 100644 --- a/backend/src/server/routes/v1/pki-subscriber-router.ts +++ b/backend/src/server/routes/v1/pki-subscriber-router.ts @@ -1,5 +1,6 @@ import { z } from "zod"; +import { CertificatesSchema } from "@app/db/schemas"; 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"; @@ -11,12 +12,13 @@ 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"; +import { PkiSubscriberStatus } from "@app/services/pki-subscriber/pki-subscriber-types"; import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types"; export const registerPkiSubscriberRouter = async (server: FastifyZodProvider) => { server.route({ method: "GET", - url: "/:subscriberId", + url: "/:subscriberName", config: { rateLimit: readLimit }, @@ -25,7 +27,10 @@ export const registerPkiSubscriberRouter = async (server: FastifyZodProvider) => tags: [ApiDocsTags.PkiSubscribers], description: "Get PKI Subscriber", params: z.object({ - subscriberId: z.string().describe(PKI_SUBSCRIBERS.GET.subscriberId) + subscriberName: z.string().describe(PKI_SUBSCRIBERS.GET.subscriberName) + }), + querystring: z.object({ + projectId: z.string().describe(PKI_SUBSCRIBERS.GET.projectId) }), response: { 200: sanitizedPkiSubscriber @@ -33,8 +38,9 @@ export const registerPkiSubscriberRouter = async (server: FastifyZodProvider) => }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { - const subscriber = await server.services.pkiSubscriber.getSubscriberById({ - subscriberId: req.params.subscriberId, + const subscriber = await server.services.pkiSubscriber.getSubscriber({ + subscriberName: req.params.subscriberName, + projectId: req.query.projectId, actor: req.permission.type, actorId: req.permission.id, actorAuthMethod: req.permission.authMethod, @@ -47,7 +53,8 @@ export const registerPkiSubscriberRouter = async (server: FastifyZodProvider) => event: { type: EventType.GET_PKI_SUBSCRIBER, metadata: { - pkiSubscriberId: subscriber.id + pkiSubscriberId: subscriber.id, + name: subscriber.name } } }); @@ -76,6 +83,10 @@ export const registerPkiSubscriberRouter = async (server: FastifyZodProvider) => .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), + status: z + .nativeEnum(PkiSubscriberStatus) + .default(PkiSubscriberStatus.ACTIVE) + .describe(PKI_SUBSCRIBERS.CREATE.status), ttl: z .string() .trim() @@ -137,7 +148,7 @@ export const registerPkiSubscriberRouter = async (server: FastifyZodProvider) => server.route({ method: "PATCH", - url: "/:subscriberId", + url: "/:subscriberName", config: { rateLimit: writeLimit }, @@ -147,9 +158,10 @@ export const registerPkiSubscriberRouter = async (server: FastifyZodProvider) => tags: [ApiDocsTags.PkiSubscribers], description: "Update PKI Subscriber", params: z.object({ - subscriberId: z.string().trim().describe(PKI_SUBSCRIBERS.UPDATE.subscriberId) + subscriberName: z.string().trim().describe(PKI_SUBSCRIBERS.UPDATE.subscriberName) }), body: z.object({ + projectId: z.string().trim().describe(PKI_SUBSCRIBERS.UPDATE.projectId), caId: z .string() .trim() @@ -159,6 +171,7 @@ export const registerPkiSubscriberRouter = async (server: FastifyZodProvider) => .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(), + status: z.nativeEnum(PkiSubscriberStatus).optional().describe(PKI_SUBSCRIBERS.UPDATE.status), subjectAlternativeNames: validateAltNameField .array() .optional() @@ -188,7 +201,7 @@ export const registerPkiSubscriberRouter = async (server: FastifyZodProvider) => }, handler: async (req) => { const subscriber = await server.services.pkiSubscriber.updateSubscriber({ - subscriberId: req.params.subscriberId, + subscriberName: req.params.subscriberName, actor: req.permission.type, actorId: req.permission.id, actorAuthMethod: req.permission.authMethod, @@ -220,7 +233,7 @@ export const registerPkiSubscriberRouter = async (server: FastifyZodProvider) => server.route({ method: "DELETE", - url: "/:subscriberId", + url: "/:subscriberName", config: { rateLimit: writeLimit }, @@ -229,7 +242,10 @@ export const registerPkiSubscriberRouter = async (server: FastifyZodProvider) => tags: [ApiDocsTags.PkiSubscribers], description: "Delete PKI Subscriber", params: z.object({ - subscriberId: z.string().describe(PKI_SUBSCRIBERS.DELETE.subscriberId) + subscriberName: z.string().describe(PKI_SUBSCRIBERS.DELETE.subscriberName) + }), + body: z.object({ + projectId: z.string().trim().describe(PKI_SUBSCRIBERS.DELETE.projectId) }), response: { 200: sanitizedPkiSubscriber @@ -238,7 +254,8 @@ export const registerPkiSubscriberRouter = async (server: FastifyZodProvider) => onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { const subscriber = await server.services.pkiSubscriber.deleteSubscriber({ - subscriberId: req.params.subscriberId, + subscriberName: req.params.subscriberName, + projectId: req.body.projectId, actor: req.permission.type, actorId: req.permission.id, actorAuthMethod: req.permission.authMethod, @@ -251,7 +268,8 @@ export const registerPkiSubscriberRouter = async (server: FastifyZodProvider) => event: { type: EventType.DELETE_PKI_SUBSCRIBER, metadata: { - pkiSubscriberId: subscriber.id + pkiSubscriberId: subscriber.id, + name: subscriber.name } } }); @@ -262,7 +280,7 @@ export const registerPkiSubscriberRouter = async (server: FastifyZodProvider) => server.route({ method: "POST", - url: "/:subscriberId/issue-certificate", + url: "/:subscriberName/issue-certificate", config: { rateLimit: writeLimit }, @@ -272,7 +290,10 @@ export const registerPkiSubscriberRouter = async (server: FastifyZodProvider) => tags: [ApiDocsTags.PkiSubscribers], description: "Issue certificate", params: z.object({ - subscriberId: z.string().describe(PKI_SUBSCRIBERS.ISSUE_CERT.subscriberId) + subscriberName: z.string().describe(PKI_SUBSCRIBERS.ISSUE_CERT.subscriberName) + }), + body: z.object({ + projectId: z.string().trim().describe(PKI_SUBSCRIBERS.ISSUE_CERT.projectId) }), response: { 200: z.object({ @@ -287,7 +308,8 @@ export const registerPkiSubscriberRouter = async (server: FastifyZodProvider) => handler: async (req) => { const { certificate, certificateChain, issuingCaCertificate, privateKey, serialNumber, subscriber } = await server.services.pkiSubscriber.issueSubscriberCert({ - subscriberId: req.params.subscriberId, + subscriberName: req.params.subscriberName, + projectId: req.body.projectId, actor: req.permission.type, actorId: req.permission.id, actorAuthMethod: req.permission.authMethod, @@ -296,11 +318,12 @@ export const registerPkiSubscriberRouter = async (server: FastifyZodProvider) => await server.services.auditLog.createAuditLog({ ...req.auditLogInfo, - orgId: req.permission.orgId, + projectId: subscriber.projectId, event: { type: EventType.ISSUE_PKI_SUBSCRIBER_CERT, metadata: { subscriberId: subscriber.id, + name: subscriber.name, serialNumber } } @@ -328,7 +351,7 @@ export const registerPkiSubscriberRouter = async (server: FastifyZodProvider) => server.route({ method: "POST", - url: "/:subscriberId/sign-certificate", + url: "/:subscriberName/sign-certificate", config: { rateLimit: writeLimit }, @@ -338,9 +361,10 @@ export const registerPkiSubscriberRouter = async (server: FastifyZodProvider) => tags: [ApiDocsTags.PkiSubscribers], description: "Sign certificate", params: z.object({ - subscriberId: z.string().describe(PKI_SUBSCRIBERS.ISSUE_CERT.subscriberId) + subscriberName: z.string().describe(PKI_SUBSCRIBERS.ISSUE_CERT.subscriberName) }), body: z.object({ + projectId: z.string().trim().describe(PKI_SUBSCRIBERS.ISSUE_CERT.projectId), csr: z.string().trim().min(1) }), response: { @@ -355,7 +379,8 @@ export const registerPkiSubscriberRouter = async (server: FastifyZodProvider) => handler: async (req) => { const { certificate, certificateChain, issuingCaCertificate, serialNumber, subscriber } = await server.services.pkiSubscriber.signSubscriberCert({ - subscriberId: req.params.subscriberId, + subscriberName: req.params.subscriberName, + projectId: req.body.projectId, csr: req.body.csr, actor: req.permission.type, actorId: req.permission.id, @@ -365,11 +390,12 @@ export const registerPkiSubscriberRouter = async (server: FastifyZodProvider) => await server.services.auditLog.createAuditLog({ ...req.auditLogInfo, - orgId: req.permission.orgId, + projectId: subscriber.projectId, event: { type: EventType.SIGN_PKI_SUBSCRIBER_CERT, metadata: { subscriberId: subscriber.id, + name: subscriber.name, serialNumber } } @@ -393,4 +419,60 @@ export const registerPkiSubscriberRouter = async (server: FastifyZodProvider) => }; } }); + + server.route({ + method: "GET", + url: "/:subscriberName/certificates", + config: { + rateLimit: readLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.PkiSubscribers], + description: "List PKI Subscriber certificates", + params: z.object({ + subscriberName: z.string().describe(PKI_SUBSCRIBERS.GET.subscriberName) + }), + querystring: z.object({ + projectId: z.string().trim().describe(PKI_SUBSCRIBERS.LIST_CERTS.projectId), + offset: z.coerce.number().min(0).max(100).default(0).describe(PKI_SUBSCRIBERS.LIST_CERTS.offset), + limit: z.coerce.number().min(1).max(100).default(25).describe(PKI_SUBSCRIBERS.LIST_CERTS.limit) + }), + response: { + 200: z.object({ + certificates: z.array(CertificatesSchema), + totalCount: z.number() + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const { totalCount, certificates } = await server.services.pkiSubscriber.listSubscriberCerts({ + subscriberName: req.params.subscriberName, + actor: req.permission.type, + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + ...req.query + }); + + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + projectId: req.query.projectId, + event: { + type: EventType.LIST_PKI_SUBSCRIBER_CERTS, + metadata: { + subscriberId: req.params.subscriberName, + name: req.params.subscriberName, + projectId: req.query.projectId + } + } + }); + + return { + certificates, + totalCount + }; + } + }); }; diff --git a/backend/src/server/routes/v2/project-router.ts b/backend/src/server/routes/v2/project-router.ts index 3acf91cda..4ff3a7ed9 100644 --- a/backend/src/server/routes/v2/project-router.ts +++ b/backend/src/server/routes/v2/project-router.ts @@ -16,7 +16,6 @@ import { sanitizedSshCertificateTemplate } from "@app/ee/services/ssh-certificat import { loginMappingSchema, sanitizedSshHost } from "@app/ee/services/ssh-host/ssh-host-schema"; 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 { sanitizedPkiSubscriber } from "@app/services/pki-subscriber/pki-subscriber-schema"; import { ApiDocsTags, PROJECTS } from "@app/lib/api-docs"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { slugSchema } from "@app/server/lib/schemas"; @@ -25,6 +24,7 @@ import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AuthMode } from "@app/services/auth/auth-type"; import { CaStatus } from "@app/services/certificate-authority/certificate-authority-types"; import { sanitizedCertificateTemplate } from "@app/services/certificate-template/certificate-template-schema"; +import { sanitizedPkiSubscriber } from "@app/services/pki-subscriber/pki-subscriber-schema"; import { ProjectFilterType } from "@app/services/project/project-types"; import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types"; @@ -657,6 +657,8 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { rateLimit: readLimit }, schema: { + hide: false, + tags: [ApiDocsTags.SshHosts], params: z.object({ projectId: z.string().trim().describe(PROJECTS.LIST_SSH_HOSTS.projectId) }), @@ -695,6 +697,8 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { rateLimit: readLimit }, schema: { + hide: false, + tags: [ApiDocsTags.SshHostGroups], params: z.object({ projectId: z.string().trim().describe(PROJECTS.LIST_SSH_HOST_GROUPS.projectId) }), diff --git a/backend/src/services/certificate-authority/certificate-authority-service.ts b/backend/src/services/certificate-authority/certificate-authority-service.ts index e1d7ce5cb..d504e38ed 100644 --- a/backend/src/services/certificate-authority/certificate-authority-service.ts +++ b/backend/src/services/certificate-authority/certificate-authority-service.ts @@ -1169,7 +1169,7 @@ export const certificateAuthorityServiceFactory = ({ ProjectPermissionSub.Certificates ); - if (ca.status === CaStatus.DISABLED) throw new BadRequestError({ message: "CA is disabled" }); + if (ca.status !== CaStatus.ACTIVE) throw new BadRequestError({ message: "CA is not active" }); if (!ca.activeCaCertId) throw new BadRequestError({ message: "CA does not have a certificate installed" }); if (ca.requireTemplateForIssuance && !certificateTemplate) { throw new BadRequestError({ message: "Certificate template is required for issuance" }); @@ -1520,7 +1520,7 @@ export const certificateAuthorityServiceFactory = ({ ); } - if (ca.status === CaStatus.DISABLED) throw new BadRequestError({ message: "CA is disabled" }); + if (ca.status !== CaStatus.ACTIVE) throw new BadRequestError({ message: "CA is not active" }); if (!ca.activeCaCertId) throw new BadRequestError({ message: "CA does not have a certificate installed" }); if (ca.requireTemplateForIssuance && !certificateTemplate) { throw new BadRequestError({ message: "Certificate template is required for issuance" }); diff --git a/backend/src/services/certificate/certificate-dal.ts b/backend/src/services/certificate/certificate-dal.ts index 71c70838c..aafbe56f4 100644 --- a/backend/src/services/certificate/certificate-dal.ts +++ b/backend/src/services/certificate/certificate-dal.ts @@ -44,8 +44,27 @@ export const certificateDALFactory = (db: TDbClient) => { } }; + const countCertificatesForPkiSubscriber = async (subscriberId: string) => { + try { + interface CountResult { + count: string; + } + + const query = db + .replicaNode()(TableName.Certificate) + .where(`${TableName.Certificate}.pkiSubscriberId`, subscriberId); + + const count = await query.count("*").first(); + + return parseInt((count as unknown as CountResult).count || "0", 10); + } catch (error) { + throw new DatabaseError({ error, name: "Count all subscriber certificates" }); + } + }; + return { ...certificateOrm, - countCertificatesInProject + countCertificatesInProject, + countCertificatesForPkiSubscriber }; }; diff --git a/backend/src/services/pki-subscriber/pki-subscriber-dal.ts b/backend/src/services/pki-subscriber/pki-subscriber-dal.ts index 355187a7d..1899c63a6 100644 --- a/backend/src/services/pki-subscriber/pki-subscriber-dal.ts +++ b/backend/src/services/pki-subscriber/pki-subscriber-dal.ts @@ -6,8 +6,5 @@ export type TPkiSubscriberDALFactory = ReturnType { const pkiSubscriberOrm = ormify(db, TableName.PkiSubscriber); - - return { - ...pkiSubscriberOrm - }; + return pkiSubscriberOrm; }; diff --git a/backend/src/services/pki-subscriber/pki-subscriber-schema.ts b/backend/src/services/pki-subscriber/pki-subscriber-schema.ts index 9fa428ff2..7ffeea3fa 100644 --- a/backend/src/services/pki-subscriber/pki-subscriber-schema.ts +++ b/backend/src/services/pki-subscriber/pki-subscriber-schema.ts @@ -6,6 +6,7 @@ export const sanitizedPkiSubscriber = PkiSubscribersSchema.pick({ caId: true, name: true, commonName: true, + status: true, subjectAlternativeNames: true, ttl: true, keyUsages: true, diff --git a/backend/src/services/pki-subscriber/pki-subscriber-service.ts b/backend/src/services/pki-subscriber/pki-subscriber-service.ts index aaff6b01b..ab4146538 100644 --- a/backend/src/services/pki-subscriber/pki-subscriber-service.ts +++ b/backend/src/services/pki-subscriber/pki-subscriber-service.ts @@ -1,5 +1,5 @@ /* eslint-disable no-bitwise */ -import { ForbiddenError } from "@casl/ability"; +import { ForbiddenError, subject } from "@casl/ability"; import * as x509 from "@peculiar/x509"; import crypto, { KeyObject } from "crypto"; @@ -40,10 +40,12 @@ import { TProjectDALFactory } from "@app/services/project/project-dal"; import { getProjectKmsCertificateKeyId } from "@app/services/project/project-fns"; import { + PkiSubscriberStatus, TCreatePkiSubscriberDTO, TDeletePkiSubscriberDTO, - TGetPkiSubscriberByIdDTO, + TGetPkiSubscriberDTO, TIssuePkiSubscriberCertDTO, + TListPkiSubscriberCertsDTO, TSignPkiSubscriberCertDTO, TUpdatePkiSubscriberDTO } from "./pki-subscriber-types"; @@ -51,13 +53,13 @@ import { type TPkiSubscriberServiceFactoryDep = { pkiSubscriberDAL: Pick< TPkiSubscriberDALFactory, - "create" | "findById" | "updateById" | "deleteById" | "transaction" | "find" + "create" | "findById" | "updateById" | "deleteById" | "transaction" | "find" | "findOne" >; certificateAuthorityDAL: Pick; certificateAuthorityCertDAL: Pick; certificateAuthoritySecretDAL: Pick; certificateAuthorityCrlDAL: Pick; - certificateDAL: Pick; + certificateDAL: Pick; certificateBodyDAL: Pick; certificateSecretDAL: Pick; projectDAL: Pick; @@ -83,6 +85,7 @@ export const pkiSubscriberServiceFactory = ({ const createSubscriber = async ({ name, commonName, + status, caId, ttl, subjectAlternativeNames, @@ -103,69 +106,42 @@ export const pkiSubscriberServiceFactory = ({ actionProjectType: ActionProjectType.CertificateManager }); - // (dangtony98): TODO: make permission more granular ForbiddenError.from(permission).throwUnlessCan( - ProjectPermissionPkiSubscriberActions.Read, - ProjectPermissionSub.PkiSubscribers + ProjectPermissionPkiSubscriberActions.Create, + subject(ProjectPermissionSub.PkiSubscribers, { + name + }) ); - const newSubscriber = await pkiSubscriberDAL.transaction(async (tx) => { - // (dangtony98): room to optimize check to ensure that - // the PKI subscriber name is unique across the whole org - const project = await projectDAL.findById(projectId, tx); - if (!project) throw new NotFoundError({ message: `Project with ID '${projectId}' not found` }); - const projects = await projectDAL.find( - { - orgId: project.orgId - }, - { tx } - ); - - const existingPkiSubscriber = await pkiSubscriberDAL.find( - { - name, - $in: { - projectId: projects.map((p) => p.id) - } - }, - { tx } - ); - - if (existingPkiSubscriber.length) { - throw new BadRequestError({ - message: `PKI subscriber with name '${name}' already exists in the organization` - }); - } - - const subscriber = await pkiSubscriberDAL.create( - { - caId, - projectId, - name, - commonName, - ttl, - subjectAlternativeNames, - keyUsages, - extendedKeyUsages - }, - tx - ); - - return subscriber; + const newSubscriber = await pkiSubscriberDAL.create({ + caId, + projectId, + name, + commonName, + status, + ttl, + subjectAlternativeNames, + keyUsages, + extendedKeyUsages }); return newSubscriber; }; - const getSubscriberById = async ({ - subscriberId, + const getSubscriber = async ({ + subscriberName, + projectId, actorId, actorAuthMethod, actor, actorOrgId - }: TGetPkiSubscriberByIdDTO) => { - const subscriber = await pkiSubscriberDAL.findById(subscriberId); - if (!subscriber) throw new NotFoundError({ message: `PKI subscriber with ID '${subscriberId}' not found` }); + }: TGetPkiSubscriberDTO) => { + const subscriber = await pkiSubscriberDAL.findOne({ + name: subscriberName, + projectId + }); + + if (!subscriber) throw new NotFoundError({ message: `PKI subscriber named '${subscriberName}' not found` }); const { permission } = await permissionService.getProjectPermission({ actor, @@ -176,19 +152,22 @@ export const pkiSubscriberServiceFactory = ({ actionProjectType: ActionProjectType.CertificateManager }); - // (dangtony98): TODO: make permission more granular ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionPkiSubscriberActions.Read, - ProjectPermissionSub.PkiSubscribers + subject(ProjectPermissionSub.PkiSubscribers, { + name: subscriber.name + }) ); return subscriber; }; const updateSubscriber = async ({ - subscriberId, + subscriberName, + projectId, name, commonName, + status, caId, ttl, subjectAlternativeNames, @@ -199,83 +178,11 @@ export const pkiSubscriberServiceFactory = ({ actor, actorOrgId }: TUpdatePkiSubscriberDTO) => { - const foundSubscriber = await pkiSubscriberDAL.findById(subscriberId); - if (!foundSubscriber) throw new NotFoundError({ message: `PKI subscriber with ID '${subscriberId}' not found` }); - - const { permission } = await permissionService.getProjectPermission({ - actor, - actorId, - projectId: foundSubscriber.projectId, - actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.CertificateManager + const subscriber = await pkiSubscriberDAL.findOne({ + name: subscriberName, + projectId }); - - // (dangtony98): TODO: make permission more granular - ForbiddenError.from(permission).throwUnlessCan( - ProjectPermissionPkiSubscriberActions.Edit, - ProjectPermissionSub.PkiSubscribers - ); - - const updatedSubscriber = await pkiSubscriberDAL.transaction(async (tx) => { - if (name) { - // (dangtony98): room to optimize check to ensure that - // the PKI subscriber name is unique across the whole org - const project = await projectDAL.findById(foundSubscriber.projectId, tx); - if (!project) throw new NotFoundError({ message: `Project with ID '${foundSubscriber.projectId}' not found` }); - const projects = await projectDAL.find( - { - orgId: project.orgId - }, - { tx } - ); - - const existingPkiSubscriber = await pkiSubscriberDAL.find( - { - name, - $in: { - projectId: projects.map((p) => p.id) - } - }, - { tx } - ); - - if (existingPkiSubscriber.length) { - throw new BadRequestError({ - message: `PKI subscriber with name '${name}' already exists in the organization` - }); - } - } - - const subscriber = await pkiSubscriberDAL.updateById( - subscriberId, - { - caId, - name, - commonName, - ttl, - subjectAlternativeNames, - keyUsages, - extendedKeyUsages - }, - tx - ); - - return subscriber; - }); - - return updatedSubscriber; - }; - - const deleteSubscriber = 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` }); + if (!subscriber) throw new NotFoundError({ message: `PKI subscriber named '${subscriberName}' not found` }); const { permission } = await permissionService.getProjectPermission({ actor, @@ -286,26 +193,76 @@ export const pkiSubscriberServiceFactory = ({ actionProjectType: ActionProjectType.CertificateManager }); - // (dangtony98): TODO: make permission more granular ForbiddenError.from(permission).throwUnlessCan( - ProjectPermissionPkiSubscriberActions.Delete, - ProjectPermissionSub.PkiSubscribers + ProjectPermissionPkiSubscriberActions.Edit, + subject(ProjectPermissionSub.PkiSubscribers, { + name: subscriber.name + }) ); - await pkiSubscriberDAL.deleteById(subscriberId); + const updatedSubscriber = await pkiSubscriberDAL.updateById(subscriber.id, { + caId, + name, + commonName, + status, + ttl, + subjectAlternativeNames, + keyUsages, + extendedKeyUsages + }); + + return updatedSubscriber; + }; + + const deleteSubscriber = async ({ + subscriberName, + projectId, + actorId, + actorAuthMethod, + actor, + actorOrgId + }: TDeletePkiSubscriberDTO) => { + const subscriber = await pkiSubscriberDAL.findOne({ + name: subscriberName, + projectId + }); + if (!subscriber) throw new NotFoundError({ message: `PKI subscriber named '${subscriberName}' not found` }); + + const { permission } = await permissionService.getProjectPermission({ + actor, + actorId, + projectId: subscriber.projectId, + actorAuthMethod, + actorOrgId, + actionProjectType: ActionProjectType.CertificateManager + }); + + ForbiddenError.from(permission).throwUnlessCan( + ProjectPermissionPkiSubscriberActions.Delete, + subject(ProjectPermissionSub.PkiSubscribers, { + name: subscriber.name + }) + ); + + await pkiSubscriberDAL.deleteById(subscriber.id); return subscriber; }; const issueSubscriberCert = async ({ - subscriberId, + subscriberName, + projectId, 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 subscriber = await pkiSubscriberDAL.findOne({ + name: subscriberName, + projectId + }); + if (!subscriber) throw new NotFoundError({ message: `PKI subscriber named '${subscriberName}' not found` }); + const ca = await certificateAuthorityDAL.findById(subscriber.caId); if (!ca) throw new NotFoundError({ message: `CA with ID '${subscriber.caId}' not found` }); @@ -318,13 +275,16 @@ export const pkiSubscriberServiceFactory = ({ actionProjectType: ActionProjectType.CertificateManager }); - // (dangtony98): TODO: make permission more granular ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionPkiSubscriberActions.IssueCert, - ProjectPermissionSub.PkiSubscribers + subject(ProjectPermissionSub.PkiSubscribers, { + name: subscriber.name + }) ); - if (ca.status === CaStatus.DISABLED) throw new BadRequestError({ message: "CA is disabled" }); + if (subscriber.status !== PkiSubscriberStatus.ACTIVE) + throw new BadRequestError({ message: "Subscriber is not active" }); + if (ca.status !== CaStatus.ACTIVE) throw new BadRequestError({ message: "CA is not active" }); 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" }); @@ -452,6 +412,7 @@ export const pkiSubscriberServiceFactory = ({ { caId: ca.id, caCertId: caCert.id, + pkiSubscriberId: subscriber.id, status: CertStatus.ACTIVE, friendlyName: subscriber.commonName, commonName: subscriber.commonName, @@ -495,7 +456,8 @@ export const pkiSubscriberServiceFactory = ({ }; const signSubscriberCert = async ({ - subscriberId, + subscriberName, + projectId, csr, actorId, actorAuthMethod, @@ -503,8 +465,11 @@ export const pkiSubscriberServiceFactory = ({ actorOrgId }: TSignPkiSubscriberCertDTO) => { const appCfg = getConfig(); - const subscriber = await pkiSubscriberDAL.findById(subscriberId); - if (!subscriber) throw new NotFoundError({ message: `PKI subscriber with ID '${subscriberId}' not found` }); + const subscriber = await pkiSubscriberDAL.findOne({ + name: subscriberName, + projectId + }); + if (!subscriber) throw new NotFoundError({ message: `PKI subscriber named '${subscriberName}' not found` }); const ca = await certificateAuthorityDAL.findById(subscriber.caId); if (!ca) throw new NotFoundError({ message: `CA with ID '${subscriber.caId}' not found` }); @@ -517,13 +482,16 @@ export const pkiSubscriberServiceFactory = ({ actionProjectType: ActionProjectType.CertificateManager }); - // (dangtony98): TODO: make permission more granular ForbiddenError.from(permission).throwUnlessCan( - ProjectPermissionPkiSubscriberActions.SignCert, - ProjectPermissionSub.PkiSubscribers + ProjectPermissionPkiSubscriberActions.IssueCert, + subject(ProjectPermissionSub.PkiSubscribers, { + name: subscriber.name + }) ); - if (ca.status === CaStatus.DISABLED) throw new BadRequestError({ message: "CA is disabled" }); + if (subscriber.status !== PkiSubscriberStatus.ACTIVE) + throw new BadRequestError({ message: "Subscriber is not active" }); + if (ca.status !== CaStatus.ACTIVE) throw new BadRequestError({ message: "CA is not active" }); 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" }); @@ -711,6 +679,7 @@ export const pkiSubscriberServiceFactory = ({ { caId: ca.id, caCertId: caCert.id, + pkiSubscriberId: subscriber.id, status: CertStatus.ACTIVE, friendlyName: subscriber.commonName, commonName: subscriber.commonName, @@ -747,12 +716,62 @@ export const pkiSubscriberServiceFactory = ({ }; }; + const listSubscriberCerts = async ({ + subscriberName, + projectId, + offset, + limit, + actorId, + actorAuthMethod, + actor, + actorOrgId + }: TListPkiSubscriberCertsDTO) => { + const subscriber = await pkiSubscriberDAL.findOne({ + name: subscriberName, + projectId + }); + if (!subscriber) throw new NotFoundError({ message: `PKI subscriber named '${subscriberName}' 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 + }); + + ForbiddenError.from(permission).throwUnlessCan( + ProjectPermissionPkiSubscriberActions.ListCerts, + subject(ProjectPermissionSub.PkiSubscribers, { + name: subscriber.name + }) + ); + + const certificates = await certificateDAL.find( + { + pkiSubscriberId: subscriber.id + }, + { offset, limit, sort: [["updatedAt", "desc"]] } + ); + + const count = await certificateDAL.countCertificatesForPkiSubscriber(subscriber.id); + + return { + certificates, + totalCount: count + }; + }; + return { createSubscriber, - getSubscriberById, + getSubscriber, updateSubscriber, deleteSubscriber, issueSubscriberCert, - signSubscriberCert + signSubscriberCert, + listSubscriberCerts }; }; diff --git a/backend/src/services/pki-subscriber/pki-subscriber-types.ts b/backend/src/services/pki-subscriber/pki-subscriber-types.ts index b149f93ee..690148f16 100644 --- a/backend/src/services/pki-subscriber/pki-subscriber-types.ts +++ b/backend/src/services/pki-subscriber/pki-subscriber-types.ts @@ -2,40 +2,53 @@ import { TProjectPermission } from "@app/lib/types"; import { CertExtendedKeyUsage, CertKeyUsage } from "../certificate/certificate-types"; +export enum PkiSubscriberStatus { + ACTIVE = "active", + DISABLED = "disabled" +} + export type TCreatePkiSubscriberDTO = { caId: string; name: string; commonName: string; + status: PkiSubscriberStatus; ttl: string; subjectAlternativeNames: string[]; keyUsages: CertKeyUsage[]; extendedKeyUsages: CertExtendedKeyUsage[]; } & TProjectPermission; -export type TGetPkiSubscriberByIdDTO = { - subscriberId: string; -} & Omit; +export type TGetPkiSubscriberDTO = { + subscriberName: string; +} & TProjectPermission; export type TUpdatePkiSubscriberDTO = { - subscriberId: string; + subscriberName: string; caId?: string; name?: string; commonName?: string; + status?: PkiSubscriberStatus; ttl?: string; subjectAlternativeNames?: string[]; keyUsages?: CertKeyUsage[]; extendedKeyUsages?: CertExtendedKeyUsage[]; -} & Omit; +} & TProjectPermission; export type TDeletePkiSubscriberDTO = { - subscriberId: string; -} & Omit; + subscriberName: string; +} & TProjectPermission; export type TIssuePkiSubscriberCertDTO = { - subscriberId: string; -} & Omit; + subscriberName: string; +} & TProjectPermission; export type TSignPkiSubscriberCertDTO = { - subscriberId: string; + subscriberName: string; csr: string; -} & Omit; +} & TProjectPermission; + +export type TListPkiSubscriberCertsDTO = { + subscriberName: string; + offset: number; + limit: number; +} & TProjectPermission; diff --git a/backend/src/services/project/project-service.ts b/backend/src/services/project/project-service.ts index 347b41845..48d907801 100644 --- a/backend/src/services/project/project-service.ts +++ b/backend/src/services/project/project-service.ts @@ -1083,19 +1083,13 @@ export const projectServiceFactory = ({ for (const subscriber of subscribers) { try { - // (dangtony98): Add more granular permissions ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionPkiSubscriberActions.Read, - ProjectPermissionSub.PkiSubscribers + subject(ProjectPermissionSub.PkiSubscribers, { + name: subscriber.name + }) ); - // ForbiddenError.from(permission).throwUnlessCan( - // ProjectPermissionSshHostActions.Read, - // subject(ProjectPermissionSub.SshHosts, { - // hostname: host.hostname - // }) - // ); - allowedSubscribers.push(subscriber); } catch { // intentionally ignore subscribers where user lacks access diff --git a/docs/api-reference/endpoints/pki/subscribers/create.mdx b/docs/api-reference/endpoints/pki/subscribers/create.mdx new file mode 100644 index 000000000..14a53b7fa --- /dev/null +++ b/docs/api-reference/endpoints/pki/subscribers/create.mdx @@ -0,0 +1,4 @@ +--- +title: "Create" +openapi: "POST /api/v1/pki/subscribers" +--- diff --git a/docs/api-reference/endpoints/pki/subscribers/delete.mdx b/docs/api-reference/endpoints/pki/subscribers/delete.mdx new file mode 100644 index 000000000..5975b89e9 --- /dev/null +++ b/docs/api-reference/endpoints/pki/subscribers/delete.mdx @@ -0,0 +1,4 @@ +--- +title: "Delete" +openapi: "DELETE /api/v1/pki/subscribers/{subscriberName}" +--- diff --git a/docs/api-reference/endpoints/pki/subscribers/issue-cert.mdx b/docs/api-reference/endpoints/pki/subscribers/issue-cert.mdx new file mode 100644 index 000000000..be57ab01b --- /dev/null +++ b/docs/api-reference/endpoints/pki/subscribers/issue-cert.mdx @@ -0,0 +1,4 @@ +--- +title: "Issue Certificate" +openapi: "POST /api/v1/pki/subscribers/{subscriberName}/issue-cert" +--- diff --git a/docs/api-reference/endpoints/pki/subscribers/list-certs.mdx b/docs/api-reference/endpoints/pki/subscribers/list-certs.mdx new file mode 100644 index 000000000..3a4607303 --- /dev/null +++ b/docs/api-reference/endpoints/pki/subscribers/list-certs.mdx @@ -0,0 +1,4 @@ +--- +title: "List Certificates" +openapi: "GET /api/v1/pki/subscribers/{subscriberName}/certificates" +--- diff --git a/docs/api-reference/endpoints/pki/subscribers/read.mdx b/docs/api-reference/endpoints/pki/subscribers/read.mdx new file mode 100644 index 000000000..0d223217d --- /dev/null +++ b/docs/api-reference/endpoints/pki/subscribers/read.mdx @@ -0,0 +1,4 @@ +--- +title: "Retrieve" +openapi: "GET /api/v1/pki/subscribers/{subscriberName}" +--- diff --git a/docs/api-reference/endpoints/pki/subscribers/sign-cert.mdx b/docs/api-reference/endpoints/pki/subscribers/sign-cert.mdx new file mode 100644 index 000000000..d31d30239 --- /dev/null +++ b/docs/api-reference/endpoints/pki/subscribers/sign-cert.mdx @@ -0,0 +1,4 @@ +--- +title: "Sign Certificate" +openapi: "POST /api/v1/pki/subscribers/{subscriberName}/sign-certificate" +--- diff --git a/docs/api-reference/endpoints/pki/subscribers/update.mdx b/docs/api-reference/endpoints/pki/subscribers/update.mdx new file mode 100644 index 000000000..5b62cbe7d --- /dev/null +++ b/docs/api-reference/endpoints/pki/subscribers/update.mdx @@ -0,0 +1,4 @@ +--- +title: "Update" +openapi: "PATCH /api/v1/pki/subscribers/{subscriberName}" +--- diff --git a/docs/api-reference/endpoints/ssh/groups/add-host.mdx b/docs/api-reference/endpoints/ssh/groups/add-host.mdx index 9f903eccd..77257cd40 100644 --- a/docs/api-reference/endpoints/ssh/groups/add-host.mdx +++ b/docs/api-reference/endpoints/ssh/groups/add-host.mdx @@ -1,4 +1,4 @@ --- title: "Add Host" -openapi: "POST /api/v1/ssh/host-groups/{sshHostGroupId}/hosts" +openapi: "POST /api/v1/ssh/host-groups/{sshHostGroupId}/hosts/{hostId}" --- diff --git a/docs/api-reference/endpoints/ssh/groups/remove-host.mdx b/docs/api-reference/endpoints/ssh/groups/remove-host.mdx index 6933e5c9f..b1de7f4ae 100644 --- a/docs/api-reference/endpoints/ssh/groups/remove-host.mdx +++ b/docs/api-reference/endpoints/ssh/groups/remove-host.mdx @@ -1,4 +1,4 @@ --- title: "Remove Host" -openapi: "DELETE /api/v1/ssh/host-groups/{sshHostGroupId}/hosts/{sshHostId}" +openapi: "DELETE /api/v1/ssh/host-groups/{sshHostGroupId}/hosts/{hostId}" --- diff --git a/docs/api-reference/endpoints/ssh/hosts/list-my.mdx b/docs/api-reference/endpoints/ssh/hosts/list-my.mdx index 2b7ab51c0..6ccc4e325 100644 --- a/docs/api-reference/endpoints/ssh/hosts/list-my.mdx +++ b/docs/api-reference/endpoints/ssh/hosts/list-my.mdx @@ -1,4 +1,4 @@ --- title: "List My Hosts" -openapi: "GET /api/v1/ssh/hosts/" +openapi: "GET /api/v1/ssh/hosts" --- diff --git a/docs/documentation/platform/pki/certificates.mdx b/docs/documentation/platform/pki/certificates.mdx index 4976b5e18..9ca08c059 100644 --- a/docs/documentation/platform/pki/certificates.mdx +++ b/docs/documentation/platform/pki/certificates.mdx @@ -75,8 +75,8 @@ In the following steps, we explore how to issue a X.509 certificate under a CA. Here's some guidance on each field: - Friendly Name: A friendly name for the certificate; this is only for display and defaults to the common name of the certificate if left empty. - - Common Name (CN): The (common) name for the certificate like `service.acme.com`. - - Alternative Names (SANs): A comma-delimited list of Subject Alternative Names (SANs) for the certificate; these can be host names or email addresses like `app1.acme.com, app2.acme.com`. + - Common Name (CN): The common name for the certificate like `service.acme.com`. + - Alternative Names (SANs): A comma-delimited list of Subject Alternative Names (SANs) for the certificate; these can be hostnames or email addresses like `app1.acme.com, app2.acme.com`. - TTL: The lifetime of the certificate in seconds. - Key Usage: The key usage extension of the certificate. - Extended Key Usage: The extended key usage extension of the certificate. diff --git a/docs/documentation/platform/pki/overview.mdx b/docs/documentation/platform/pki/overview.mdx index 259f15a5d..8ee9b113d 100644 --- a/docs/documentation/platform/pki/overview.mdx +++ b/docs/documentation/platform/pki/overview.mdx @@ -4,9 +4,10 @@ sidebarTitle: "Overview" description: "Learn how to create a Private CA hierarchy and issue X.509 certificates." --- -Infisical can be used to create a Private Certificate Authority (CA) hierarchy and issue X.509 certificates for internal use. This allows you to manage your own PKI infrastructure and issue digital certificates for services, applications, and devices. +Infisical can be used to create a Private Certificate Authority (CA) hierarchy and issue X.509 certificates for internal use. This allows you to manage your own PKI infrastructure and issue digital certificates for subscribers such as services, applications, and devices. -Infisical's internal PKI offering is split into two modules: +Infisical's PKI offering is split into three components: -- [Private CA](/documentation/platform/pki/private-ca): Infisical lets you create private CAs, including root and intermediary CAs. -- [Certificates](/documentation/platform/pki/certificates): Infisical allows you to issue X.509 certificates using the private CAs you create. +- [Certificate Authorities](/documentation/platform/pki/private-ca): Create and manage private CAs, including root and intermediate CAs. +- [Subscribers](/documentation/platform/pki/subscribers): Define and manage entities that will request X.509 certificates from CAs. This module provides a centralized view of all subscribers, enabling you to issue certificates and monitor their status. +- [Certificates](/documentation/platform/pki/certificates): Track and monitor issued X.509 certificates, maintaining a comprehensive inventory of all active and expired certificates. diff --git a/docs/documentation/platform/pki/private-ca.mdx b/docs/documentation/platform/pki/private-ca.mdx index d7f3f896c..7d7ee1220 100644 --- a/docs/documentation/platform/pki/private-ca.mdx +++ b/docs/documentation/platform/pki/private-ca.mdx @@ -7,7 +7,7 @@ description: "Learn how to create a Private CA hierarchy with Infisical." ## Concept The first step to creating your Internal PKI is to create a Private Certificate Authority (CA) hierarchy that is a structure of entities -used to issue digital certificates for services, applications, and devices. +used to issue digital certificates for your [subscribers](/documentation/platform/pki/subscribers).
@@ -24,7 +24,7 @@ graph TD A typical workflow for setting up a Private CA hierarchy consists of the following steps: -1. Configuring an Infisical root CA with details like name, validity period, and path length — This step is optional if you wish to use an external root CA. +1. Configuring an Infisical root CA with details like name, validity period, and path length — This step is optional if you wish to use an external root CA with Infisical only serving the intermediate CAs. 2. Configuring and chaining intermediate CA(s) with details like name, validity period, path length, and imported certificate to your Root CA. 3. Managing the CA lifecycle events such as CA succession. @@ -99,7 +99,7 @@ consisting of an (optional) root CA and an intermediate CA. ![pki cas](/images/platform/pki/ca/cas.png) Great! You've successfully created a Private CA hierarchy with a root CA and an intermediate CA. - Now check out the [Certificates](/documentation/platform/pki/certificates) page to learn more about how to issue X.509 certificates using the intermediate CA. + Now check out the [Subscribers](/documentation/platform/pki/subscribers) page to learn more about how to issue X.509 certificates using the intermediate CA. 2.3b. If you have an external root CA, select **External CA** for the **Parent CA Type** field. @@ -110,7 +110,7 @@ consisting of an (optional) root CA and an intermediate CA. Finally, press **Install** to import the certificate and certificate chain as part of the installation step for the intermediate CA Great! You've successfully created a Private CA hierarchy with an intermediate CA chained to an external root CA. - Now check out the [Certificates](/documentation/platform/pki/certificates) page to learn more about how to issue X.509 certificates using the intermediate CA. + Now check out the [Subscribers](/documentation/platform/pki/subscribers) page to learn more about how to issue X.509 certificates using the intermediate CA. @@ -255,7 +255,7 @@ consisting of an (optional) root CA and an intermediate CA. } ``` - Great! You’ve successfully created a Private CA hierarchy with a root CA and an intermediate CA. Now check out the Certificates page to learn more about how to issue X.509 certificates using the intermediate CA. + Great! You’ve successfully created a Private CA hierarchy with a root CA and an intermediate CA. Now check out the [Subscribers](/documentation/platform/pki/subscribers) page to learn more about how to issue X.509 certificates using the intermediate CA. diff --git a/docs/documentation/platform/pki/subscribers.mdx b/docs/documentation/platform/pki/subscribers.mdx new file mode 100644 index 000000000..1a15c7f91 --- /dev/null +++ b/docs/documentation/platform/pki/subscribers.mdx @@ -0,0 +1,131 @@ +--- +title: "Subscribers" +sidebarTitle: "Subscribers" +description: "Learn how to manage PKI subscribers and issue X.509 certificates for them." +--- + +## Concept + +In Infisical PKI, subscribers are logical representatiosn of entities such as devices, servers, applications that request and receive certificates from Certificate Authorities (CAs). + +
+ +```mermaid +graph TD + A[Root CA] --> B[Intermediate CA] + B --> C1[Certificate] + C1 --> S1[Subscriber] + B --> C2[Certificate] + C2 --> S2[Subscriber] +``` + +
+ +## Workflow + +The typical workflow for managing subscribers consists of the following steps: + +1. Creating a subscriber and defining attributes to be included on the X.509 certificates issued for it including common name, subject alternative names, TLL, etc. +2. Requesting for a certificate against the subscriber with or without a certificate signing request (CSR). +3. Managing certificate lifecycle events such as certificate renewal and revocation. As part of the certificate revocation flow, + you can also query for a Certificate Revocation List [CRL](https://en.wikipedia.org/wiki/Certificate_revocation_list), a time-stamped, signed + data structure issued by a CA containing a list of revoked certificates to check if a certificate has been revoked. + + + Note that this workflow can be executed via the Infisical UI or manually such + as via API. + + +## Guide to Issuing Certificates with Subscribers + +In the following steps, we explore how to issue a X.509 certificate for a subscriber. + + + + A subscriber is the logical representation of an entity that requests and + receives certificates from a CA. With a subscriber, you can specify the + attributes that must be present on the X.509 certificates issued for it. + + Head to your Infisical PKI Project > Subscribers to create a subscriber. + + ![pki create subscriber](/images/platform/pki/subscriber/subscriber-create.png) + + ![pki create subscriber 2](/images/platform/pki/subscriber/subscriber-create-2.png) + + Here's some guidance on each field. + + - Subscriber Name: A slug-friendly name for the subscriber such as `web-service`. + - Issuing CA: The Certificate Authority (CA) that will issue X.509 certificates for the subscriber. + - Common Name (CN): The common name to be included on certificates to be issued to the subscriber. + - Subject Alternative Names (SANs): A comma-delimited list of Subject Alternative Names (SANs) to be included on certificates; these can be hostnames or email addresses like `app1.acme.com, app2.acme.com`. + - TTL: The lifetime of the certificate. + - Key Usage: The key usage extension of the certificate. + - Extended Key Usage: The extended key usage extension of the certificate. + + + It's possible to issue certificates for a subscriber with or without a certificate signing request (CSR). + - If requesting without a CSR, the attributes specified on the subscriber will be used to issue a certificate for the subscriber. + - If requesting with a CSR, the attributes on it will be validated against the attributes specified on the subscriber + and a certificate is only issued if they comply. + + + + + Once you have created a subscriber from step 1, you can issue a certificate for it. + + Press on the subscriber you want to issue a certificate for and click on the **Issue Certificate** button on that subscriber's page. + + ![pki issue subscriber certificate](/images/platform/pki/subscriber/subscriber-issue-cert.png) + + ![pki issue subscriber certificate 2](/images/platform/pki/subscriber/subscriber-issue-cert-2.png) + + + + +## Guide to Revoking Certificates + +In the following steps, we explore how to revoke a X.509 certificate and obtain a Certificate Revocation List (CRL) for a CA. + + + + Assuming that you've issued a certificate for a subscriber, you can revoke it by + selecting the **Revoke Certificate** option on the certificate you wish to revoke + on the subscriber's page. + + ![pki revoke subscriber certificate](/images/platform/pki/subscriber/subscriber-revoke-cert.png) + + + + In order to check the revocation status of a certificate, you can check it + against the CRL of a CA by heading to its Issuing CA and downloading the CRL. + + ![pki view crl](/images/platform/pki/subscriber/subscriber-ca-crl.png) + + To verify a certificate against the + downloaded CRL with OpenSSL, you can use the following command: + +```bash +openssl verify -crl_check -CAfile chain.pem -CRLfile crl.pem cert.pem +``` + +Note that you can also obtain the CRL from the certificate itself by +referencing the CRL distribution point extension on the certificate itself. + +To check a certificate against the CRL distribution point specified within it with OpenSSL, you can use the following command: + +```bash +openssl verify -verbose -crl_check -crl_download -CAfile chain.pem cert.pem +``` + + + + +## FAQ + + + + To renew a certificate, you have to issue a new certificate for the same + subscriber The original certificate will continue to be valid through its + original TTL unless explicitly revoked. + + diff --git a/docs/images/platform/pki/subscriber/subscriber-ca-crl.png b/docs/images/platform/pki/subscriber/subscriber-ca-crl.png new file mode 100644 index 000000000..35f7dad65 Binary files /dev/null and b/docs/images/platform/pki/subscriber/subscriber-ca-crl.png differ diff --git a/docs/images/platform/pki/subscriber/subscriber-create-2.png b/docs/images/platform/pki/subscriber/subscriber-create-2.png new file mode 100644 index 000000000..fdfa44d27 Binary files /dev/null and b/docs/images/platform/pki/subscriber/subscriber-create-2.png differ diff --git a/docs/images/platform/pki/subscriber/subscriber-create.png b/docs/images/platform/pki/subscriber/subscriber-create.png new file mode 100644 index 000000000..8a4709ea3 Binary files /dev/null and b/docs/images/platform/pki/subscriber/subscriber-create.png differ diff --git a/docs/images/platform/pki/subscriber/subscriber-issue-cert-2.png b/docs/images/platform/pki/subscriber/subscriber-issue-cert-2.png new file mode 100644 index 000000000..916c5aab9 Binary files /dev/null and b/docs/images/platform/pki/subscriber/subscriber-issue-cert-2.png differ diff --git a/docs/images/platform/pki/subscriber/subscriber-issue-cert.png b/docs/images/platform/pki/subscriber/subscriber-issue-cert.png new file mode 100644 index 000000000..f96c7db28 Binary files /dev/null and b/docs/images/platform/pki/subscriber/subscriber-issue-cert.png differ diff --git a/docs/images/platform/pki/subscriber/subscriber-revoke-cert.png b/docs/images/platform/pki/subscriber/subscriber-revoke-cert.png new file mode 100644 index 000000000..4601991c8 Binary files /dev/null and b/docs/images/platform/pki/subscriber/subscriber-revoke-cert.png differ diff --git a/docs/mint.json b/docs/mint.json index a25a70124..c34ce945d 100644 --- a/docs/mint.json +++ b/docs/mint.json @@ -112,6 +112,7 @@ "pages": [ "documentation/platform/pki/overview", "documentation/platform/pki/private-ca", + "documentation/platform/pki/subscribers", "documentation/platform/pki/certificates", "documentation/platform/pki/pki-issuer", "documentation/platform/pki/est", @@ -1427,6 +1428,18 @@ { "group": "Infisical PKI", "pages": [ + { + "group": "Subscribers", + "pages": [ + "api-reference/endpoints/pki/subscribers/list-certs", + "api-reference/endpoints/pki/subscribers/create", + "api-reference/endpoints/pki/subscribers/read", + "api-reference/endpoints/pki/subscribers/update", + "api-reference/endpoints/pki/subscribers/delete", + "api-reference/endpoints/pki/subscribers/issue-cert", + "api-reference/endpoints/pki/subscribers/sign-cert" + ] + }, { "group": "Certificate Authorities", "pages": [ diff --git a/frontend/public/lotties/pki-subscriber.json b/frontend/public/lotties/pki-subscriber.json new file mode 100644 index 000000000..f6e0ce16e --- /dev/null +++ b/frontend/public/lotties/pki-subscriber.json @@ -0,0 +1 @@ +{"v":"5.12.1","fr":60,"ip":0,"op":89,"w":430,"h":430,"nm":"wired-outline-88-document-user","ddd":0,"assets":[{"id":"comp_1","nm":"Content-12","fr":60,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"outline 4","parent":2,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.41,"y":0},"t":6,"s":[0.044,-73.171,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":35,"s":[0.044,-117.966,0],"to":[0,0,0],"ti":[0,0,0]},{"t":50,"s":[0.044,-100.171,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,22.108],[22.108,0],[0,-22.108],[-22.108,0]],"o":[[0,-22.108],[-22.108,0],[0,22.108],[22.108,0]],"v":[[40.03,0],[0,-40.03],[-40.03,0],[0,40.03]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3,"x":"var $bm_rt;\n$bm_rt = comp('wired-outline-88-document-user').layer('control').effect('secondary')('Color');"},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":18,"ix":5,"x":"var $bm_rt;\n$bm_rt = $bm_mul($bm_div(value, 3), comp('wired-outline-88-document-user').layer('control').effect('stroke')('Menu'));"},"lc":2,"lj":2,"bm":0,"nm":".secondary","mn":"ADBE Vector Graphic - Stroke","hd":false,"cl":"secondary"},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":844,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"outline 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.14,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[214.956,575.075,0],"to":[0,0,0],"ti":[0,0,0]},{"t":29,"s":[214.956,315.075,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[-30.376,0],[0,0],[0,-30.376]],"o":[[0,0],[0,0],[0,-30.376],[0,0],[30.376,0],[0,0]],"v":[[80.015,33.358],[-80.015,33.358],[-80.015,21.642],[-25.015,-33.358],[25.015,-33.358],[80.015,21.642]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3,"x":"var $bm_rt;\n$bm_rt = comp('wired-outline-88-document-user').layer('control').effect('secondary')('Color');"},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":18,"ix":5,"x":"var $bm_rt;\n$bm_rt = $bm_mul($bm_div(value, 3), comp('wired-outline-88-document-user').layer('control').effect('stroke')('Menu'));"},"lc":2,"lj":2,"bm":0,"nm":".secondary","mn":"ADBE Vector Graphic - Stroke","hd":false,"cl":"secondary"},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":844,"st":0,"ct":1,"bm":0}]},{"id":"comp_3","nm":"Content-36","fr":60,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"outline 4","parent":2,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.41,"y":0},"t":6,"s":[0.044,-73.171,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":35,"s":[0.044,-117.966,0],"to":[0,0,0],"ti":[0,0,0]},{"t":50,"s":[0.044,-100.171,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,22.108],[22.108,0],[0,-22.108],[-22.108,0]],"o":[[0,-22.108],[-22.108,0],[0,22.108],[22.108,0]],"v":[[40.03,0],[0,-40.03],[-40.03,0],[0,40.03]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3,"x":"var $bm_rt;\n$bm_rt = comp('wired-outline-88-document-user').layer('control').effect('secondary')('Color');"},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":18,"ix":5,"x":"var $bm_rt;\n$bm_rt = $bm_mul($bm_div(value, 3), comp('wired-outline-88-document-user').layer('control').effect('stroke')('Menu'));"},"lc":2,"lj":2,"bm":0,"nm":".secondary","mn":"ADBE Vector Graphic - Stroke","hd":false,"cl":"secondary"},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":844,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"outline 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.14,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[214.956,575.075,0],"to":[0,0,0],"ti":[0,0,0]},{"t":29,"s":[214.956,315.075,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[-30.376,0],[0,0],[0,-30.376]],"o":[[0,0],[0,0],[0,-30.376],[0,0],[30.376,0],[0,0]],"v":[[80.015,33.358],[-80.015,33.358],[-80.015,21.642],[-25.015,-33.358],[25.015,-33.358],[80.015,21.642]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3,"x":"var $bm_rt;\n$bm_rt = comp('wired-outline-88-document-user').layer('control').effect('secondary')('Color');"},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":18,"ix":5,"x":"var $bm_rt;\n$bm_rt = $bm_mul($bm_div(value, 3), comp('wired-outline-88-document-user').layer('control').effect('stroke')('Menu'));"},"lc":2,"lj":2,"bm":0,"nm":".secondary","mn":"ADBE Vector Graphic - Stroke","hd":false,"cl":"secondary"},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":844,"st":0,"ct":1,"bm":0}]},{"id":"comp_4","nm":"hover-swipe","fr":60,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Page-corner","parent":2,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[250.001,249.76,0],"ix":2,"l":2},"a":{"a":0,"k":[250.001,249.76,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.22,"y":1},"o":{"x":0.333,"y":0},"t":42,"s":[{"i":[[0,0],[-49.694,-50.431],[0,0]],"o":[[0,0],[50.313,51.06],[0,0]],"v":[[-53.373,-53.373],[-0.373,-0.627],[53.373,53.373]],"c":false}]},{"t":89,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-53.373,-53.373],[-53.373,53.373],[53.373,53.373]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3,"x":"var $bm_rt;\n$bm_rt = comp('wired-outline-88-document-user').layer('control').effect('primary')('Color');"},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":18,"ix":5,"x":"var $bm_rt;\n$bm_rt = $bm_mul($bm_div(value, 3), comp('wired-outline-88-document-user').layer('control').effect('stroke')('Menu'));"},"lc":2,"lj":2,"bm":0,"nm":".primary","mn":"ADBE Vector Graphic - Stroke","hd":false,"cl":"primary"},{"ty":"tr","p":{"a":0,"k":[330.06,116.567],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":844,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Page","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.243],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[0]},{"i":{"x":[0.326],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":20,"s":[9]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":47,"s":[-7]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":70,"s":[5]},{"t":89,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.243,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[317.001,368.76,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.326,"y":1},"o":{"x":0.333,"y":0},"t":20,"s":[351.001,381.76,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":42,"s":[291.751,356.51,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":65,"s":[321.001,369.26,0],"to":[0,0,0],"ti":[0,0,0]},{"t":80,"s":[317.001,368.76,0]}],"ix":2,"l":2},"a":{"a":0,"k":[352.001,403.76,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":0,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-53.373,-53.373],[-53.373,53.373],[53.373,53.373]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":20,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-53.373,-53.373],[-53.373,53.373],[53.373,53.373]],"c":false}]},{"t":38,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-213.237,-53.373],[-213.237,319.57],[53.373,319.57]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3,"x":"var $bm_rt;\n$bm_rt = comp('wired-outline-88-document-user').layer('control').effect('primary')('Color');"},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":18,"ix":5,"x":"var $bm_rt;\n$bm_rt = $bm_mul($bm_div(value, 3), comp('wired-outline-88-document-user').layer('control').effect('stroke')('Menu'));"},"lc":2,"lj":2,"bm":0,"nm":".primary","mn":"ADBE Vector Graphic - Stroke","hd":false,"cl":"primary"},{"ty":"tr","p":{"a":0,"k":[330.06,116.567],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":1,"k":[{"t":20,"s":[100],"h":1},{"t":38,"s":[0],"h":1}],"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[26.69,-186.57],[-133.43,-186.57],[-133.43,186.57],[133.43,186.57],[133.43,-79.82]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3,"x":"var $bm_rt;\n$bm_rt = comp('wired-outline-88-document-user').layer('control').effect('primary')('Color');"},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":18,"ix":5,"x":"var $bm_rt;\n$bm_rt = $bm_mul($bm_div(value, 3), comp('wired-outline-88-document-user').layer('control').effect('stroke')('Menu'));"},"lc":2,"lj":2,"bm":0,"nm":".primary","mn":"ADBE Vector Graphic - Stroke","hd":false,"cl":"primary"},{"ty":"tr","p":{"a":0,"k":[250,249.76],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":844,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"mask","parent":2,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[249.001,249.76,0],"ix":2,"l":2},"a":{"a":0,"k":[250.001,249.76,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":20,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[26.69,-186.57],[26.75,-186.57],[26.75,-79.76],[133.43,-79.76],[133.43,-79.82]],"c":true}]},{"t":38,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[26.69,-186.57],[-133.43,-186.57],[-133.43,186.57],[133.43,186.57],[133.43,-79.82]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[250,249.76],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":51,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":0,"nm":"Content-12","parent":2,"tt":2,"tp":3,"refId":"comp_1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[250,250,0],"ix":2,"l":2},"a":{"a":0,"k":[215,215,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"w":430,"h":430,"ip":0,"op":51,"st":-50,"bm":0},{"ddd":0,"ind":5,"ty":0,"nm":"Content-12","parent":2,"refId":"comp_1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[250,250,0],"ix":2,"l":2},"a":{"a":0,"k":[215,215,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"hasMask":true,"masksProperties":[{"inv":false,"mode":"a","pt":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[348.631,28.403],[82.211,28.403],[82.211,401.557],[348.631,401.557]],"c":true},"ix":1},"o":{"a":0,"k":100,"ix":3},"x":{"a":0,"k":0,"ix":4},"nm":"Mask 1"}],"w":430,"h":430,"ip":37.5,"op":881.5,"st":37.5,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"control","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ef":[{"ty":5,"nm":"stroke","np":3,"mn":"Pseudo/@@jxAy4KF1Sn6X4aYQ0vVH/w","ix":1,"en":1,"ef":[{"ty":7,"nm":"Menu","mn":"Pseudo/@@jxAy4KF1Sn6X4aYQ0vVH/w-0001","ix":1,"v":{"a":0,"k":3,"ix":1}}]},{"ty":5,"nm":"primary","np":3,"mn":"ADBE Color Control","ix":2,"en":1,"ef":[{"ty":2,"nm":"Color","mn":"ADBE Color Control-0001","ix":1,"v":{"a":0,"k":[1,1,1],"ix":1}}]},{"ty":5,"nm":"secondary","np":3,"mn":"ADBE Color Control","ix":3,"en":1,"ef":[{"ty":2,"nm":"Color","mn":"ADBE Color Control-0001","ix":1,"v":{"a":0,"k":[1,1,1],"ix":1}}]}],"ip":0,"op":360,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":0,"nm":"hover-swipe","refId":"comp_4","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[215,215,0],"ix":2,"l":2},"a":{"a":0,"k":[215,215,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"w":430,"h":430,"ip":0,"op":99,"st":0,"bm":0}],"markers":[{"tm":0,"cm":"default:hover-swipe","dr":89}],"props":{}} \ No newline at end of file diff --git a/frontend/src/const/routes.ts b/frontend/src/const/routes.ts index 7f79a0fb2..dd963594e 100644 --- a/frontend/src/const/routes.ts +++ b/frontend/src/const/routes.ts @@ -300,8 +300,8 @@ export const ROUTE_PATHS = Object.freeze({ "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/pki-collections/$collectionId" ), PkiSubscriberDetailsByIDPage: setRoute( - "/cert-manager/$projectId/subscribers/$subscriberId", - "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers/$subscriberId" + "/cert-manager/$projectId/subscribers/$subscriberName", + "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers/$subscriberName" ) }, Ssh: { diff --git a/frontend/src/context/ProjectPermissionContext/index.tsx b/frontend/src/context/ProjectPermissionContext/index.tsx index 7736e9de1..d7b7334ea 100644 --- a/frontend/src/context/ProjectPermissionContext/index.tsx +++ b/frontend/src/context/ProjectPermissionContext/index.tsx @@ -10,5 +10,6 @@ export { ProjectPermissionKmipActions, ProjectPermissionMemberActions, ProjectPermissionPkiSubscriberActions, + ProjectPermissionSshHostActions, ProjectPermissionSub } from "./types"; diff --git a/frontend/src/context/ProjectPermissionContext/types.ts b/frontend/src/context/ProjectPermissionContext/types.ts index 7e31ceaa9..a640f6eaa 100644 --- a/frontend/src/context/ProjectPermissionContext/types.ts +++ b/frontend/src/context/ProjectPermissionContext/types.ts @@ -100,7 +100,8 @@ export enum ProjectPermissionPkiSubscriberActions { Create = "create", Edit = "edit", Delete = "delete", - IssueCert = "issue-cert" + IssueCert = "issue-cert", + ListCerts = "list-certs" } export enum ProjectPermissionSecretRotationActions { @@ -229,6 +230,14 @@ export type SecretRotationSubjectFields = { secretPath: string; }; +export type SshHostSubjectFields = { + hostname: string; +}; + +export type PkiSubscriberSubjectFields = { + name: string; +}; + export type ProjectPermissionSet = | [ ProjectPermissionSecretActions, @@ -291,10 +300,22 @@ export type ProjectPermissionSet = | [ProjectPermissionActions, ProjectPermissionSub.SshCertificateTemplates] | [ProjectPermissionActions, ProjectPermissionSub.SshCertificates] | [ProjectPermissionActions, ProjectPermissionSub.SshHostGroups] - | [ProjectPermissionSshHostActions, ProjectPermissionSub.SshHosts] + | [ + ProjectPermissionSshHostActions, + ( + | ProjectPermissionSub.SshHosts + | (ForcedSubject & SshHostSubjectFields) + ) + ] + | [ + ProjectPermissionPkiSubscriberActions, + ( + | ProjectPermissionSub.PkiSubscribers + | (ForcedSubject & PkiSubscriberSubjectFields) + ) + ] | [ProjectPermissionActions, ProjectPermissionSub.PkiAlerts] | [ProjectPermissionActions, ProjectPermissionSub.PkiCollections] - | [ProjectPermissionPkiSubscriberActions, ProjectPermissionSub.PkiSubscribers] | [ProjectPermissionSecretSyncActions, ProjectPermissionSub.SecretSyncs] | [ProjectPermissionActions.Delete, ProjectPermissionSub.Project] | [ProjectPermissionActions.Edit, ProjectPermissionSub.Project] diff --git a/frontend/src/context/index.tsx b/frontend/src/context/index.tsx index bd2b8e062..91fcd9055 100644 --- a/frontend/src/context/index.tsx +++ b/frontend/src/context/index.tsx @@ -18,6 +18,7 @@ export { ProjectPermissionKmipActions, ProjectPermissionMemberActions, ProjectPermissionPkiSubscriberActions, + ProjectPermissionSshHostActions, ProjectPermissionSub, useProjectPermission } from "./ProjectPermissionContext"; diff --git a/frontend/src/hooks/api/pkiSubscriber/constants.tsx b/frontend/src/hooks/api/pkiSubscriber/constants.tsx new file mode 100644 index 000000000..1de5e9ddb --- /dev/null +++ b/frontend/src/hooks/api/pkiSubscriber/constants.tsx @@ -0,0 +1,20 @@ +export enum PkiSubscriberStatus { + ACTIVE = "active", + DISABLED = "disabled" +} + +export const pkiSubscriberStatusToNameMap: { [K in PkiSubscriberStatus]: string } = { + [PkiSubscriberStatus.ACTIVE]: "Active", + [PkiSubscriberStatus.DISABLED]: "Disabled" +}; + +export const getPkiSubscriberStatusBadgeVariant = (status: PkiSubscriberStatus) => { + switch (status) { + case PkiSubscriberStatus.ACTIVE: + return "success"; + case PkiSubscriberStatus.DISABLED: + return "danger"; + default: + return "primary"; + } +}; diff --git a/frontend/src/hooks/api/pkiSubscriber/index.tsx b/frontend/src/hooks/api/pkiSubscriber/index.tsx index b7bb58623..b086839df 100644 --- a/frontend/src/hooks/api/pkiSubscriber/index.tsx +++ b/frontend/src/hooks/api/pkiSubscriber/index.tsx @@ -1,6 +1,7 @@ export { useCreatePkiSubscriber, useDeletePkiSubscriber, + useIssuePkiSubscriberCert, useUpdatePkiSubscriber } from "./mutations"; -export { useGetPkiSubscriberById } from "./queries"; +export { useGetPkiSubscriber, useGetPkiSubscriberCertificates } from "./queries"; diff --git a/frontend/src/hooks/api/pkiSubscriber/mutations.tsx b/frontend/src/hooks/api/pkiSubscriber/mutations.tsx index 978009ae7..a7d0eef92 100644 --- a/frontend/src/hooks/api/pkiSubscriber/mutations.tsx +++ b/frontend/src/hooks/api/pkiSubscriber/mutations.tsx @@ -2,10 +2,13 @@ import { useMutation, useQueryClient } from "@tanstack/react-query"; import { apiRequest } from "@app/config/request"; +import { TCreateCertificateResponse } from "../ca/types"; import { workspaceKeys } from "../workspace/query-keys"; +import { pkiSubscriberKeys } from "./queries"; import { TCreatePkiSubscriberDTO, TDeletePkiSubscriberDTO, + TIssuePkiSubscriberCertDTO, TPkiSubscriber, TUpdatePkiSubscriberDTO } from "./types"; @@ -17,10 +20,16 @@ export const useCreatePkiSubscriber = () => { const { data: subscriber } = await apiRequest.post("/api/v1/pki/subscribers", body); return subscriber; }, - onSuccess: ({ projectId }) => { + onSuccess: ({ projectId, name }) => { queryClient.invalidateQueries({ queryKey: workspaceKeys.getWorkspacePkiSubscribers(projectId) }); + queryClient.invalidateQueries({ + queryKey: pkiSubscriberKeys.getPkiSubscriber({ + subscriberName: name, + projectId + }) + }); } }); }; @@ -28,17 +37,23 @@ export const useCreatePkiSubscriber = () => { export const useUpdatePkiSubscriber = () => { const queryClient = useQueryClient(); return useMutation({ - mutationFn: async ({ subscriberId, ...body }) => { + mutationFn: async ({ subscriberName, ...body }) => { const { data: subscriber } = await apiRequest.patch( - `/api/v1/pki/subscribers/${subscriberId}`, + `/api/v1/pki/subscribers/${subscriberName}`, body ); return subscriber; }, - onSuccess: ({ projectId }) => { + onSuccess: ({ projectId, name }) => { queryClient.invalidateQueries({ queryKey: workspaceKeys.getWorkspacePkiSubscribers(projectId) }); + queryClient.invalidateQueries({ + queryKey: pkiSubscriberKeys.getPkiSubscriber({ + subscriberName: name, + projectId + }) + }); } }); }; @@ -46,16 +61,50 @@ export const useUpdatePkiSubscriber = () => { export const useDeletePkiSubscriber = () => { const queryClient = useQueryClient(); return useMutation({ - mutationFn: async ({ subscriberId }) => { + mutationFn: async ({ subscriberName, projectId }) => { const { data: subscriber } = await apiRequest.delete( - `/api/v1/pki/subscribers/${subscriberId}` + `/api/v1/pki/subscribers/${subscriberName}`, + { + data: { + projectId + } + } ); return subscriber; }, - onSuccess: ({ projectId }) => { + onSuccess: ({ name, projectId }) => { queryClient.invalidateQueries({ queryKey: workspaceKeys.getWorkspacePkiSubscribers(projectId) }); + queryClient.invalidateQueries({ + queryKey: pkiSubscriberKeys.getPkiSubscriber({ + subscriberName: name, + projectId + }) + }); + } + }); +}; + +export const useIssuePkiSubscriberCert = () => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: async ({ subscriberName, projectId }) => { + const { data } = await apiRequest.post( + `/api/v1/pki/subscribers/${subscriberName}/issue-certificate`, + { + projectId + } + ); + return data; + }, + onSuccess: (_, { subscriberName, projectId }) => { + queryClient.invalidateQueries({ + queryKey: pkiSubscriberKeys.forPkiSubscriberCertificates({ + subscriberName, + projectId + }) + }); } }); }; diff --git a/frontend/src/hooks/api/pkiSubscriber/queries.tsx b/frontend/src/hooks/api/pkiSubscriber/queries.tsx index ca244568e..dcaa4de57 100644 --- a/frontend/src/hooks/api/pkiSubscriber/queries.tsx +++ b/frontend/src/hooks/api/pkiSubscriber/queries.tsx @@ -2,21 +2,101 @@ import { useQuery } from "@tanstack/react-query"; import { apiRequest } from "@app/config/request"; +import { TCertificate } from "../certificates/types"; import { TPkiSubscriber } from "./types"; export const pkiSubscriberKeys = { - getPkiSubscriberById: (subscriberId: string) => [{ subscriberId }, "pki-subscriber"] as const + getPkiSubscriber: ({ + subscriberName, + projectId + }: { + subscriberName: string; + projectId: string; + }) => [{ subscriberName, projectId }, "pki-subscriber"] as const, + allPkiSubscriberCertificates: () => ["pki-subscriber-certificates"] as const, + forPkiSubscriberCertificates: ({ + subscriberName, + projectId + }: { + subscriberName: string; + projectId: string; + }) => [...pkiSubscriberKeys.allPkiSubscriberCertificates(), subscriberName, projectId] as const, + specificPkiSubscriberCertificates: ({ + subscriberName, + projectId, + offset, + limit + }: { + subscriberName: string; + projectId: string; + offset: number; + limit: number; + }) => + [ + ...pkiSubscriberKeys.forPkiSubscriberCertificates({ subscriberName, projectId }), + { offset, limit, projectId } + ] as const }; -export const useGetPkiSubscriberById = (subscriberId: string) => { +export const useGetPkiSubscriber = ({ + subscriberName, + projectId +}: { + subscriberName: string; + projectId: string; +}) => { return useQuery({ - queryKey: pkiSubscriberKeys.getPkiSubscriberById(subscriberId), + queryKey: pkiSubscriberKeys.getPkiSubscriber({ subscriberName, projectId }), queryFn: async () => { const { data: pkiSubscriber } = await apiRequest.get( - `/api/v1/pki/subscribers/${subscriberId}` + `/api/v1/pki/subscribers/${subscriberName}`, + { + params: { + projectId + } + } ); return pkiSubscriber; }, - enabled: Boolean(subscriberId) + enabled: Boolean(subscriberName) && Boolean(projectId) + }); +}; + +export const useGetPkiSubscriberCertificates = ({ + subscriberName, + projectId, + offset, + limit +}: { + subscriberName: string; + projectId: string; + offset: number; + limit: number; +}) => { + return useQuery({ + queryKey: pkiSubscriberKeys.specificPkiSubscriberCertificates({ + subscriberName, + projectId, + offset: 0, + limit: 25 + }), + queryFn: async () => { + const params = new URLSearchParams({ + offset: String(offset), + limit: String(limit), + projectId + }); + + const { + data: { certificates, totalCount } + } = await apiRequest.get<{ certificates: TCertificate[]; totalCount: number }>( + `/api/v1/pki/subscribers/${subscriberName}/certificates`, + { + params + } + ); + return { certificates, totalCount }; + }, + enabled: Boolean(subscriberName) && Boolean(projectId) }); }; diff --git a/frontend/src/hooks/api/pkiSubscriber/types.ts b/frontend/src/hooks/api/pkiSubscriber/types.ts index c499721d4..e6050dd13 100644 --- a/frontend/src/hooks/api/pkiSubscriber/types.ts +++ b/frontend/src/hooks/api/pkiSubscriber/types.ts @@ -1,11 +1,17 @@ import { CertExtendedKeyUsage, CertKeyUsage } from "../certificates/enums"; +export enum PkiSubscriberStatus { + ACTIVE = "active", + DISABLED = "disabled" +} + export type TPkiSubscriber = { id: string; projectId: string; caId: string; name: string; commonName: string; + status: PkiSubscriberStatus; ttl: string; subjectAlternativeNames: string[]; keyUsages: CertKeyUsage[]; @@ -24,10 +30,12 @@ export type TCreatePkiSubscriberDTO = { }; export type TUpdatePkiSubscriberDTO = { - subscriberId: string; + subscriberName: string; + projectId: string; caId?: string; name?: string; commonName?: string; + status?: PkiSubscriberStatus; ttl?: string; subjectAlternativeNames?: string[]; keyUsages?: CertKeyUsage[]; @@ -35,5 +43,11 @@ export type TUpdatePkiSubscriberDTO = { }; export type TDeletePkiSubscriberDTO = { - subscriberId: string; + subscriberName: string; + projectId: string; +}; + +export type TIssuePkiSubscriberCertDTO = { + subscriberName: string; + projectId: string; }; diff --git a/frontend/src/layouts/ProjectLayout/ProjectLayout.tsx b/frontend/src/layouts/ProjectLayout/ProjectLayout.tsx index fb14d0e93..af90f79de 100644 --- a/frontend/src/layouts/ProjectLayout/ProjectLayout.tsx +++ b/frontend/src/layouts/ProjectLayout/ProjectLayout.tsx @@ -112,7 +112,7 @@ export const ProjectLayout = () => { }} > {({ isActive }) => ( - + Subscribers )} diff --git a/frontend/src/pages/cert-manager/AlertingPage/AlertingPage.tsx b/frontend/src/pages/cert-manager/AlertingPage/AlertingPage.tsx index 40e3eb100..97d50b9bf 100644 --- a/frontend/src/pages/cert-manager/AlertingPage/AlertingPage.tsx +++ b/frontend/src/pages/cert-manager/AlertingPage/AlertingPage.tsx @@ -15,7 +15,10 @@ export const AlertingPage = () => { {t("common.head-title", { title: "Alerting" })}
- + { {t("common.head-title", { title: "Certificate Authorities" })}
- + { {t("common.head-title", { title: "Certificates" })}
- + {/* If both are false, the section does not render. This is to prevent duplicate banners. */} {(canAccessCerts || canAccessPkiColl) && ( { - + @@ -85,7 +85,7 @@ export const CertificatesTable = ({ handlePopUpOpen }: Props) => { const { variant, label } = getCertValidUntilBadgeDetails(certificate.notAfter); return ( - + + - {isPending && } + {isPending && } {!isPending && data && data.length > 0 && @@ -61,18 +74,22 @@ export const PkiSubscribersTable = ({ handlePopUpOpen }: Props) => { { - e.preventDefault(); + onClick={() => navigate({ - to: `/${ProjectType.CertificateManager}/$projectId/subscribers/$subscriberId` as const, + to: `/${ProjectType.CertificateManager}/$projectId/subscribers/$subscriberName` as const, params: { projectId: currentWorkspace.id, - subscriberId: subscriber.id + subscriberName: subscriber.name } - }); - }} + }) + } > +
Friendly NameCommon Name Status Not Before Not After
{certificate.friendlyName}{certificate.commonName} {certificate.status === CertStatus.REVOKED ? ( Revoked diff --git a/frontend/src/pages/cert-manager/PkiSubscriberDetailsByIDPage/PkiSubscriberDetailsByIDPage.tsx b/frontend/src/pages/cert-manager/PkiSubscriberDetailsByIDPage/PkiSubscriberDetailsByIDPage.tsx index 009934c5e..e355aa478 100644 --- a/frontend/src/pages/cert-manager/PkiSubscriberDetailsByIDPage/PkiSubscriberDetailsByIDPage.tsx +++ b/frontend/src/pages/cert-manager/PkiSubscriberDetailsByIDPage/PkiSubscriberDetailsByIDPage.tsx @@ -21,22 +21,25 @@ import { ProjectPermissionSub, useWorkspace } from "@app/context"; -import { useDeletePkiSubscriber, useGetPkiSubscriberById } from "@app/hooks/api"; +import { useDeletePkiSubscriber, useGetPkiSubscriber } from "@app/hooks/api"; import { ProjectType } from "@app/hooks/api/workspace/types"; import { usePopUp } from "@app/hooks/usePopUp"; import { PkiSubscriberModal } from "../PkiSubscribersPage/components/PkiSubscriberModal"; -// import { PkiSubscriberCertificatesSection, PkiSubscriberDetailsSection } from "./components"; +import { PkiSubscriberCertificatesSection, PkiSubscriberDetailsSection } from "./components"; const Page = () => { - const { currentWorkspace } = useWorkspace(); const navigate = useNavigate(); - const projectId = currentWorkspace?.id || ""; - const subscriberId = useParams({ + const { currentWorkspace } = useWorkspace(); + const projectId = currentWorkspace.id; + const subscriberName = useParams({ from: ROUTE_PATHS.CertManager.PkiSubscriberDetailsByIDPage.id, - select: (el) => el.subscriberId + select: (el) => el.subscriberName + }); + const { data } = useGetPkiSubscriber({ + subscriberName, + projectId }); - const { data } = useGetPkiSubscriberById(subscriberId); const { mutateAsync: deletePkiSubscriber } = useDeletePkiSubscriber(); @@ -45,14 +48,14 @@ const Page = () => { "deletePkiSubscriber" ] as const); - const onRemoveSubscriberSubmit = async (subscriberIdToDelete: string) => { + const onRemoveSubscriberSubmit = async (subscriberNameToDelete: string) => { try { if (!projectId) return; - await deletePkiSubscriber({ subscriberId: subscriberIdToDelete }); + await deletePkiSubscriber({ subscriberName: subscriberNameToDelete, projectId }); createNotification({ - text: "Successfully deleted PKI subscriber", + text: "Successfully deleted subscriber", type: "success" }); @@ -66,7 +69,7 @@ const Page = () => { } catch (err) { console.error(err); createNotification({ - text: "Failed to delete PKI subscriber", + text: "Failed to delete subscriber", type: "error" }); } @@ -114,15 +117,13 @@ const Page = () => {
- TODO: PkiSubscriberDetailsSection - {/* */} + />
- TODO: PkiSubscriberCertificatesSection - {/* */} +
@@ -152,28 +153,14 @@ export const PkiSubscriberDetailsByIDPage = () => { {t("common.head-title", { title: "PKI Subscriber" })} - {/* - */} - TESTTT + ); }; - -/** - * import { createFileRoute } from "@tanstack/react-router"; - -import { PkiSubscriberDetailsByIDPage } from "./PkiSubscriberDetailsByIDPage"; - -export const Route = createFileRoute( - "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers/$subscriberId" -)({ - component: PkiSubscriberDetailsByIDPage -}); - - */ diff --git a/frontend/src/pages/cert-manager/PkiSubscriberDetailsByIDPage/components/PkiSubscriberCertificatesSection.tsx b/frontend/src/pages/cert-manager/PkiSubscriberDetailsByIDPage/components/PkiSubscriberCertificatesSection.tsx new file mode 100644 index 000000000..2e486cf85 --- /dev/null +++ b/frontend/src/pages/cert-manager/PkiSubscriberDetailsByIDPage/components/PkiSubscriberCertificatesSection.tsx @@ -0,0 +1,27 @@ +import { usePopUp } from "@app/hooks"; +import { CertificateRevocationModal } from "@app/pages/cert-manager/CertificatesPage/components/CertificateRevocationModal"; + +import { PkiSubscriberCertificatesTable } from "./PkiSubscriberCertificatesTable"; + +type Props = { + subscriberName: string; +}; + +export const PkiSubscriberCertificatesSection = ({ subscriberName }: Props) => { + const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["revokeCertificate"] as const); + + return ( +
+
+

Certificates

+
+
+ +
+ +
+ ); +}; diff --git a/frontend/src/pages/cert-manager/PkiSubscriberDetailsByIDPage/components/PkiSubscriberCertificatesTable.tsx b/frontend/src/pages/cert-manager/PkiSubscriberDetailsByIDPage/components/PkiSubscriberCertificatesTable.tsx new file mode 100644 index 000000000..93abc3324 --- /dev/null +++ b/frontend/src/pages/cert-manager/PkiSubscriberDetailsByIDPage/components/PkiSubscriberCertificatesTable.tsx @@ -0,0 +1,166 @@ +import { useState } from "react"; +import { faCertificate, faEllipsis, faTrash } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { format } from "date-fns"; +import { twMerge } from "tailwind-merge"; + +import { ProjectPermissionCan } from "@app/components/permissions"; +import { + Badge, + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, + EmptyState, + Pagination, + Table, + TableContainer, + TableSkeleton, + TBody, + Td, + Th, + THead, + Tooltip, + Tr +} from "@app/components/v2"; +import { + ProjectPermissionPkiSubscriberActions, + ProjectPermissionSub, + useWorkspace +} from "@app/context"; +import { useGetPkiSubscriberCertificates } from "@app/hooks/api"; +import { CertStatus } from "@app/hooks/api/certificates/enums"; +import { UsePopUpState } from "@app/hooks/usePopUp"; + +type Props = { + subscriberName: string; + handlePopUpOpen?: (popUpName: keyof UsePopUpState<["revokeCertificate"]>, data?: object) => void; +}; + +const PER_PAGE_INIT = 25; + +export const PkiSubscriberCertificatesTable = ({ subscriberName, handlePopUpOpen }: Props) => { + const { currentWorkspace } = useWorkspace(); + const projectId = currentWorkspace.id; + const [page, setPage] = useState(1); + const [perPage, setPerPage] = useState(PER_PAGE_INIT); + + const { data, isPending } = useGetPkiSubscriberCertificates({ + subscriberName, + projectId, + offset: (page - 1) * perPage, + limit: perPage + }); + + const getCertStatusBadge = (status: string, notAfter: string) => { + if (status === CertStatus.REVOKED) { + return Revoked; + } + + const expiryDate = new Date(notAfter); + const now = new Date(); + const daysUntilExpiry = Math.floor( + (expiryDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24) + ); + + if (daysUntilExpiry < 0) { + return Expired; + } + + if (daysUntilExpiry < 30) { + return Expiring Soon; + } + + return Valid; + }; + + return ( +
+ + + + + + + + + + + + {isPending && } + {!isPending && + data?.certificates?.map((certificate) => { + return ( + + + + + + + + ); + })} + +
Common NameStatusNot BeforeNot After +
{certificate.commonName}{getCertStatusBadge(certificate.status, certificate.notAfter)} + {certificate.notBefore + ? format(new Date(certificate.notBefore), "yyyy-MM-dd") + : "-"} + + {certificate.notAfter + ? format(new Date(certificate.notAfter), "yyyy-MM-dd") + : "-"} + + + +
+ + + +
+
+ + + {(isAllowed) => ( + + handlePopUpOpen && + handlePopUpOpen("revokeCertificate", { + serialNumber: certificate.serialNumber + }) + } + disabled={!isAllowed} + icon={} + > + Revoke Certificate + + )} + + +
+
+ {!isPending && data?.totalCount !== undefined && data.totalCount >= PER_PAGE_INIT && ( + setPage(newPage)} + onChangePerPage={(newPerPage) => setPerPage(newPerPage)} + /> + )} + {!isPending && !data?.certificates?.length && ( + + )} +
+
+ ); +}; diff --git a/frontend/src/pages/cert-manager/PkiSubscriberDetailsByIDPage/components/PkiSubscriberDetailsSection.tsx b/frontend/src/pages/cert-manager/PkiSubscriberDetailsByIDPage/components/PkiSubscriberDetailsSection.tsx new file mode 100644 index 000000000..da11a0ba4 --- /dev/null +++ b/frontend/src/pages/cert-manager/PkiSubscriberDetailsByIDPage/components/PkiSubscriberDetailsSection.tsx @@ -0,0 +1,183 @@ +import { useState } from "react"; +import { subject } from "@casl/ability"; +import { faCheck, faCopy, faPencil } 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, IconButton, Modal, ModalContent, Tooltip } from "@app/components/v2"; +import { + ProjectPermissionPkiSubscriberActions, + ProjectPermissionSub, + useProjectPermission, + useWorkspace +} from "@app/context"; +import { useTimedReset } from "@app/hooks"; +import { useGetPkiSubscriber, useIssuePkiSubscriberCert } from "@app/hooks/api"; +import { UsePopUpState } from "@app/hooks/usePopUp"; + +import { CertificateContent } from "../../CertificatesPage/components/CertificateContent"; + +type Props = { + subscriberName: string; + handlePopUpOpen: (popUpName: keyof UsePopUpState<["pkiSubscriber"]>, data?: object) => void; +}; + +type TCertificateDetails = { + serialNumber: string; + certificate: string; + certificateChain: string; + privateKey: string; +}; + +export const PkiSubscriberDetailsSection = ({ subscriberName, handlePopUpOpen }: Props) => { + const { currentWorkspace } = useWorkspace(); + const projectId = currentWorkspace.id; + const { permission } = useProjectPermission(); + const [certificateDetails, setCertificateDetails] = useState(null); + const [isModalOpen, setIsModalOpen] = useState(false); + const [copyTextId, isCopyingId, setCopyTextId] = useTimedReset({ + initialState: "Copy ID to clipboard" + }); + + const { data: pkiSubscriber } = useGetPkiSubscriber({ + subscriberName, + projectId + }); + const { mutateAsync: issuePkiSubscriberCert, isPending: isIssuingCert } = + useIssuePkiSubscriberCert(); + + const onIssuePkiSubscriberCert = async () => { + try { + const response = await issuePkiSubscriberCert({ subscriberName, projectId }); + + setCertificateDetails({ + serialNumber: response.serialNumber, + certificate: response.certificate, + certificateChain: response.certificateChain, + privateKey: response.privateKey + }); + + setIsModalOpen(true); + + createNotification({ + text: "Successfully issued certificate", + type: "success" + }); + } catch (err) { + console.error(err); + createNotification({ + text: "Failed to issue certificate", + type: "error" + }); + } + }; + + const canIssuePkiSubscriberCert = permission.can( + ProjectPermissionPkiSubscriberActions.IssueCert, + subject(ProjectPermissionSub.PkiSubscribers, { + name: pkiSubscriber?.name ?? "" + }) + ); + + return pkiSubscriber ? ( +
+
+

PKI Subscriber Details

+ + {(isAllowed) => { + return ( + + { + e.stopPropagation(); + handlePopUpOpen("pkiSubscriber", { + subscriberName: pkiSubscriber.name + }); + }} + > + + + + ); + }} + +
+
+
+

PKI Subscriber ID

+
+

{pkiSubscriber.id}

+
+ + { + navigator.clipboard.writeText(pkiSubscriber.id); + setCopyTextId("Copied"); + }} + > + + + +
+
+
+
+

Name

+

{pkiSubscriber.name}

+
+
+

Common Name

+

{pkiSubscriber.commonName}

+
+ {canIssuePkiSubscriberCert && ( + + )} +
+ + { + setIsModalOpen(isOpen); + if (!isOpen) { + setCertificateDetails(null); + } + }} + > + + {certificateDetails && ( + + )} + + +
+ ) : ( +
+ ); +}; diff --git a/frontend/src/pages/cert-manager/PkiSubscriberDetailsByIDPage/components/index.tsx b/frontend/src/pages/cert-manager/PkiSubscriberDetailsByIDPage/components/index.tsx new file mode 100644 index 000000000..4a671fdcd --- /dev/null +++ b/frontend/src/pages/cert-manager/PkiSubscriberDetailsByIDPage/components/index.tsx @@ -0,0 +1,2 @@ +export { PkiSubscriberCertificatesSection } from "./PkiSubscriberCertificatesSection"; +export { PkiSubscriberDetailsSection } from "./PkiSubscriberDetailsSection"; diff --git a/frontend/src/pages/cert-manager/PkiSubscriberDetailsByIDPage/route.tsx b/frontend/src/pages/cert-manager/PkiSubscriberDetailsByIDPage/route.tsx index 65a8fdb54..f10ba23b6 100644 --- a/frontend/src/pages/cert-manager/PkiSubscriberDetailsByIDPage/route.tsx +++ b/frontend/src/pages/cert-manager/PkiSubscriberDetailsByIDPage/route.tsx @@ -1,16 +1,9 @@ import { createFileRoute } from "@tanstack/react-router"; -function RouteComponent() { - return ( -
- Hello - "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers/$subscriberId"! -
- ); -} +import { PkiSubscriberDetailsByIDPage } from "./PkiSubscriberDetailsByIDPage"; export const Route = createFileRoute( - "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers/$subscriberId" + "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers/$subscriberName" )({ - component: RouteComponent + component: PkiSubscriberDetailsByIDPage }); diff --git a/frontend/src/pages/cert-manager/PkiSubscribersPage/PkiSubscribersPage.tsx b/frontend/src/pages/cert-manager/PkiSubscribersPage/PkiSubscribersPage.tsx index 6d9c7a4e8..e1ff2cf19 100644 --- a/frontend/src/pages/cert-manager/PkiSubscribersPage/PkiSubscribersPage.tsx +++ b/frontend/src/pages/cert-manager/PkiSubscribersPage/PkiSubscribersPage.tsx @@ -15,7 +15,10 @@ export const PkiSubscribersPage = () => {
- +
diff --git a/frontend/src/pages/cert-manager/PkiSubscribersPage/components/PkiSubscriberModal.tsx b/frontend/src/pages/cert-manager/PkiSubscribersPage/components/PkiSubscriberModal.tsx index e48bff1cc..e8d897b2d 100644 --- a/frontend/src/pages/cert-manager/PkiSubscribersPage/components/PkiSubscriberModal.tsx +++ b/frontend/src/pages/cert-manager/PkiSubscribersPage/components/PkiSubscriberModal.tsx @@ -22,7 +22,7 @@ import { useWorkspace } from "@app/context"; import { CaStatus, useCreatePkiSubscriber, - useGetPkiSubscriberById, + useGetPkiSubscriber, useListWorkspaceCas, useListWorkspacePkiSubscribers, useUpdatePkiSubscriber @@ -72,16 +72,18 @@ export type FormData = z.infer; export const PkiSubscriberModal = ({ popUp, handlePopUpToggle }: Props) => { const { currentWorkspace } = useWorkspace(); - const projectId = currentWorkspace?.id || ""; + 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 { data: pkiSubscriber } = useGetPkiSubscriber({ + subscriberName: + (popUp?.pkiSubscriber?.data as { subscriberName: string })?.subscriberName || "", + projectId + }); const { mutateAsync: createMutateAsync } = useCreatePkiSubscriber(); const { mutateAsync: updateMutateAsync } = useUpdatePkiSubscriber(); @@ -200,7 +202,8 @@ export const PkiSubscriberModal = ({ popUp, handlePopUpToggle }: Props) => { if (pkiSubscriber) { await updateMutateAsync({ - subscriberId: pkiSubscriber.id, + subscriberName: pkiSubscriber.name, + projectId, name, caId, commonName, @@ -258,7 +261,7 @@ export const PkiSubscriberModal = ({ popUp, handlePopUpToggle }: Props) => { name="name" render={({ field, fieldState: { error } }) => ( { + const { currentWorkspace } = useWorkspace(); + const projectId = currentWorkspace.id; const { mutateAsync: deletePkiSubscriber } = useDeletePkiSubscriber(); + const { mutateAsync: updatePkiSubscriber } = useUpdatePkiSubscriber(); const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([ "pkiSubscriber", + "pkiSubscriberStatus", // enable / disable "deletePkiSubscriber" ] as const); - const onRemovePkiSubscriberSubmit = async (subscriberId: string) => { + const onRemovePkiSubscriberSubmit = async (subscriberName: string) => { try { - const subscriber = await deletePkiSubscriber({ subscriberId }); + const subscriber = await deletePkiSubscriber({ subscriberName, projectId }); createNotification({ text: `Successfully deleted PKI subscriber: ${subscriber.name}`, @@ -38,6 +47,41 @@ export const PkiSubscriberSection = () => { } }; + const onUpdatePkiSubscriberStatus = async ({ + subscriberName, + status + }: { + subscriberName: string; + status: PkiSubscriberStatus; + }) => { + try { + if (!currentWorkspace?.slug) return; + + await updatePkiSubscriber({ subscriberName, projectId, status }); + + createNotification({ + text: `Successfully ${status === PkiSubscriberStatus.ACTIVE ? "enabled" : "disabled"} subscriber`, + type: "success" + }); + + handlePopUpClose("pkiSubscriberStatus"); + } catch (err) { + console.error(err); + createNotification({ + text: `Failed to ${status === PkiSubscriberStatus.ACTIVE ? "enabled" : "disabled"} subscriber`, + type: "error" + }); + } + }; + + const subscriberStatusData = popUp?.pkiSubscriberStatus?.data as { + status: PkiSubscriberStatus; + subscriberName: string; + }; + + const isEnabling = subscriberStatusData?.status === PkiSubscriberStatus.ACTIVE; + const subscriberName = subscriberStatusData?.subscriberName || ""; + return (
@@ -46,7 +90,7 @@ export const PkiSubscriberSection = () => { Documentation{" "} @@ -77,6 +121,20 @@ export const PkiSubscriberSection = () => {
+ handlePopUpToggle("pkiSubscriberStatus", isOpen)} + deleteKey="confirm" + buttonColorSchema={isEnabling ? "primary" : "danger"} + buttonText={isEnabling ? "Enable" : "Disable"} + onDeleteApproved={() => onUpdatePkiSubscriberStatus(subscriberStatusData)} + /> { deleteKey="confirm" onDeleteApproved={() => onRemovePkiSubscriberSubmit( - (popUp?.deletePkiSubscriber?.data as { subscriberId: string })?.subscriberId + (popUp?.deletePkiSubscriber?.data as { subscriberName: string })?.subscriberName ) } /> diff --git a/frontend/src/pages/cert-manager/PkiSubscribersPage/components/PkiSubscribersTable.tsx b/frontend/src/pages/cert-manager/PkiSubscribersPage/components/PkiSubscribersTable.tsx index a32b86b00..3ba473985 100644 --- a/frontend/src/pages/cert-manager/PkiSubscribersPage/components/PkiSubscribersTable.tsx +++ b/frontend/src/pages/cert-manager/PkiSubscribersPage/components/PkiSubscribersTable.tsx @@ -1,10 +1,17 @@ -import { faEllipsis, faPencil, faServer, faTrash } from "@fortawesome/free-solid-svg-icons"; +import { + faBan, + faEllipsis, + faPencil, + faTrash, + faUserShield +} from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useNavigate } from "@tanstack/react-router"; import { twMerge } from "tailwind-merge"; import { ProjectPermissionCan } from "@app/components/permissions"; import { + Badge, DropdownMenu, DropdownMenuContent, DropdownMenuItem, @@ -26,12 +33,17 @@ import { useWorkspace } from "@app/context"; import { useListWorkspacePkiSubscribers } from "@app/hooks/api"; +import { + getPkiSubscriberStatusBadgeVariant, + PkiSubscriberStatus, + pkiSubscriberStatusToNameMap +} from "@app/hooks/api/pkiSubscriber/constants"; import { ProjectType } from "@app/hooks/api/workspace/types"; import { UsePopUpState } from "@app/hooks/usePopUp"; type Props = { handlePopUpOpen: ( - popUpName: keyof UsePopUpState<["deletePkiSubscriber", "pkiSubscriber"]>, + popUpName: keyof UsePopUpState<["deletePkiSubscriber", "pkiSubscriber", "pkiSubscriberStatus"]>, data?: object ) => void; }; @@ -47,12 +59,13 @@ export const PkiSubscribersTable = ({ handlePopUpOpen }: Props) => {
NameStatus Common Name
{subscriber.name} + + {pkiSubscriberStatusToNameMap[subscriber.status]} + + {subscriber.commonName} @@ -96,7 +113,7 @@ export const PkiSubscribersTable = ({ handlePopUpOpen }: Props) => { onClick={(e) => { e.stopPropagation(); handlePopUpOpen("pkiSubscriber", { - subscriberId: subscriber.id + subscriberName: subscriber.name }); }} disabled={!isAllowed} @@ -106,6 +123,32 @@ export const PkiSubscribersTable = ({ handlePopUpOpen }: Props) => { )} + + {(isAllowed) => ( + { + e.stopPropagation(); + handlePopUpOpen("pkiSubscriberStatus", { + subscriberName: subscriber.name, + status: + subscriber.status === PkiSubscriberStatus.ACTIVE + ? PkiSubscriberStatus.DISABLED + : PkiSubscriberStatus.ACTIVE + }); + }} + disabled={!isAllowed} + icon={} + > + {`${subscriber.status === PkiSubscriberStatus.ACTIVE ? "Disable" : "Enable"} Subscriber`} + + )} + { onClick={(e) => { e.stopPropagation(); handlePopUpOpen("deletePkiSubscriber", { - subscriberId: subscriber.id + subscriberName: subscriber.name }); }} disabled={!isAllowed} @@ -137,7 +180,7 @@ export const PkiSubscribersTable = ({ handlePopUpOpen }: Props) => {
{!isPending && data?.length === 0 && ( - + )}
diff --git a/frontend/src/pages/cert-manager/PkiSubscribersPage/route.tsx b/frontend/src/pages/cert-manager/PkiSubscribersPage/route.tsx index 4c0928674..d8d9fbadd 100644 --- a/frontend/src/pages/cert-manager/PkiSubscribersPage/route.tsx +++ b/frontend/src/pages/cert-manager/PkiSubscribersPage/route.tsx @@ -1,18 +1,9 @@ -import { createFileRoute } from '@tanstack/react-router' +import { createFileRoute } from "@tanstack/react-router"; -import { PkiSubscribersPage } from './PkiSubscribersPage' +import { PkiSubscribersPage } from "./PkiSubscribersPage"; export const Route = createFileRoute( - '/_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/subscribers/" )({ - component: PkiSubscribersPage, -}) - -function RouteComponent() { - return ( -
- Hello - "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers"! -
- ) -} + component: PkiSubscribersPage +}); diff --git a/frontend/src/pages/project/RoleDetailsBySlugPage/components/PkiSubscriberPermissionConditions.tsx b/frontend/src/pages/project/RoleDetailsBySlugPage/components/PkiSubscriberPermissionConditions.tsx new file mode 100644 index 000000000..58840fe2f --- /dev/null +++ b/frontend/src/pages/project/RoleDetailsBySlugPage/components/PkiSubscriberPermissionConditions.tsx @@ -0,0 +1,173 @@ +import { Controller, useFieldArray, useFormContext } from "react-hook-form"; +import { faInfoCircle, faPlus, faTrash, faWarning } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; + +import { + Button, + FormControl, + IconButton, + Input, + Select, + SelectItem, + Tooltip +} from "@app/components/v2"; +import { + PermissionConditionOperators, + ProjectPermissionSub +} from "@app/context/ProjectPermissionContext/types"; + +import { getConditionOperatorHelperInfo } from "./PermissionConditionHelpers"; +import { TFormSchema } from "./ProjectRoleModifySection.utils"; + +type Props = { + position?: number; + isDisabled?: boolean; +}; + +export const PkiSubscriberPermissionConditions = ({ position = 0, isDisabled }: Props) => { + const { + control, + watch, + formState: { errors } + } = useFormContext(); + + const permissionSubject = ProjectPermissionSub.PkiSubscribers; + const items = useFieldArray({ + control, + name: `permissions.${permissionSubject}.${position}.conditions` + }); + + return ( +
+

Conditions

+

+ Conditions determine when a policy will be applied (always if no conditions are present). +

+

+ All conditions must evaluate to true for the policy to take effect. +

+
+ {items.fields.map((el, index) => { + const condition = + (watch(`permissions.${permissionSubject}.${position}.conditions.${index}`) as { + lhs: string; + rhs: string; + operator: string; + }) || {}; + + return ( +
+
+ ( + + + + )} + /> +
+
+ ( + + + + )} + /> + + + +
+
+ ( + + + + )} + /> +
+
+ items.remove(index)} + > + + +
+
+ ); + })} +
+ {errors?.permissions?.[permissionSubject]?.[position]?.conditions?.message && ( +
+ + {errors?.permissions?.[permissionSubject]?.[position]?.conditions?.message} +
+ )} +
+ +
+
+ ); +}; diff --git a/frontend/src/pages/project/RoleDetailsBySlugPage/components/ProjectRoleModifySection.utils.tsx b/frontend/src/pages/project/RoleDetailsBySlugPage/components/ProjectRoleModifySection.utils.tsx index cd7b8cec3..8c0e19ec4 100644 --- a/frontend/src/pages/project/RoleDetailsBySlugPage/components/ProjectRoleModifySection.utils.tsx +++ b/frontend/src/pages/project/RoleDetailsBySlugPage/components/ProjectRoleModifySection.utils.tsx @@ -17,6 +17,7 @@ import { ProjectPermissionIdentityActions, ProjectPermissionKmipActions, ProjectPermissionMemberActions, + ProjectPermissionPkiSubscriberActions, ProjectPermissionSecretActions, ProjectPermissionSecretRotationActions, ProjectPermissionSecretSyncActions, @@ -130,6 +131,15 @@ const SshHostPolicyActionSchema = z.object({ [ProjectPermissionSshHostActions.IssueHostCert]: z.boolean().optional() }); +const PkiSubscriberPolicyActionSchema = z.object({ + [ProjectPermissionPkiSubscriberActions.Read]: z.boolean().optional(), + [ProjectPermissionPkiSubscriberActions.Create]: z.boolean().optional(), + [ProjectPermissionPkiSubscriberActions.Edit]: z.boolean().optional(), + [ProjectPermissionPkiSubscriberActions.Delete]: z.boolean().optional(), + [ProjectPermissionPkiSubscriberActions.IssueCert]: z.boolean().optional(), + [ProjectPermissionPkiSubscriberActions.ListCerts]: z.boolean().optional() +}); + const SecretRollbackPolicyActionSchema = z.object({ read: z.boolean().optional(), create: z.boolean().optional() @@ -229,6 +239,12 @@ export const projectRoleFormSchema = z.object({ [ProjectPermissionSub.IpAllowList]: GeneralPolicyActionSchema.array().default([]), [ProjectPermissionSub.CertificateAuthorities]: GeneralPolicyActionSchema.array().default([]), [ProjectPermissionSub.Certificates]: CertificatePolicyActionSchema.array().default([]), + [ProjectPermissionSub.PkiSubscribers]: PkiSubscriberPolicyActionSchema.extend({ + inverted: z.boolean().optional(), + conditions: ConditionSchema + }) + .array() + .default([]), [ProjectPermissionSub.PkiAlerts]: GeneralPolicyActionSchema.array().default([]), [ProjectPermissionSub.PkiCollections]: GeneralPolicyActionSchema.array().default([]), [ProjectPermissionSub.CertificateTemplates]: GeneralPolicyActionSchema.array().default([]), @@ -270,6 +286,7 @@ type TConditionalFields = | ProjectPermissionSub.SecretFolders | ProjectPermissionSub.SecretImports | ProjectPermissionSub.DynamicSecrets + | ProjectPermissionSub.PkiSubscribers | ProjectPermissionSub.SshHosts | ProjectPermissionSub.SecretRotation | ProjectPermissionSub.Identity; @@ -283,7 +300,8 @@ export const isConditionalSubjects = ( subject === ProjectPermissionSub.SecretFolders || subject === ProjectPermissionSub.Identity || subject === ProjectPermissionSub.SshHosts || - subject === ProjectPermissionSub.SecretRotation; + subject === ProjectPermissionSub.SecretRotation || + subject === ProjectPermissionSub.PkiSubscribers; const convertCaslConditionToFormOperator = (caslConditions: TPermissionCondition) => { const formConditions: z.infer = []; @@ -714,6 +732,33 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => { inverted }); } + + if (subject === ProjectPermissionSub.PkiSubscribers) { + if (!formVal[subject]) formVal[subject] = []; + + formVal[subject]!.push({ + [ProjectPermissionPkiSubscriberActions.Edit]: action.includes( + ProjectPermissionPkiSubscriberActions.Edit + ), + [ProjectPermissionPkiSubscriberActions.Delete]: action.includes( + ProjectPermissionPkiSubscriberActions.Delete + ), + [ProjectPermissionPkiSubscriberActions.Create]: action.includes( + ProjectPermissionPkiSubscriberActions.Create + ), + [ProjectPermissionPkiSubscriberActions.Read]: action.includes( + ProjectPermissionPkiSubscriberActions.Read + ), + [ProjectPermissionPkiSubscriberActions.IssueCert]: action.includes( + ProjectPermissionPkiSubscriberActions.IssueCert + ), + [ProjectPermissionPkiSubscriberActions.ListCerts]: action.includes( + ProjectPermissionPkiSubscriberActions.ListCerts + ), + conditions: conditions ? convertCaslConditionToFormOperator(conditions) : [], + inverted + }); + } }); return formVal; @@ -1103,6 +1148,17 @@ export const PROJECT_PERMISSION_OBJECT: TProjectPermissionObject = { { label: "Remove", value: "delete" } ] }, + [ProjectPermissionSub.PkiSubscribers]: { + title: "PKI Subscribers", + actions: [ + { label: "Read", value: ProjectPermissionPkiSubscriberActions.Read }, + { label: "Create", value: ProjectPermissionPkiSubscriberActions.Create }, + { label: "Modify", value: ProjectPermissionPkiSubscriberActions.Edit }, + { label: "Remove", value: ProjectPermissionPkiSubscriberActions.Delete }, + { label: "Issue Certificate", value: ProjectPermissionPkiSubscriberActions.IssueCert }, + { label: "List Certificates", value: ProjectPermissionPkiSubscriberActions.ListCerts } + ] + }, [ProjectPermissionSub.PkiCollections]: { title: "PKI Collections", actions: [ diff --git a/frontend/src/pages/project/RoleDetailsBySlugPage/components/RolePermissionsSection.tsx b/frontend/src/pages/project/RoleDetailsBySlugPage/components/RolePermissionsSection.tsx index 2f46bd065..42223e884 100644 --- a/frontend/src/pages/project/RoleDetailsBySlugPage/components/RolePermissionsSection.tsx +++ b/frontend/src/pages/project/RoleDetailsBySlugPage/components/RolePermissionsSection.tsx @@ -26,6 +26,7 @@ import { GeneralPermissionConditions } from "./GeneralPermissionConditions"; import { GeneralPermissionPolicies } from "./GeneralPermissionPolicies"; import { IdentityManagementPermissionConditions } from "./IdentityManagementPermissionConditions"; import { PermissionEmptyState } from "./PermissionEmptyState"; +import { PkiSubscriberPermissionConditions } from "./PkiSubscriberPermissionConditions"; import { formRolePermission2API, isConditionalSubjects, @@ -61,6 +62,10 @@ export const renderConditionalComponents = ( return ; } + if (subject === ProjectPermissionSub.PkiSubscribers) { + return ; + } + return ; } diff --git a/frontend/src/pages/ssh/SshHostsPage/components/SshHostsTable.tsx b/frontend/src/pages/ssh/SshHostsPage/components/SshHostsTable.tsx index 491307d21..cb85e6638 100644 --- a/frontend/src/pages/ssh/SshHostsPage/components/SshHostsTable.tsx +++ b/frontend/src/pages/ssh/SshHostsPage/components/SshHostsTable.tsx @@ -27,7 +27,7 @@ import { Tooltip, Tr } from "@app/components/v2"; -import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context"; +import { ProjectPermissionSshHostActions, ProjectPermissionSub, useWorkspace } from "@app/context"; import { fetchSshHostUserCaPublicKey, useListWorkspaceSshHosts } from "@app/hooks/api"; import { LoginMappingSource } from "@app/hooks/api/sshHost/types"; import { UsePopUpState } from "@app/hooks/usePopUp"; @@ -180,7 +180,7 @@ export const SshHostsTable = ({ handlePopUpOpen }: Props) => { Download User CA Public Key {(isAllowed) => ( @@ -202,7 +202,7 @@ export const SshHostsTable = ({ handlePopUpOpen }: Props) => { )} {(isAllowed) => ( diff --git a/frontend/src/routeTree.gen.ts b/frontend/src/routeTree.gen.ts index d78c30024..4f850c944 100644 --- a/frontend/src/routeTree.gen.ts +++ b/frontend/src/routeTree.gen.ts @@ -91,7 +91,6 @@ import { Route as organizationSettingsPageOauthCallbackPageRouteImport } from '. import { Route as kmsSettingsPageRouteImport } from './pages/kms/SettingsPage/route' import { Route as kmsOverviewPageRouteImport } from './pages/kms/OverviewPage/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 certManagerCertificatesPageRouteImport } from './pages/cert-manager/CertificatesPage/route' import { Route as certManagerCertificateAuthoritiesPageRouteImport } from './pages/cert-manager/CertificateAuthoritiesPage/route' @@ -118,6 +117,7 @@ import { Route as organizationAppConnectionsOauthCallbackPageRouteImport } from import { Route as certManagerPkiSubscriberDetailsByIDPageRouteImport } from './pages/cert-manager/PkiSubscriberDetailsByIDPage/route' import { Route as certManagerCertAuthDetailsByIDPageRouteImport } from './pages/cert-manager/CertAuthDetailsByIDPage/route' import { Route as secretManagerIntegrationsListPageRouteImport } from './pages/secret-manager/IntegrationsListPage/route' +import { Route as certManagerPkiSubscribersPageRouteImport } from './pages/cert-manager/PkiSubscribersPage/route' import { Route as secretManagerIntegrationsWindmillConfigurePageRouteImport } from './pages/secret-manager/integrations/WindmillConfigurePage/route' import { Route as secretManagerIntegrationsWindmillAuthorizePageRouteImport } from './pages/secret-manager/integrations/WindmillAuthorizePage/route' import { Route as secretManagerIntegrationsVercelConfigurePageRouteImport } from './pages/secret-manager/integrations/VercelConfigurePage/route' @@ -248,6 +248,10 @@ const AuthenticateInjectOrgDetailsOrgLayoutSecretManagerProjectIdSecretManagerLa createFileRoute( '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations', )() +const AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutSubscribersImport = + createFileRoute( + '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers', + )() // Create/Update Routes @@ -814,6 +818,15 @@ const secretManagerIntegrationsRouteAzureAppConfigurationsOauthRedirectRoute = } as any, ) +const AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutSubscribersRoute = + AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutSubscribersImport.update( + { + id: '/subscribers', + path: '/subscribers', + getParentRoute: () => certManagerLayoutRoute, + } as any, + ) + const projectAccessControlPageRouteCertManagerRoute = projectAccessControlPageRouteCertManagerImport.update({ id: '/access-management', @@ -906,13 +919,6 @@ const kmsKmipPageRouteRoute = kmsKmipPageRouteImport.update({ getParentRoute: () => kmsLayoutRoute, } as any) -const certManagerPkiSubscribersPageRouteRoute = - certManagerPkiSubscribersPageRouteImport.update({ - id: '/subscribers', - path: '/subscribers', - getParentRoute: () => certManagerLayoutRoute, - } as any) - const certManagerSettingsPageRouteRoute = certManagerSettingsPageRouteImport.update({ id: '/settings', @@ -1078,9 +1084,10 @@ const organizationAppConnectionsOauthCallbackPageRouteRoute = const certManagerPkiSubscriberDetailsByIDPageRouteRoute = certManagerPkiSubscriberDetailsByIDPageRouteImport.update({ - id: '/$subscriberId', - path: '/$subscriberId', - getParentRoute: () => certManagerPkiSubscribersPageRouteRoute, + id: '/$subscriberName', + path: '/$subscriberName', + getParentRoute: () => + AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutSubscribersRoute, } as any) const certManagerCertAuthDetailsByIDPageRouteRoute = @@ -1098,6 +1105,14 @@ const secretManagerIntegrationsListPageRouteRoute = AuthenticateInjectOrgDetailsOrgLayoutSecretManagerProjectIdSecretManagerLayoutIntegrationsRoute, } as any) +const certManagerPkiSubscribersPageRouteRoute = + certManagerPkiSubscribersPageRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => + AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutSubscribersRoute, + } as any) + const secretManagerIntegrationsWindmillConfigurePageRouteRoute = secretManagerIntegrationsWindmillConfigurePageRouteImport.update({ id: '/windmill/create', @@ -2200,13 +2215,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof certManagerSettingsPageRouteImport 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': { id: '/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout/kmip' path: '/kmip' @@ -2305,6 +2313,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof projectAccessControlPageRouteCertManagerImport 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 AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutSubscribersImport + parentRoute: typeof certManagerLayoutImport + } '/_authenticate/_inject-org-details/_org-layout/integrations/azure-app-configuration/oauth2/callback': { id: '/_authenticate/_inject-org-details/_org-layout/integrations/azure-app-configuration/oauth2/callback' path: '/azure-app-configuration/oauth2/callback' @@ -2396,6 +2411,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof projectAccessControlPageRouteSshImport parentRoute: typeof sshLayoutImport } + '/_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: '/' + fullPath: '/cert-manager/$projectId/subscribers/' + preLoaderRoute: typeof certManagerPkiSubscribersPageRouteImport + parentRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutSubscribersImport + } '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/': { id: '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/' path: '/' @@ -2410,12 +2432,12 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof certManagerCertAuthDetailsByIDPageRouteImport parentRoute: typeof certManagerLayoutImport } - '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers/$subscriberId': { - id: '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers/$subscriberId' - path: '/$subscriberId' - fullPath: '/cert-manager/$projectId/subscribers/$subscriberId' + '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers/$subscriberName': { + id: '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers/$subscriberName' + path: '/$subscriberName' + fullPath: '/cert-manager/$projectId/subscribers/$subscriberName' preLoaderRoute: typeof certManagerPkiSubscriberDetailsByIDPageRouteImport - parentRoute: typeof certManagerPkiSubscribersPageRouteImport + parentRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutSubscribersImport } '/_authenticate/_inject-org-details/_org-layout/organization/app-connections/$appConnection/oauth/callback': { id: '/_authenticate/_inject-org-details/_org-layout/organization/app-connections/$appConnection/oauth/callback' @@ -3246,19 +3268,22 @@ const AuthenticateInjectOrgDetailsOrgLayoutOrganizationRouteWithChildren = AuthenticateInjectOrgDetailsOrgLayoutOrganizationRouteChildren, ) -interface certManagerPkiSubscribersPageRouteRouteChildren { +interface AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutSubscribersRouteChildren { + certManagerPkiSubscribersPageRouteRoute: typeof certManagerPkiSubscribersPageRouteRoute certManagerPkiSubscriberDetailsByIDPageRouteRoute: typeof certManagerPkiSubscriberDetailsByIDPageRouteRoute } -const certManagerPkiSubscribersPageRouteRouteChildren: certManagerPkiSubscribersPageRouteRouteChildren = +const AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutSubscribersRouteChildren: AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutSubscribersRouteChildren = { + certManagerPkiSubscribersPageRouteRoute: + certManagerPkiSubscribersPageRouteRoute, certManagerPkiSubscriberDetailsByIDPageRouteRoute: certManagerPkiSubscriberDetailsByIDPageRouteRoute, } -const certManagerPkiSubscribersPageRouteRouteWithChildren = - certManagerPkiSubscribersPageRouteRoute._addFileChildren( - certManagerPkiSubscribersPageRouteRouteChildren, +const AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutSubscribersRouteWithChildren = + AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutSubscribersRoute._addFileChildren( + AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutSubscribersRouteChildren, ) interface certManagerLayoutRouteChildren { @@ -3266,8 +3291,8 @@ interface certManagerLayoutRouteChildren { certManagerCertificateAuthoritiesPageRouteRoute: typeof certManagerCertificateAuthoritiesPageRouteRoute certManagerCertificatesPageRouteRoute: typeof certManagerCertificatesPageRouteRoute certManagerSettingsPageRouteRoute: typeof certManagerSettingsPageRouteRoute - certManagerPkiSubscribersPageRouteRoute: typeof certManagerPkiSubscribersPageRouteRouteWithChildren projectAccessControlPageRouteCertManagerRoute: typeof projectAccessControlPageRouteCertManagerRoute + AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutSubscribersRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutSubscribersRouteWithChildren certManagerCertAuthDetailsByIDPageRouteRoute: typeof certManagerCertAuthDetailsByIDPageRouteRoute projectIdentityDetailsByIDPageRouteCertManagerRoute: typeof projectIdentityDetailsByIDPageRouteCertManagerRoute projectMemberDetailsByIDPageRouteCertManagerRoute: typeof projectMemberDetailsByIDPageRouteCertManagerRoute @@ -3281,10 +3306,10 @@ const certManagerLayoutRouteChildren: certManagerLayoutRouteChildren = { certManagerCertificateAuthoritiesPageRouteRoute, certManagerCertificatesPageRouteRoute: certManagerCertificatesPageRouteRoute, certManagerSettingsPageRouteRoute: certManagerSettingsPageRouteRoute, - certManagerPkiSubscribersPageRouteRoute: - certManagerPkiSubscribersPageRouteRouteWithChildren, projectAccessControlPageRouteCertManagerRoute: projectAccessControlPageRouteCertManagerRoute, + AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutSubscribersRoute: + AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutSubscribersRouteWithChildren, certManagerCertAuthDetailsByIDPageRouteRoute: certManagerCertAuthDetailsByIDPageRouteRoute, projectIdentityDetailsByIDPageRouteCertManagerRoute: @@ -3953,7 +3978,6 @@ export interface FileRoutesByFullPath { '/cert-manager/$projectId/certificate-authorities': typeof certManagerCertificateAuthoritiesPageRouteRoute '/cert-manager/$projectId/overview': typeof certManagerCertificatesPageRouteRoute '/cert-manager/$projectId/settings': typeof certManagerSettingsPageRouteRoute - '/cert-manager/$projectId/subscribers': typeof certManagerPkiSubscribersPageRouteRouteWithChildren '/kms/$projectId/kmip': typeof kmsKmipPageRouteRoute '/kms/$projectId/overview': typeof kmsOverviewPageRouteRoute '/kms/$projectId/settings': typeof kmsSettingsPageRouteRoute @@ -3968,6 +3992,7 @@ export interface FileRoutesByFullPath { '/ssh/$projectId/overview': typeof sshSshHostsPageRouteRoute '/ssh/$projectId/settings': typeof sshSettingsPageRouteRoute '/cert-manager/$projectId/access-management': typeof projectAccessControlPageRouteCertManagerRoute + '/cert-manager/$projectId/subscribers': typeof AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutSubscribersRouteWithChildren '/integrations/azure-app-configuration/oauth2/callback': typeof secretManagerIntegrationsRouteAzureAppConfigurationsOauthRedirectRoute '/integrations/azure-key-vault/oauth2/callback': typeof secretManagerIntegrationsRouteAzureKeyVaultOauthRedirectRoute '/integrations/bitbucket/oauth2/callback': typeof secretManagerIntegrationsRouteBitbucketOauthRedirectRoute @@ -3981,9 +4006,10 @@ export interface FileRoutesByFullPath { '/secret-manager/$projectId/access-management': typeof projectAccessControlPageRouteSecretManagerRoute '/secret-manager/$projectId/integrations': typeof AuthenticateInjectOrgDetailsOrgLayoutSecretManagerProjectIdSecretManagerLayoutIntegrationsRouteWithChildren '/ssh/$projectId/access-management': typeof projectAccessControlPageRouteSshRoute + '/cert-manager/$projectId/subscribers/': typeof certManagerPkiSubscribersPageRouteRoute '/secret-manager/$projectId/integrations/': typeof secretManagerIntegrationsListPageRouteRoute '/cert-manager/$projectId/ca/$caId': typeof certManagerCertAuthDetailsByIDPageRouteRoute - '/cert-manager/$projectId/subscribers/$subscriberId': typeof certManagerPkiSubscriberDetailsByIDPageRouteRoute + '/cert-manager/$projectId/subscribers/$subscriberName': typeof certManagerPkiSubscriberDetailsByIDPageRouteRoute '/organization/app-connections/$appConnection/oauth/callback': typeof organizationAppConnectionsOauthCallbackPageRouteRoute '/secret-manager/$projectId/integrations/$integrationId': typeof secretManagerIntegrationsDetailsByIDPageRouteRoute '/secret-manager/$projectId/integrations/select-integration-auth': typeof secretManagerIntegrationsSelectIntegrationAuthPageRouteRoute @@ -4134,7 +4160,6 @@ export interface FileRoutesByTo { '/cert-manager/$projectId/certificate-authorities': typeof certManagerCertificateAuthoritiesPageRouteRoute '/cert-manager/$projectId/overview': typeof certManagerCertificatesPageRouteRoute '/cert-manager/$projectId/settings': typeof certManagerSettingsPageRouteRoute - '/cert-manager/$projectId/subscribers': typeof certManagerPkiSubscribersPageRouteRouteWithChildren '/kms/$projectId/kmip': typeof kmsKmipPageRouteRoute '/kms/$projectId/overview': typeof kmsOverviewPageRouteRoute '/kms/$projectId/settings': typeof kmsSettingsPageRouteRoute @@ -4161,9 +4186,10 @@ export interface FileRoutesByTo { '/kms/$projectId/access-management': typeof projectAccessControlPageRouteKmsRoute '/secret-manager/$projectId/access-management': typeof projectAccessControlPageRouteSecretManagerRoute '/ssh/$projectId/access-management': typeof projectAccessControlPageRouteSshRoute + '/cert-manager/$projectId/subscribers': typeof certManagerPkiSubscribersPageRouteRoute '/secret-manager/$projectId/integrations': typeof secretManagerIntegrationsListPageRouteRoute '/cert-manager/$projectId/ca/$caId': typeof certManagerCertAuthDetailsByIDPageRouteRoute - '/cert-manager/$projectId/subscribers/$subscriberId': typeof certManagerPkiSubscriberDetailsByIDPageRouteRoute + '/cert-manager/$projectId/subscribers/$subscriberName': typeof certManagerPkiSubscriberDetailsByIDPageRouteRoute '/organization/app-connections/$appConnection/oauth/callback': typeof organizationAppConnectionsOauthCallbackPageRouteRoute '/secret-manager/$projectId/integrations/$integrationId': typeof secretManagerIntegrationsDetailsByIDPageRouteRoute '/secret-manager/$projectId/integrations/select-integration-auth': typeof secretManagerIntegrationsSelectIntegrationAuthPageRouteRoute @@ -4332,7 +4358,6 @@ 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/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/subscribers': typeof certManagerPkiSubscribersPageRouteRouteWithChildren '/_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/settings': typeof kmsSettingsPageRouteRoute @@ -4347,6 +4372,7 @@ export interface FileRoutesById { '/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/overview': typeof sshSshHostsPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/settings': typeof sshSettingsPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/access-management': typeof projectAccessControlPageRouteCertManagerRoute + '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers': typeof AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutSubscribersRouteWithChildren '/_authenticate/_inject-org-details/_org-layout/integrations/azure-app-configuration/oauth2/callback': typeof secretManagerIntegrationsRouteAzureAppConfigurationsOauthRedirectRoute '/_authenticate/_inject-org-details/_org-layout/integrations/azure-key-vault/oauth2/callback': typeof secretManagerIntegrationsRouteAzureKeyVaultOauthRedirectRoute '/_authenticate/_inject-org-details/_org-layout/integrations/bitbucket/oauth2/callback': typeof secretManagerIntegrationsRouteBitbucketOauthRedirectRoute @@ -4360,9 +4386,10 @@ export interface FileRoutesById { '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/access-management': typeof projectAccessControlPageRouteSecretManagerRoute '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations': typeof AuthenticateInjectOrgDetailsOrgLayoutSecretManagerProjectIdSecretManagerLayoutIntegrationsRouteWithChildren '/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/access-management': typeof projectAccessControlPageRouteSshRoute + '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers/': typeof certManagerPkiSubscribersPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/': typeof secretManagerIntegrationsListPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/ca/$caId': typeof certManagerCertAuthDetailsByIDPageRouteRoute - '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers/$subscriberId': typeof certManagerPkiSubscriberDetailsByIDPageRouteRoute + '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers/$subscriberName': typeof certManagerPkiSubscriberDetailsByIDPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/organization/app-connections/$appConnection/oauth/callback': typeof organizationAppConnectionsOauthCallbackPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/$integrationId': typeof secretManagerIntegrationsDetailsByIDPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/select-integration-auth': typeof secretManagerIntegrationsSelectIntegrationAuthPageRouteRoute @@ -4523,7 +4550,6 @@ export interface FileRouteTypes { | '/cert-manager/$projectId/certificate-authorities' | '/cert-manager/$projectId/overview' | '/cert-manager/$projectId/settings' - | '/cert-manager/$projectId/subscribers' | '/kms/$projectId/kmip' | '/kms/$projectId/overview' | '/kms/$projectId/settings' @@ -4538,6 +4564,7 @@ export interface FileRouteTypes { | '/ssh/$projectId/overview' | '/ssh/$projectId/settings' | '/cert-manager/$projectId/access-management' + | '/cert-manager/$projectId/subscribers' | '/integrations/azure-app-configuration/oauth2/callback' | '/integrations/azure-key-vault/oauth2/callback' | '/integrations/bitbucket/oauth2/callback' @@ -4551,9 +4578,10 @@ export interface FileRouteTypes { | '/secret-manager/$projectId/access-management' | '/secret-manager/$projectId/integrations' | '/ssh/$projectId/access-management' + | '/cert-manager/$projectId/subscribers/' | '/secret-manager/$projectId/integrations/' | '/cert-manager/$projectId/ca/$caId' - | '/cert-manager/$projectId/subscribers/$subscriberId' + | '/cert-manager/$projectId/subscribers/$subscriberName' | '/organization/app-connections/$appConnection/oauth/callback' | '/secret-manager/$projectId/integrations/$integrationId' | '/secret-manager/$projectId/integrations/select-integration-auth' @@ -4703,7 +4731,6 @@ export interface FileRouteTypes { | '/cert-manager/$projectId/certificate-authorities' | '/cert-manager/$projectId/overview' | '/cert-manager/$projectId/settings' - | '/cert-manager/$projectId/subscribers' | '/kms/$projectId/kmip' | '/kms/$projectId/overview' | '/kms/$projectId/settings' @@ -4730,9 +4757,10 @@ export interface FileRouteTypes { | '/kms/$projectId/access-management' | '/secret-manager/$projectId/access-management' | '/ssh/$projectId/access-management' + | '/cert-manager/$projectId/subscribers' | '/secret-manager/$projectId/integrations' | '/cert-manager/$projectId/ca/$caId' - | '/cert-manager/$projectId/subscribers/$subscriberId' + | '/cert-manager/$projectId/subscribers/$subscriberName' | '/organization/app-connections/$appConnection/oauth/callback' | '/secret-manager/$projectId/integrations/$integrationId' | '/secret-manager/$projectId/integrations/select-integration-auth' @@ -4899,7 +4927,6 @@ 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/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/subscribers' | '/_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/settings' @@ -4914,6 +4941,7 @@ export interface FileRouteTypes { | '/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/overview' | '/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/settings' | '/_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/subscribers' | '/_authenticate/_inject-org-details/_org-layout/integrations/azure-app-configuration/oauth2/callback' | '/_authenticate/_inject-org-details/_org-layout/integrations/azure-key-vault/oauth2/callback' | '/_authenticate/_inject-org-details/_org-layout/integrations/bitbucket/oauth2/callback' @@ -4927,9 +4955,10 @@ export interface FileRouteTypes { | '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/access-management' | '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations' | '/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/access-management' + | '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers/' | '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/' | '/_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/subscribers/$subscriberId' + | '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers/$subscriberName' | '/_authenticate/_inject-org-details/_org-layout/organization/app-connections/$appConnection/oauth/callback' | '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/$integrationId' | '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/select-integration-auth' @@ -5428,8 +5457,8 @@ 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/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/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/subscribers", "/_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/members/$membershipId", @@ -5499,13 +5528,6 @@ export const routeTree = rootRoute "filePath": "cert-manager/SettingsPage/route.tsx", "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", - "children": [ - "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers/$subscriberId" - ] - }, "/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout/kmip": { "filePath": "kms/KmipPage/route.tsx", "parent": "/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout" @@ -5562,6 +5584,14 @@ export const routeTree = rootRoute "filePath": "project/AccessControlPage/route-cert-manager.tsx", "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": "", + "parent": "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout", + "children": [ + "/_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/subscribers/$subscriberName" + ] + }, "/_authenticate/_inject-org-details/_org-layout/integrations/azure-app-configuration/oauth2/callback": { "filePath": "secret-manager/integrations/route-azure-app-configurations-oauth-redirect.tsx", "parent": "/_authenticate/_inject-org-details/_org-layout/integrations" @@ -5694,6 +5724,10 @@ export const routeTree = rootRoute "filePath": "project/AccessControlPage/route-ssh.tsx", "parent": "/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-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/subscribers" + }, "/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/": { "filePath": "secret-manager/IntegrationsListPage/route.tsx", "parent": "/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations" @@ -5702,7 +5736,7 @@ export const routeTree = rootRoute "filePath": "cert-manager/CertAuthDetailsByIDPage/route.tsx", "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/$subscriberId": { + "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers/$subscriberName": { "filePath": "cert-manager/PkiSubscriberDetailsByIDPage/route.tsx", "parent": "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers" }, diff --git a/frontend/src/routes.ts b/frontend/src/routes.ts index 7e916b012..37234310d 100644 --- a/frontend/src/routes.ts +++ b/frontend/src/routes.ts @@ -286,9 +286,8 @@ const certManagerRoutes = route("/cert-manager/$projectId", [ layout("cert-manager-layout", "cert-manager/layout.tsx", [ route("/subscribers", [ index("cert-manager/PkiSubscribersPage/route.tsx"), - route("/$subscriberId", "cert-manager/PkiSubscriberDetailsByIDPage/route.tsx") + route("/$subscriberName", "cert-manager/PkiSubscriberDetailsByIDPage/route.tsx") ]), - route("/overview", "cert-manager/CertificatesPage/route.tsx"), route("/certificate-authorities", "cert-manager/CertificateAuthoritiesPage/route.tsx"), route("/alerting", "cert-manager/AlertingPage/route.tsx"),