mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
Complete preliminary docs for pki subscribers
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 !== ""))))
|
||||
})
|
||||
});
|
||||
|
||||
@@ -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."
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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)
|
||||
}),
|
||||
|
||||
@@ -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" });
|
||||
|
||||
@@ -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
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -6,6 +6,7 @@ export const sanitizedPkiSubscriber = PkiSubscribersSchema.pick({
|
||||
caId: true,
|
||||
name: true,
|
||||
commonName: true,
|
||||
status: true,
|
||||
subjectAlternativeNames: true,
|
||||
ttl: true,
|
||||
keyUsages: true,
|
||||
|
||||
@@ -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
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
4
docs/api-reference/endpoints/pki/subscribers/create.mdx
Normal file
4
docs/api-reference/endpoints/pki/subscribers/create.mdx
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
title: "Create"
|
||||
openapi: "POST /api/v1/pki/subscribers"
|
||||
---
|
||||
4
docs/api-reference/endpoints/pki/subscribers/delete.mdx
Normal file
4
docs/api-reference/endpoints/pki/subscribers/delete.mdx
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
title: "Delete"
|
||||
openapi: "DELETE /api/v1/pki/subscribers/{subscriberName}"
|
||||
---
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
title: "Issue Certificate"
|
||||
openapi: "POST /api/v1/pki/subscribers/{subscriberName}/issue-cert"
|
||||
---
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
title: "List Certificates"
|
||||
openapi: "GET /api/v1/pki/subscribers/{subscriberName}/certificates"
|
||||
---
|
||||
4
docs/api-reference/endpoints/pki/subscribers/read.mdx
Normal file
4
docs/api-reference/endpoints/pki/subscribers/read.mdx
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
title: "Retrieve"
|
||||
openapi: "GET /api/v1/pki/subscribers/{subscriberName}"
|
||||
---
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
title: "Sign Certificate"
|
||||
openapi: "POST /api/v1/pki/subscribers/{subscriberName}/sign-certificate"
|
||||
---
|
||||
4
docs/api-reference/endpoints/pki/subscribers/update.mdx
Normal file
4
docs/api-reference/endpoints/pki/subscribers/update.mdx
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
title: "Update"
|
||||
openapi: "PATCH /api/v1/pki/subscribers/{subscriberName}"
|
||||
---
|
||||
@@ -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}"
|
||||
---
|
||||
|
||||
@@ -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}"
|
||||
---
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
---
|
||||
title: "List My Hosts"
|
||||
openapi: "GET /api/v1/ssh/hosts/"
|
||||
openapi: "GET /api/v1/ssh/hosts"
|
||||
---
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||

|
||||
|
||||
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>
|
||||
|
||||
131
docs/documentation/platform/pki/subscribers.mdx
Normal file
131
docs/documentation/platform/pki/subscribers.mdx
Normal 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.
|
||||
|
||||

|
||||
|
||||

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

|
||||
|
||||

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

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

|
||||
|
||||
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>
|
||||
BIN
docs/images/platform/pki/subscriber/subscriber-ca-crl.png
Normal file
BIN
docs/images/platform/pki/subscriber/subscriber-ca-crl.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 MiB |
BIN
docs/images/platform/pki/subscriber/subscriber-create-2.png
Normal file
BIN
docs/images/platform/pki/subscriber/subscriber-create-2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 550 KiB |
BIN
docs/images/platform/pki/subscriber/subscriber-create.png
Normal file
BIN
docs/images/platform/pki/subscriber/subscriber-create.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.0 MiB |
BIN
docs/images/platform/pki/subscriber/subscriber-issue-cert-2.png
Normal file
BIN
docs/images/platform/pki/subscriber/subscriber-issue-cert-2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 904 KiB |
BIN
docs/images/platform/pki/subscriber/subscriber-issue-cert.png
Normal file
BIN
docs/images/platform/pki/subscriber/subscriber-issue-cert.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
BIN
docs/images/platform/pki/subscriber/subscriber-revoke-cert.png
Normal file
BIN
docs/images/platform/pki/subscriber/subscriber-revoke-cert.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
@@ -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": [
|
||||
|
||||
1
frontend/public/lotties/pki-subscriber.json
Normal file
1
frontend/public/lotties/pki-subscriber.json
Normal file
File diff suppressed because one or more lines are too long
@@ -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: {
|
||||
|
||||
@@ -10,5 +10,6 @@ export {
|
||||
ProjectPermissionKmipActions,
|
||||
ProjectPermissionMemberActions,
|
||||
ProjectPermissionPkiSubscriberActions,
|
||||
ProjectPermissionSshHostActions,
|
||||
ProjectPermissionSub
|
||||
} from "./types";
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -18,6 +18,7 @@ export {
|
||||
ProjectPermissionKmipActions,
|
||||
ProjectPermissionMemberActions,
|
||||
ProjectPermissionPkiSubscriberActions,
|
||||
ProjectPermissionSshHostActions,
|
||||
ProjectPermissionSub,
|
||||
useProjectPermission
|
||||
} from "./ProjectPermissionContext";
|
||||
|
||||
20
frontend/src/hooks/api/pkiSubscriber/constants.tsx
Normal file
20
frontend/src/hooks/api/pkiSubscriber/constants.tsx
Normal 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";
|
||||
}
|
||||
};
|
||||
@@ -1,6 +1,7 @@
|
||||
export {
|
||||
useCreatePkiSubscriber,
|
||||
useDeletePkiSubscriber,
|
||||
useIssuePkiSubscriberCert,
|
||||
useUpdatePkiSubscriber
|
||||
} from "./mutations";
|
||||
export { useGetPkiSubscriberById } from "./queries";
|
||||
export { useGetPkiSubscriber, useGetPkiSubscriberCertificates } from "./queries";
|
||||
|
||||
@@ -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
|
||||
})
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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)
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -112,7 +112,7 @@ export const ProjectLayout = () => {
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive} icon="certificate-authority">
|
||||
<MenuItem isSelected={isActive} icon="pki-subscriber">
|
||||
Subscribers
|
||||
</MenuItem>
|
||||
)}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
});
|
||||
|
||||
*/
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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 />
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,2 @@
|
||||
export { PkiSubscriberCertificatesSection } from "./PkiSubscriberCertificatesSection";
|
||||
export { PkiSubscriberDetailsSection } from "./PkiSubscriberDetailsSection";
|
||||
@@ -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
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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: [
|
||||
|
||||
@@ -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} />;
|
||||
}
|
||||
|
||||
|
||||
@@ -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) => (
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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"),
|
||||
|
||||
Reference in New Issue
Block a user