Complete preliminary docs for pki subscribers

This commit is contained in:
Tuan Dang
2025-05-08 08:52:10 -07:00
parent c891b8f5d3
commit 107cca0b62
71 changed files with 1702 additions and 396 deletions

View File

@@ -18,13 +18,29 @@ export async function up(knex: Knex): Promise<void> {
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<void> {
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);
}

View File

@@ -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<typeof CertificatesSchema>;

View File

@@ -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<typeof PkiSubscribersSchema>;

View File

@@ -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

View File

@@ -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) => {

View File

@@ -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<ProjectPermissionSub.SshHosts> & SshHostSubjectFields)
]
| [ProjectPermissionPkiSubscriberActions, ProjectPermissionSub.PkiSubscribers] // (dangtony98): TODO: update
| [
ProjectPermissionPkiSubscriberActions,
(
| ProjectPermissionSub.PkiSubscribers
| (ForcedSubject<ProjectPermissionSub.PkiSubscribers> & 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
);

View File

@@ -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) {

View File

@@ -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 !== ""))))
})
});

View File

@@ -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."
}
};

View File

@@ -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
};
}
});
};

View File

@@ -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)
}),

View File

@@ -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" });

View File

@@ -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
};
};

View File

@@ -6,8 +6,5 @@ export type TPkiSubscriberDALFactory = ReturnType<typeof pkiSubscriberDALFactory
export const pkiSubscriberDALFactory = (db: TDbClient) => {
const pkiSubscriberOrm = ormify(db, TableName.PkiSubscriber);
return {
...pkiSubscriberOrm
};
return pkiSubscriberOrm;
};

View File

@@ -6,6 +6,7 @@ export const sanitizedPkiSubscriber = PkiSubscribersSchema.pick({
caId: true,
name: true,
commonName: true,
status: true,
subjectAlternativeNames: true,
ttl: true,
keyUsages: true,

View File

@@ -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<TCertificateAuthorityDALFactory, "findById">;
certificateAuthorityCertDAL: Pick<TCertificateAuthorityCertDALFactory, "findById">;
certificateAuthoritySecretDAL: Pick<TCertificateAuthoritySecretDALFactory, "findOne">;
certificateAuthorityCrlDAL: Pick<TCertificateAuthorityCrlDALFactory, "findOne">;
certificateDAL: Pick<TCertificateDALFactory, "create" | "transaction">;
certificateDAL: Pick<TCertificateDALFactory, "create" | "transaction" | "countCertificatesForPkiSubscriber" | "find">;
certificateBodyDAL: Pick<TCertificateBodyDALFactory, "create">;
certificateSecretDAL: Pick<TCertificateSecretDALFactory, "create">;
projectDAL: Pick<TProjectDALFactory, "findOne" | "updateById" | "transaction" | "findById" | "find">;
@@ -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
};
};

View File

@@ -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<TProjectPermission, "projectId">;
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, "projectId">;
} & TProjectPermission;
export type TDeletePkiSubscriberDTO = {
subscriberId: string;
} & Omit<TProjectPermission, "projectId">;
subscriberName: string;
} & TProjectPermission;
export type TIssuePkiSubscriberCertDTO = {
subscriberId: string;
} & Omit<TProjectPermission, "projectId">;
subscriberName: string;
} & TProjectPermission;
export type TSignPkiSubscriberCertDTO = {
subscriberId: string;
subscriberName: string;
csr: string;
} & Omit<TProjectPermission, "projectId">;
} & TProjectPermission;
export type TListPkiSubscriberCertsDTO = {
subscriberName: string;
offset: number;
limit: number;
} & TProjectPermission;

View File

@@ -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

View File

@@ -0,0 +1,4 @@
---
title: "Create"
openapi: "POST /api/v1/pki/subscribers"
---

View File

@@ -0,0 +1,4 @@
---
title: "Delete"
openapi: "DELETE /api/v1/pki/subscribers/{subscriberName}"
---

View File

@@ -0,0 +1,4 @@
---
title: "Issue Certificate"
openapi: "POST /api/v1/pki/subscribers/{subscriberName}/issue-cert"
---

View File

@@ -0,0 +1,4 @@
---
title: "List Certificates"
openapi: "GET /api/v1/pki/subscribers/{subscriberName}/certificates"
---

View File

@@ -0,0 +1,4 @@
---
title: "Retrieve"
openapi: "GET /api/v1/pki/subscribers/{subscriberName}"
---

View File

@@ -0,0 +1,4 @@
---
title: "Sign Certificate"
openapi: "POST /api/v1/pki/subscribers/{subscriberName}/sign-certificate"
---

View File

@@ -0,0 +1,4 @@
---
title: "Update"
openapi: "PATCH /api/v1/pki/subscribers/{subscriberName}"
---

View File

@@ -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}"
---

View File

@@ -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}"
---

View File

@@ -1,4 +1,4 @@
---
title: "List My Hosts"
openapi: "GET /api/v1/ssh/hosts/"
openapi: "GET /api/v1/ssh/hosts"
---

View File

@@ -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.

View File

@@ -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.

View File

@@ -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).
<div align="center">
@@ -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.
</Step>
</Steps>
@@ -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.
</Step>
</Steps>

View File

@@ -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).
<div align="center">
```mermaid
graph TD
A[Root CA] --> B[Intermediate CA]
B --> C1[Certificate]
C1 --> S1[Subscriber]
B --> C2[Certificate]
C2 --> S2[Subscriber]
```
</div>
## 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>
Note that this workflow can be executed via the Infisical UI or manually such
as via API.
</Note>
## Guide to Issuing Certificates with Subscribers
In the following steps, we explore how to issue a X.509 certificate for a subscriber.
<Steps>
<Step title="Creating 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.
<Note>
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.
</Note>
</Step>
<Step title="Requesting a certificate">
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)
</Step>
</Steps>
## 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.
<Steps>
<Step title="Revoking a Certificate">
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)
</Step>
<Step title="Obtaining a CRL">
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
```
</Step>
</Steps>
## FAQ
<AccordionGroup>
<Accordion title="What is the workflow for renewing a certificate?">
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.
</Accordion>
</AccordionGroup>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 550 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 904 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

View File

@@ -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": [

File diff suppressed because one or more lines are too long

View File

@@ -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: {

View File

@@ -10,5 +10,6 @@ export {
ProjectPermissionKmipActions,
ProjectPermissionMemberActions,
ProjectPermissionPkiSubscriberActions,
ProjectPermissionSshHostActions,
ProjectPermissionSub
} from "./types";

View File

@@ -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<ProjectPermissionSub.SshHosts> & SshHostSubjectFields)
)
]
| [
ProjectPermissionPkiSubscriberActions,
(
| ProjectPermissionSub.PkiSubscribers
| (ForcedSubject<ProjectPermissionSub.PkiSubscribers> & PkiSubscriberSubjectFields)
)
]
| [ProjectPermissionActions, ProjectPermissionSub.PkiAlerts]
| [ProjectPermissionActions, ProjectPermissionSub.PkiCollections]
| [ProjectPermissionPkiSubscriberActions, ProjectPermissionSub.PkiSubscribers]
| [ProjectPermissionSecretSyncActions, ProjectPermissionSub.SecretSyncs]
| [ProjectPermissionActions.Delete, ProjectPermissionSub.Project]
| [ProjectPermissionActions.Edit, ProjectPermissionSub.Project]

View File

@@ -18,6 +18,7 @@ export {
ProjectPermissionKmipActions,
ProjectPermissionMemberActions,
ProjectPermissionPkiSubscriberActions,
ProjectPermissionSshHostActions,
ProjectPermissionSub,
useProjectPermission
} from "./ProjectPermissionContext";

View File

@@ -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";
}
};

View File

@@ -1,6 +1,7 @@
export {
useCreatePkiSubscriber,
useDeletePkiSubscriber,
useIssuePkiSubscriberCert,
useUpdatePkiSubscriber
} from "./mutations";
export { useGetPkiSubscriberById } from "./queries";
export { useGetPkiSubscriber, useGetPkiSubscriberCertificates } from "./queries";

View File

@@ -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<TPkiSubscriber, object, TUpdatePkiSubscriberDTO>({
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<TPkiSubscriber, object, TDeletePkiSubscriberDTO>({
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<TCreateCertificateResponse, object, TIssuePkiSubscriberCertDTO>({
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
})
});
}
});
};

View File

@@ -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<TPkiSubscriber>(
`/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)
});
};

View File

@@ -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;
};

View File

@@ -112,7 +112,7 @@ export const ProjectLayout = () => {
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="certificate-authority">
<MenuItem isSelected={isActive} icon="pki-subscriber">
Subscribers
</MenuItem>
)}

View File

@@ -15,7 +15,10 @@ export const AlertingPage = () => {
<title>{t("common.head-title", { title: "Alerting" })}</title>
</Helmet>
<div className="mx-auto mb-6 w-full max-w-7xl">
<PageHeader title="Alerting" />
<PageHeader
title="Alerting"
description="Configure alerts for expiring certificates and CAs to maintain security and compliance."
/>
<ProjectPermissionCan
renderGuardBanner
I={ProjectPermissionActions.Read}

View File

@@ -15,7 +15,10 @@ export const CertificateAuthoritiesPage = () => {
<title>{t("common.head-title", { title: "Certificate Authorities" })}</title>
</Helmet>
<div className="mx-auto mb-6 w-full max-w-7xl">
<PageHeader title="Certificate Authorities" />
<PageHeader
title="Certificate Authorities"
description="Manage internal private certificate authorities for issuing and signing certificates, including root and intermediate CAs."
/>
<ProjectPermissionCan
renderGuardBanner
I={ProjectPermissionActions.Read}

View File

@@ -32,7 +32,10 @@ export const CertificatesPage = () => {
<title>{t("common.head-title", { title: "Certificates" })}</title>
</Helmet>
<div className="mx-auto mb-6 w-full max-w-7xl">
<PageHeader title="Certificates" />
<PageHeader
title="Certificates"
description="View and track issued certificates, monitor expiration dates, and manage certificate lifecycles."
/>
{/* If both are false, the section does not render. This is to prevent duplicate banners. */}
{(canAccessCerts || canAccessPkiColl) && (
<ProjectPermissionCan

View File

@@ -71,7 +71,7 @@ export const CertificatesTable = ({ handlePopUpOpen }: Props) => {
<Table>
<THead>
<Tr>
<Th>Friendly Name</Th>
<Th>Common Name</Th>
<Th>Status</Th>
<Th>Not Before</Th>
<Th>Not After</Th>
@@ -85,7 +85,7 @@ export const CertificatesTable = ({ handlePopUpOpen }: Props) => {
const { variant, label } = getCertValidUntilBadgeDetails(certificate.notAfter);
return (
<Tr className="h-10" key={`certificate-${certificate.id}`}>
<Td>{certificate.friendlyName}</Td>
<Td>{certificate.commonName}</Td>
<Td>
{certificate.status === CertStatus.REVOKED ? (
<Badge variant="danger">Revoked</Badge>

View File

@@ -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 = () => {
</PageHeader>
<div className="flex">
<div className="mr-4 w-96">
TODO: PkiSubscriberDetailsSection
{/* <PkiSubscriberDetailsSection
subscriberId={subscriberId}
<PkiSubscriberDetailsSection
subscriberName={data.name}
handlePopUpOpen={handlePopUpOpen}
/> */}
/>
</div>
<div className="w-full">
TODO: PkiSubscriberCertificatesSection
{/* <PkiSubscriberCertificatesSection subscriberId={subscriberId} /> */}
<PkiSubscriberCertificatesSection subscriberName={data.name} />
</div>
</div>
</div>
@@ -152,28 +153,14 @@ export const PkiSubscriberDetailsByIDPage = () => {
<Helmet>
<title>{t("common.head-title", { title: "PKI Subscriber" })}</title>
</Helmet>
{/* <ProjectPermissionCan
<ProjectPermissionCan
I={ProjectPermissionPkiSubscriberActions.Read}
a={ProjectPermissionSub.PkiSubscribers}
passThrough={false}
renderGuardBanner
>
<Page />
</ProjectPermissionCan> */}
TESTTT
</ProjectPermissionCan>
</>
);
};
/**
* 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
});
*/

View File

@@ -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 (
<div className="h-full rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="flex items-center justify-between border-b border-mineshaft-400 pb-4">
<h3 className="text-lg font-semibold text-mineshaft-100">Certificates</h3>
</div>
<div className="py-4">
<PkiSubscriberCertificatesTable
subscriberName={subscriberName}
handlePopUpOpen={handlePopUpOpen}
/>
</div>
<CertificateRevocationModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
</div>
);
};

View File

@@ -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 <Badge variant="danger">Revoked</Badge>;
}
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 <Badge variant="danger">Expired</Badge>;
}
if (daysUntilExpiry < 30) {
return <Badge variant="primary">Expiring Soon</Badge>;
}
return <Badge variant="success">Valid</Badge>;
};
return (
<div>
<TableContainer>
<Table>
<THead>
<Tr>
<Th>Common Name</Th>
<Th>Status</Th>
<Th>Not Before</Th>
<Th>Not After</Th>
<Th />
</Tr>
</THead>
<TBody>
{isPending && <TableSkeleton columns={5} innerKey="pki-subscriber-certificates" />}
{!isPending &&
data?.certificates?.map((certificate) => {
return (
<Tr className="h-10" key={`certificate-${certificate.id}`}>
<Td>{certificate.commonName}</Td>
<Td>{getCertStatusBadge(certificate.status, certificate.notAfter)}</Td>
<Td>
{certificate.notBefore
? format(new Date(certificate.notBefore), "yyyy-MM-dd")
: "-"}
</Td>
<Td>
{certificate.notAfter
? format(new Date(certificate.notAfter), "yyyy-MM-dd")
: "-"}
</Td>
<Td className="flex justify-end">
<DropdownMenu>
<DropdownMenuTrigger asChild className="rounded-lg">
<div className="hover:text-primary-400 data-[state=open]:text-primary-400">
<Tooltip content="More options">
<FontAwesomeIcon size="lg" icon={faEllipsis} />
</Tooltip>
</div>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="p-1">
<ProjectPermissionCan
I={ProjectPermissionPkiSubscriberActions.Delete}
a={ProjectPermissionSub.PkiSubscribers}
>
{(isAllowed) => (
<DropdownMenuItem
className={twMerge(
!isAllowed && "pointer-events-none cursor-not-allowed opacity-50"
)}
onClick={() =>
handlePopUpOpen &&
handlePopUpOpen("revokeCertificate", {
serialNumber: certificate.serialNumber
})
}
disabled={!isAllowed}
icon={<FontAwesomeIcon icon={faTrash} />}
>
Revoke Certificate
</DropdownMenuItem>
)}
</ProjectPermissionCan>
</DropdownMenuContent>
</DropdownMenu>
</Td>
</Tr>
);
})}
</TBody>
</Table>
{!isPending && data?.totalCount !== undefined && data.totalCount >= PER_PAGE_INIT && (
<Pagination
count={data.totalCount}
page={page}
perPage={perPage}
onChangePage={(newPage) => setPage(newPage)}
onChangePerPage={(newPerPage) => setPerPage(newPerPage)}
/>
)}
{!isPending && !data?.certificates?.length && (
<EmptyState
title="No certificates have been issued for this subscriber"
icon={faCertificate}
/>
)}
</TableContainer>
</div>
);
};

View File

@@ -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<TCertificateDetails | null>(null);
const [isModalOpen, setIsModalOpen] = useState(false);
const [copyTextId, isCopyingId, setCopyTextId] = useTimedReset<string>({
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 ? (
<div className="rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="flex items-center justify-between border-b border-mineshaft-400 pb-4">
<h3 className="text-lg font-semibold text-mineshaft-100">PKI Subscriber Details</h3>
<ProjectPermissionCan
I={ProjectPermissionPkiSubscriberActions.Edit}
a={ProjectPermissionSub.PkiSubscribers}
>
{(isAllowed) => {
return (
<Tooltip content="Edit PKI Subscriber">
<IconButton
isDisabled={!isAllowed}
ariaLabel="edit icon"
variant="plain"
className="group relative"
onClick={(e) => {
e.stopPropagation();
handlePopUpOpen("pkiSubscriber", {
subscriberName: pkiSubscriber.name
});
}}
>
<FontAwesomeIcon icon={faPencil} />
</IconButton>
</Tooltip>
);
}}
</ProjectPermissionCan>
</div>
<div className="pt-4">
<div className="mb-4">
<p className="text-sm font-semibold text-mineshaft-300">PKI Subscriber ID</p>
<div className="group flex align-top">
<p className="text-sm text-mineshaft-300">{pkiSubscriber.id}</p>
<div className="opacity-0 transition-opacity duration-300 group-hover:opacity-100">
<Tooltip content={copyTextId}>
<IconButton
ariaLabel="copy icon"
variant="plain"
className="group relative ml-2"
onClick={() => {
navigator.clipboard.writeText(pkiSubscriber.id);
setCopyTextId("Copied");
}}
>
<FontAwesomeIcon icon={isCopyingId ? faCheck : faCopy} />
</IconButton>
</Tooltip>
</div>
</div>
</div>
<div className="mb-4">
<p className="text-sm font-semibold text-mineshaft-300">Name</p>
<p className="text-sm text-mineshaft-300">{pkiSubscriber.name}</p>
</div>
<div className="mb-4">
<p className="text-sm font-semibold text-mineshaft-300">Common Name</p>
<p className="text-sm text-mineshaft-300">{pkiSubscriber.commonName}</p>
</div>
{canIssuePkiSubscriberCert && (
<Button
isDisabled={!canIssuePkiSubscriberCert}
className="mt-4 w-full"
colorSchema="primary"
type="submit"
isLoading={isIssuingCert}
onClick={() => {
onIssuePkiSubscriberCert();
}}
>
Issue Certificate
</Button>
)}
</div>
<Modal
isOpen={isModalOpen}
onOpenChange={(isOpen) => {
setIsModalOpen(isOpen);
if (!isOpen) {
setCertificateDetails(null);
}
}}
>
<ModalContent title="Certificate Details">
{certificateDetails && (
<CertificateContent
serialNumber={certificateDetails.serialNumber}
certificate={certificateDetails.certificate}
certificateChain={certificateDetails.certificateChain}
privateKey={certificateDetails.privateKey}
/>
)}
</ModalContent>
</Modal>
</div>
) : (
<div />
);
};

View File

@@ -0,0 +1,2 @@
export { PkiSubscriberCertificatesSection } from "./PkiSubscriberCertificatesSection";
export { PkiSubscriberDetailsSection } from "./PkiSubscriberDetailsSection";

View File

@@ -1,16 +1,9 @@
import { createFileRoute } from "@tanstack/react-router";
function RouteComponent() {
return (
<div>
Hello
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers/$subscriberId"!
</div>
);
}
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
});

View File

@@ -15,7 +15,10 @@ export const PkiSubscribersPage = () => {
<div className="h-full bg-bunker-800">
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
<div className="mx-auto mb-6 w-full max-w-7xl">
<PageHeader title="Subscribers" description="Manage your PKI subscribers." />
<PageHeader
title="Subscribers"
description="Manage subscribers that request and receive certificates, including user devices, servers, and services."
/>
<PkiSubscriberSection />
</div>
</div>

View File

@@ -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<typeof schema>;
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 } }) => (
<FormControl
label="Name"
label="Subscriber Name"
isError={Boolean(error)}
errorText={error?.message}
isRequired

View File

@@ -4,24 +4,33 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { createNotification } from "@app/components/notifications";
import { ProjectPermissionCan } from "@app/components/permissions";
import { Button, DeleteActionModal } from "@app/components/v2";
import { ProjectPermissionPkiSubscriberActions, ProjectPermissionSub } from "@app/context";
import { useDeletePkiSubscriber } from "@app/hooks/api";
import {
ProjectPermissionPkiSubscriberActions,
ProjectPermissionSub,
useWorkspace
} from "@app/context";
import { useDeletePkiSubscriber, useUpdatePkiSubscriber } from "@app/hooks/api";
import { PkiSubscriberStatus } from "@app/hooks/api/pkiSubscriber/types";
import { usePopUp } from "@app/hooks/usePopUp";
import { PkiSubscriberModal } from "./PkiSubscriberModal";
import { PkiSubscribersTable } from "./PkiSubscribersTable";
export const PkiSubscriberSection = () => {
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 (
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="mb-4 flex justify-between">
@@ -46,7 +90,7 @@ export const PkiSubscriberSection = () => {
<a
target="_blank"
rel="noopener noreferrer"
href="https://infisical.com/docs/documentation/platform/pki"
href="https://infisical.com/docs/documentation/platform/pki/subscribers"
>
<span className="flex w-max cursor-pointer items-center rounded-md border border-mineshaft-500 bg-mineshaft-600 px-4 py-2 text-mineshaft-200 duration-200 hover:border-primary/40 hover:bg-primary/10 hover:text-white">
Documentation{" "}
@@ -77,6 +121,20 @@ export const PkiSubscriberSection = () => {
</div>
<PkiSubscribersTable handlePopUpOpen={handlePopUpOpen} />
<PkiSubscriberModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
<DeleteActionModal
isOpen={popUp.pkiSubscriberStatus.isOpen}
title={`Are you sure want to ${isEnabling ? "enable" : "disable"} the subscriber ${subscriberName}?`}
subTitle={
isEnabling
? "This action will allow issuing certificates for this subscriber again."
: "This action will prevent issuing certificates for this subscriber."
}
onChange={(isOpen) => handlePopUpToggle("pkiSubscriberStatus", isOpen)}
deleteKey="confirm"
buttonColorSchema={isEnabling ? "primary" : "danger"}
buttonText={isEnabling ? "Enable" : "Disable"}
onDeleteApproved={() => onUpdatePkiSubscriberStatus(subscriberStatusData)}
/>
<DeleteActionModal
isOpen={popUp.deletePkiSubscriber.isOpen}
title="Are you sure you want to remove the PKI subscriber?"
@@ -84,7 +142,7 @@ export const PkiSubscriberSection = () => {
deleteKey="confirm"
onDeleteApproved={() =>
onRemovePkiSubscriberSubmit(
(popUp?.deletePkiSubscriber?.data as { subscriberId: string })?.subscriberId
(popUp?.deletePkiSubscriber?.data as { subscriberName: string })?.subscriberName
)
}
/>

View File

@@ -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) => {
<THead>
<Tr>
<Th>Name</Th>
<Th>Status</Th>
<Th>Common Name</Th>
<Th />
</Tr>
</THead>
<TBody>
{isPending && <TableSkeleton columns={3} innerKey="pki-subscribers" />}
{isPending && <TableSkeleton columns={4} innerKey="pki-subscribers" />}
{!isPending &&
data &&
data.length > 0 &&
@@ -61,18 +74,22 @@ export const PkiSubscribersTable = ({ handlePopUpOpen }: Props) => {
<Tr
className="h-10 cursor-pointer transition-colors duration-100 hover:bg-mineshaft-700"
key={`pki-subscriber-${subscriber.id}`}
onClick={(e) => {
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
}
});
}}
})
}
>
<Td>{subscriber.name}</Td>
<Td>
<Badge variant={getPkiSubscriberStatusBadgeVariant(subscriber.status)}>
{pkiSubscriberStatusToNameMap[subscriber.status]}
</Badge>
</Td>
<Td>{subscriber.commonName}</Td>
<Td className="text-right align-middle">
<DropdownMenu>
@@ -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) => {
</DropdownMenuItem>
)}
</ProjectPermissionCan>
<ProjectPermissionCan
I={ProjectPermissionPkiSubscriberActions.Edit}
a={ProjectPermissionSub.PkiSubscribers}
>
{(isAllowed) => (
<DropdownMenuItem
className={twMerge(
!isAllowed && "pointer-events-none cursor-not-allowed opacity-50"
)}
onClick={(e) => {
e.stopPropagation();
handlePopUpOpen("pkiSubscriberStatus", {
subscriberName: subscriber.name,
status:
subscriber.status === PkiSubscriberStatus.ACTIVE
? PkiSubscriberStatus.DISABLED
: PkiSubscriberStatus.ACTIVE
});
}}
disabled={!isAllowed}
icon={<FontAwesomeIcon icon={faBan} />}
>
{`${subscriber.status === PkiSubscriberStatus.ACTIVE ? "Disable" : "Enable"} Subscriber`}
</DropdownMenuItem>
)}
</ProjectPermissionCan>
<ProjectPermissionCan
I={ProjectPermissionPkiSubscriberActions.Delete}
a={ProjectPermissionSub.PkiSubscribers}
@@ -118,7 +161,7 @@ export const PkiSubscribersTable = ({ handlePopUpOpen }: Props) => {
onClick={(e) => {
e.stopPropagation();
handlePopUpOpen("deletePkiSubscriber", {
subscriberId: subscriber.id
subscriberName: subscriber.name
});
}}
disabled={!isAllowed}
@@ -137,7 +180,7 @@ export const PkiSubscribersTable = ({ handlePopUpOpen }: Props) => {
</TBody>
</Table>
{!isPending && data?.length === 0 && (
<EmptyState title="No PKI subscribers have been added" icon={faServer} />
<EmptyState title="No PKI subscribers have been added" icon={faUserShield} />
)}
</TableContainer>
</div>

View File

@@ -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 (
<div>
Hello
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers"!
</div>
)
}
component: PkiSubscribersPage
});

View File

@@ -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<TFormSchema>();
const permissionSubject = ProjectPermissionSub.PkiSubscribers;
const items = useFieldArray({
control,
name: `permissions.${permissionSubject}.${position}.conditions`
});
return (
<div className="mt-6 border-t border-t-mineshaft-600 bg-mineshaft-800 pt-2">
<p className="mt-2 text-gray-300">Conditions</p>
<p className="text-sm text-mineshaft-400">
Conditions determine when a policy will be applied (always if no conditions are present).
</p>
<p className="mb-3 text-sm leading-4 text-mineshaft-400">
All conditions must evaluate to true for the policy to take effect.
</p>
<div className="mt-2 flex flex-col space-y-2">
{items.fields.map((el, index) => {
const condition =
(watch(`permissions.${permissionSubject}.${position}.conditions.${index}`) as {
lhs: string;
rhs: string;
operator: string;
}) || {};
return (
<div
key={el.id}
className="flex gap-2 bg-mineshaft-800 first:rounded-t-md last:rounded-b-md"
>
<div className="w-1/4">
<Controller
control={control}
name={`permissions.${permissionSubject}.${position}.conditions.${index}.lhs`}
render={({ field, fieldState: { error } }) => (
<FormControl
isError={Boolean(error?.message)}
errorText={error?.message}
className="mb-0"
>
<Select
defaultValue={field.value}
{...field}
onValueChange={(e) => field.onChange(e)}
className="w-full"
>
<SelectItem value="name">Name</SelectItem>
</Select>
</FormControl>
)}
/>
</div>
<div className="flex w-36 items-center space-x-2">
<Controller
control={control}
name={`permissions.${permissionSubject}.${position}.conditions.${index}.operator`}
render={({ field, fieldState: { error } }) => (
<FormControl
isError={Boolean(error?.message)}
errorText={error?.message}
className="mb-0 flex-grow"
>
<Select
defaultValue={field.value}
{...field}
onValueChange={(e) => field.onChange(e)}
className="w-full"
>
<SelectItem value={PermissionConditionOperators.$EQ}>Equals</SelectItem>
<SelectItem value={PermissionConditionOperators.$GLOB}>Glob</SelectItem>
<SelectItem value={PermissionConditionOperators.$IN}>In</SelectItem>
</Select>
</FormControl>
)}
/>
<Tooltip
asChild
content={getConditionOperatorHelperInfo(
condition?.operator as PermissionConditionOperators
)}
className="max-w-xs"
>
<FontAwesomeIcon icon={faInfoCircle} size="xs" className="text-gray-400" />
</Tooltip>
</div>
<div className="flex-grow">
<Controller
control={control}
name={`permissions.${permissionSubject}.${position}.conditions.${index}.rhs`}
render={({ field, fieldState: { error } }) => (
<FormControl
isError={Boolean(error?.message)}
errorText={error?.message}
className="mb-0 flex-grow"
>
<Input {...field} />
</FormControl>
)}
/>
</div>
<div>
<IconButton
ariaLabel="plus"
variant="outline_bg"
className="p-2.5"
onClick={() => items.remove(index)}
>
<FontAwesomeIcon icon={faTrash} />
</IconButton>
</div>
</div>
);
})}
</div>
{errors?.permissions?.[permissionSubject]?.[position]?.conditions?.message && (
<div className="flex items-center space-x-2 py-2 text-sm text-gray-400">
<FontAwesomeIcon icon={faWarning} className="text-red" />
<span>{errors?.permissions?.[permissionSubject]?.[position]?.conditions?.message}</span>
</div>
)}
<div>
<Button
leftIcon={<FontAwesomeIcon icon={faPlus} />}
variant="star"
size="xs"
className="mt-3"
isDisabled={isDisabled}
onClick={() =>
items.append({
lhs: "name",
operator: PermissionConditionOperators.$EQ,
rhs: ""
})
}
>
Add Condition
</Button>
</div>
</div>
);
};

View File

@@ -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<typeof ConditionSchema> = [];
@@ -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: [

View File

@@ -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 <SshHostPermissionConditions isDisabled={isDisabled} />;
}
if (subject === ProjectPermissionSub.PkiSubscribers) {
return <PkiSubscriberPermissionConditions isDisabled={isDisabled} />;
}
return <GeneralPermissionConditions isDisabled={isDisabled} type={subject} />;
}

View File

@@ -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
</DropdownMenuItem>
<ProjectPermissionCan
I={ProjectPermissionActions.Edit}
I={ProjectPermissionSshHostActions.Edit}
a={ProjectPermissionSub.SshHosts}
>
{(isAllowed) => (
@@ -202,7 +202,7 @@ export const SshHostsTable = ({ handlePopUpOpen }: Props) => {
)}
</ProjectPermissionCan>
<ProjectPermissionCan
I={ProjectPermissionActions.Delete}
I={ProjectPermissionSshHostActions.Delete}
a={ProjectPermissionSub.SshHosts}
>
{(isAllowed) => (

View File

@@ -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"
},

View File

@@ -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"),