mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-08 14:28:20 +00:00
misc: added route for acme
This commit is contained in:
@@ -78,6 +78,7 @@ export async function up(knex: Knex): Promise<void> {
|
|||||||
.onDelete("CASCADE");
|
.onDelete("CASCADE");
|
||||||
t.binary("credentials");
|
t.binary("credentials");
|
||||||
t.json("configuration");
|
t.json("configuration");
|
||||||
|
t.string("status").notNullable();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ export const ExternalCertificateAuthoritiesSchema = z.object({
|
|||||||
dnsAppConnectionId: z.string().uuid().nullable().optional(),
|
dnsAppConnectionId: z.string().uuid().nullable().optional(),
|
||||||
certificateAuthorityId: z.string().uuid(),
|
certificateAuthorityId: z.string().uuid(),
|
||||||
credentials: zodBuffer.nullable().optional(),
|
credentials: zodBuffer.nullable().optional(),
|
||||||
configuration: z.unknown().nullable().optional()
|
configuration: z.unknown().nullable().optional(),
|
||||||
|
status: z.string()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TExternalCertificateAuthorities = z.infer<typeof ExternalCertificateAuthoritiesSchema>;
|
export type TExternalCertificateAuthorities = z.infer<typeof ExternalCertificateAuthoritiesSchema>;
|
||||||
|
|||||||
@@ -133,6 +133,7 @@ import { certificateAuthorityDALFactory } from "@app/services/certificate-author
|
|||||||
import { certificateAuthorityQueueFactory } from "@app/services/certificate-authority/certificate-authority-queue";
|
import { certificateAuthorityQueueFactory } from "@app/services/certificate-authority/certificate-authority-queue";
|
||||||
import { certificateAuthoritySecretDALFactory } from "@app/services/certificate-authority/certificate-authority-secret-dal";
|
import { certificateAuthoritySecretDALFactory } from "@app/services/certificate-authority/certificate-authority-secret-dal";
|
||||||
import { certificateAuthorityServiceFactory } from "@app/services/certificate-authority/certificate-authority-service";
|
import { certificateAuthorityServiceFactory } from "@app/services/certificate-authority/certificate-authority-service";
|
||||||
|
import { externalCertificateAuthorityDALFactory } from "@app/services/certificate-authority/external-certificate-authority-dal";
|
||||||
import { internalCertificateAuthorityDALFactory } from "@app/services/certificate-authority/internal/internal-certificate-authority-dal";
|
import { internalCertificateAuthorityDALFactory } from "@app/services/certificate-authority/internal/internal-certificate-authority-dal";
|
||||||
import { internalCertificateAuthorityServiceFactory } from "@app/services/certificate-authority/internal/internal-certificate-authority-service";
|
import { internalCertificateAuthorityServiceFactory } from "@app/services/certificate-authority/internal/internal-certificate-authority-service";
|
||||||
import { certificateTemplateDALFactory } from "@app/services/certificate-template/certificate-template-dal";
|
import { certificateTemplateDALFactory } from "@app/services/certificate-template/certificate-template-dal";
|
||||||
@@ -820,6 +821,7 @@ export const registerRoutes = async (
|
|||||||
|
|
||||||
const certificateAuthorityDAL = certificateAuthorityDALFactory(db);
|
const certificateAuthorityDAL = certificateAuthorityDALFactory(db);
|
||||||
const internalCertificateAuthorityDAL = internalCertificateAuthorityDALFactory(db);
|
const internalCertificateAuthorityDAL = internalCertificateAuthorityDALFactory(db);
|
||||||
|
const externalCertificateAuthorityDAL = externalCertificateAuthorityDALFactory(db);
|
||||||
const certificateAuthorityCertDAL = certificateAuthorityCertDALFactory(db);
|
const certificateAuthorityCertDAL = certificateAuthorityCertDALFactory(db);
|
||||||
const certificateAuthoritySecretDAL = certificateAuthoritySecretDALFactory(db);
|
const certificateAuthoritySecretDAL = certificateAuthoritySecretDALFactory(db);
|
||||||
const certificateAuthorityCrlDAL = certificateAuthorityCrlDALFactory(db);
|
const certificateAuthorityCrlDAL = certificateAuthorityCrlDALFactory(db);
|
||||||
@@ -904,23 +906,6 @@ export const registerRoutes = async (
|
|||||||
groupDAL
|
groupDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
const certificateAuthorityService = certificateAuthorityServiceFactory({
|
|
||||||
certificateAuthorityDAL,
|
|
||||||
certificateAuthorityCertDAL,
|
|
||||||
certificateAuthoritySecretDAL,
|
|
||||||
certificateAuthorityCrlDAL,
|
|
||||||
certificateTemplateDAL,
|
|
||||||
certificateAuthorityQueue,
|
|
||||||
certificateDAL,
|
|
||||||
certificateBodyDAL,
|
|
||||||
certificateSecretDAL,
|
|
||||||
pkiCollectionDAL,
|
|
||||||
pkiCollectionItemDAL,
|
|
||||||
projectDAL,
|
|
||||||
kmsService,
|
|
||||||
permissionService
|
|
||||||
});
|
|
||||||
|
|
||||||
const internalCertificateAuthorityService = internalCertificateAuthorityServiceFactory({
|
const internalCertificateAuthorityService = internalCertificateAuthorityServiceFactory({
|
||||||
certificateAuthorityDAL,
|
certificateAuthorityDAL,
|
||||||
certificateAuthorityCertDAL,
|
certificateAuthorityCertDAL,
|
||||||
@@ -1695,6 +1680,16 @@ export const registerRoutes = async (
|
|||||||
appConnectionDAL
|
appConnectionDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const certificateAuthorityService = certificateAuthorityServiceFactory({
|
||||||
|
certificateAuthorityDAL,
|
||||||
|
projectDAL,
|
||||||
|
permissionService,
|
||||||
|
appConnectionDAL,
|
||||||
|
appConnectionService,
|
||||||
|
externalCertificateAuthorityDAL,
|
||||||
|
internalCertificateAuthorityService
|
||||||
|
});
|
||||||
|
|
||||||
await secretRotationV2QueueServiceFactory({
|
await secretRotationV2QueueServiceFactory({
|
||||||
secretRotationV2Service,
|
secretRotationV2Service,
|
||||||
secretRotationV2DAL,
|
secretRotationV2DAL,
|
||||||
|
|||||||
+18
@@ -0,0 +1,18 @@
|
|||||||
|
import {
|
||||||
|
AcmeCertificateAuthoritySchema,
|
||||||
|
CreateAcmeCertificateAuthoritySchema,
|
||||||
|
UpdateAcmeCertificateAuthoritySchema
|
||||||
|
} from "@app/services/certificate-authority/acme/acme-certificate-authority-schemas";
|
||||||
|
import { CaType } from "@app/services/certificate-authority/certificate-authority-enums";
|
||||||
|
|
||||||
|
import { registerCertificateAuthorityEndpoints } from "./certificate-authority-endpoints";
|
||||||
|
|
||||||
|
export const registerAcmeCertificateAuthorityRouter = async (server: FastifyZodProvider) => {
|
||||||
|
registerCertificateAuthorityEndpoints({
|
||||||
|
caType: CaType.ACME,
|
||||||
|
server,
|
||||||
|
responseSchema: AcmeCertificateAuthoritySchema,
|
||||||
|
createSchema: CreateAcmeCertificateAuthoritySchema,
|
||||||
|
updateSchema: UpdateAcmeCertificateAuthoritySchema
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { CaType } from "@app/services/certificate-authority/certificate-authority-enums";
|
import { CaType } from "@app/services/certificate-authority/certificate-authority-enums";
|
||||||
|
|
||||||
|
import { registerAcmeCertificateAuthorityRouter } from "./acme-certificate-authority-router";
|
||||||
import { registerInternalCertificateAuthorityRouter } from "./internal-certificate-authority-router";
|
import { registerInternalCertificateAuthorityRouter } from "./internal-certificate-authority-router";
|
||||||
|
|
||||||
export * from "./internal-certificate-authority-router";
|
export * from "./internal-certificate-authority-router";
|
||||||
@@ -7,5 +8,5 @@ export * from "./internal-certificate-authority-router";
|
|||||||
export const CERTIFICATE_AUTHORITY_REGISTER_ROUTER_MAP: Record<CaType, (server: FastifyZodProvider) => Promise<void>> =
|
export const CERTIFICATE_AUTHORITY_REGISTER_ROUTER_MAP: Record<CaType, (server: FastifyZodProvider) => Promise<void>> =
|
||||||
{
|
{
|
||||||
[CaType.INTERNAL]: registerInternalCertificateAuthorityRouter,
|
[CaType.INTERNAL]: registerInternalCertificateAuthorityRouter,
|
||||||
[CaType.ACME]: registerInternalCertificateAuthorityRouter
|
[CaType.ACME]: registerAcmeCertificateAuthorityRouter
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export enum AcmeDnsProvider {
|
||||||
|
Route53 = "route53"
|
||||||
|
}
|
||||||
@@ -0,0 +1,199 @@
|
|||||||
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
|
import { OrgServiceActor } from "@app/lib/types";
|
||||||
|
import { TAppConnectionDALFactory } from "@app/services/app-connection/app-connection-dal";
|
||||||
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
|
import { TAppConnectionServiceFactory } from "@app/services/app-connection/app-connection-service";
|
||||||
|
|
||||||
|
import { TCertificateAuthorityDALFactory } from "../certificate-authority-dal";
|
||||||
|
import { CaStatus, CaType } from "../certificate-authority-enums";
|
||||||
|
import { TCertificateAuthority } from "../certificate-authority-types";
|
||||||
|
import { TExternalCertificateAuthorityDALFactory } from "../external-certificate-authority-dal";
|
||||||
|
import { AcmeDnsProvider } from "./acme-certificate-authority-enums";
|
||||||
|
import {
|
||||||
|
TCreateAcmeCertificateAuthorityDTO,
|
||||||
|
TUpdateAcmeCertificateAuthorityDTO
|
||||||
|
} from "./acme-certificate-authority-types";
|
||||||
|
|
||||||
|
type TAcmeCertificateAuthorityFnsDeps = {
|
||||||
|
appConnectionDAL: Pick<TAppConnectionDALFactory, "findById">;
|
||||||
|
appConnectionService: Pick<TAppConnectionServiceFactory, "connectAppConnectionById">;
|
||||||
|
certificateAuthorityDAL: Pick<
|
||||||
|
TCertificateAuthorityDALFactory,
|
||||||
|
"create" | "transaction" | "findByIdWithAssociatedCa" | "updateById"
|
||||||
|
>;
|
||||||
|
externalCertificateAuthorityDAL: Pick<TExternalCertificateAuthorityDALFactory, "create" | "update">;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AcmeCertificateAuthorityFns = ({
|
||||||
|
appConnectionDAL,
|
||||||
|
appConnectionService,
|
||||||
|
certificateAuthorityDAL,
|
||||||
|
externalCertificateAuthorityDAL
|
||||||
|
}: TAcmeCertificateAuthorityFnsDeps) => {
|
||||||
|
const createCertificateAuthority = async ({
|
||||||
|
name,
|
||||||
|
projectId,
|
||||||
|
configuration,
|
||||||
|
disableDirectIssuance,
|
||||||
|
actor,
|
||||||
|
status
|
||||||
|
}: {
|
||||||
|
status: CaStatus;
|
||||||
|
name: string;
|
||||||
|
projectId: string;
|
||||||
|
configuration: TCreateAcmeCertificateAuthorityDTO["configuration"];
|
||||||
|
disableDirectIssuance: boolean;
|
||||||
|
actor: OrgServiceActor;
|
||||||
|
}) => {
|
||||||
|
const { dnsAppConnectionId, directoryUrl, accountEmail, dnsProvider } = configuration;
|
||||||
|
const appConnection = await appConnectionDAL.findById(dnsAppConnectionId);
|
||||||
|
|
||||||
|
if (!appConnection) {
|
||||||
|
throw new NotFoundError({ message: `App connection with ID '${dnsAppConnectionId}' not found` });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dnsProvider === AcmeDnsProvider.Route53 && appConnection.app !== AppConnection.AWS) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `App connection with ID '${dnsAppConnectionId}' is not an AWS connection`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// validates permission to connect
|
||||||
|
await appConnectionService.connectAppConnectionById(appConnection.app as AppConnection, dnsAppConnectionId, actor);
|
||||||
|
|
||||||
|
const caEntity = await certificateAuthorityDAL.transaction(async (tx) => {
|
||||||
|
const ca = await certificateAuthorityDAL.create(
|
||||||
|
{
|
||||||
|
projectId,
|
||||||
|
disableDirectIssuance
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
|
||||||
|
await externalCertificateAuthorityDAL.create(
|
||||||
|
{
|
||||||
|
certificateAuthorityId: ca.id,
|
||||||
|
dnsAppConnectionId,
|
||||||
|
type: CaType.ACME,
|
||||||
|
name,
|
||||||
|
configuration: {
|
||||||
|
directoryUrl,
|
||||||
|
accountEmail,
|
||||||
|
dnsProvider
|
||||||
|
},
|
||||||
|
status
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
|
||||||
|
return certificateAuthorityDAL.findByIdWithAssociatedCa(ca.id, tx);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!caEntity.externalCa) {
|
||||||
|
throw new BadRequestError({ message: "Failed to create external certificate authority" });
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: caEntity.id,
|
||||||
|
type: CaType.ACME,
|
||||||
|
disableDirectIssuance: caEntity.disableDirectIssuance,
|
||||||
|
name: caEntity.externalCa.name,
|
||||||
|
projectId,
|
||||||
|
status,
|
||||||
|
configuration: caEntity.externalCa.configuration
|
||||||
|
} as TCertificateAuthority;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateCertificateAuthority = async ({
|
||||||
|
id,
|
||||||
|
status,
|
||||||
|
configuration,
|
||||||
|
disableDirectIssuance,
|
||||||
|
actor
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
status?: CaStatus;
|
||||||
|
configuration: TUpdateAcmeCertificateAuthorityDTO["configuration"];
|
||||||
|
disableDirectIssuance?: boolean;
|
||||||
|
actor: OrgServiceActor;
|
||||||
|
}) => {
|
||||||
|
const updatedCa = await certificateAuthorityDAL.transaction(async (tx) => {
|
||||||
|
if (configuration) {
|
||||||
|
const { dnsAppConnectionId, directoryUrl, accountEmail, dnsProvider } = configuration;
|
||||||
|
const appConnection = await appConnectionDAL.findById(dnsAppConnectionId);
|
||||||
|
|
||||||
|
if (!appConnection) {
|
||||||
|
throw new NotFoundError({ message: `App connection with ID '${dnsAppConnectionId}' not found` });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dnsProvider === AcmeDnsProvider.Route53 && appConnection.app !== AppConnection.AWS) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `App connection with ID '${dnsAppConnectionId}' is not an AWS connection`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// validates permission to connect
|
||||||
|
await appConnectionService.connectAppConnectionById(
|
||||||
|
appConnection.app as AppConnection,
|
||||||
|
dnsAppConnectionId,
|
||||||
|
actor
|
||||||
|
);
|
||||||
|
|
||||||
|
await externalCertificateAuthorityDAL.update(
|
||||||
|
{
|
||||||
|
certificateAuthorityId: id,
|
||||||
|
type: CaType.ACME
|
||||||
|
},
|
||||||
|
{
|
||||||
|
configuration: { directoryUrl, accountEmail, dnsProvider, dnsAppConnectionId }
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status) {
|
||||||
|
await externalCertificateAuthorityDAL.update(
|
||||||
|
{
|
||||||
|
certificateAuthorityId: id,
|
||||||
|
type: CaType.ACME
|
||||||
|
},
|
||||||
|
{
|
||||||
|
status
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (disableDirectIssuance !== undefined) {
|
||||||
|
await certificateAuthorityDAL.updateById(
|
||||||
|
id,
|
||||||
|
{
|
||||||
|
disableDirectIssuance
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return certificateAuthorityDAL.findByIdWithAssociatedCa(id, tx);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!updatedCa.externalCa) {
|
||||||
|
throw new BadRequestError({ message: "Failed to update external certificate authority" });
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: updatedCa.id,
|
||||||
|
type: CaType.ACME,
|
||||||
|
disableDirectIssuance: updatedCa.disableDirectIssuance,
|
||||||
|
name: updatedCa.externalCa.name,
|
||||||
|
projectId: updatedCa.projectId,
|
||||||
|
status: updatedCa.externalCa.status,
|
||||||
|
configuration: updatedCa.externalCa.configuration
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
createCertificateAuthority,
|
||||||
|
updateCertificateAuthority
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { CaType } from "../certificate-authority-enums";
|
||||||
|
import {
|
||||||
|
BaseCertificateAuthoritySchema,
|
||||||
|
GenericCreateCertificateAuthorityFieldsSchema,
|
||||||
|
GenericUpdateCertificateAuthorityFieldsSchema
|
||||||
|
} from "../certificate-authority-schemas";
|
||||||
|
import { AcmeDnsProvider } from "./acme-certificate-authority-enums";
|
||||||
|
|
||||||
|
export const AcmeCertificateAuthorityConfigurationSchema = z.object({
|
||||||
|
dnsAppConnectionId: z.string().trim(),
|
||||||
|
dnsProvider: z.nativeEnum(AcmeDnsProvider),
|
||||||
|
directoryUrl: z.string().trim(),
|
||||||
|
accountEmail: z.string().trim()
|
||||||
|
});
|
||||||
|
|
||||||
|
export const AcmeCertificateAuthoritySchema = BaseCertificateAuthoritySchema(CaType.ACME).extend({
|
||||||
|
type: z.literal(CaType.ACME),
|
||||||
|
configuration: AcmeCertificateAuthorityConfigurationSchema
|
||||||
|
});
|
||||||
|
|
||||||
|
export const CreateAcmeCertificateAuthoritySchema = GenericCreateCertificateAuthorityFieldsSchema(CaType.ACME).extend({
|
||||||
|
configuration: AcmeCertificateAuthorityConfigurationSchema
|
||||||
|
});
|
||||||
|
|
||||||
|
export const UpdateAcmeCertificateAuthoritySchema = GenericUpdateCertificateAuthorityFieldsSchema(CaType.ACME).extend({
|
||||||
|
configuration: AcmeCertificateAuthorityConfigurationSchema.optional()
|
||||||
|
});
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import {
|
||||||
|
AcmeCertificateAuthoritySchema,
|
||||||
|
CreateAcmeCertificateAuthoritySchema,
|
||||||
|
UpdateAcmeCertificateAuthoritySchema
|
||||||
|
} from "./acme-certificate-authority-schemas";
|
||||||
|
|
||||||
|
export type TAcmeCertificateAuthority = z.infer<typeof AcmeCertificateAuthoritySchema>;
|
||||||
|
|
||||||
|
export type TAcmeCertificateAuthorityInput = z.infer<typeof CreateAcmeCertificateAuthoritySchema>;
|
||||||
|
|
||||||
|
export type TCreateAcmeCertificateAuthorityDTO = z.infer<typeof CreateAcmeCertificateAuthoritySchema>;
|
||||||
|
|
||||||
|
export type TUpdateAcmeCertificateAuthorityDTO = z.infer<typeof UpdateAcmeCertificateAuthoritySchema>;
|
||||||
@@ -21,6 +21,11 @@ export const certificateAuthorityDALFactory = (db: TDbClient) => {
|
|||||||
`${TableName.CertificateAuthority}.id`,
|
`${TableName.CertificateAuthority}.id`,
|
||||||
`${TableName.InternalCertificateAuthority}.certificateAuthorityId`
|
`${TableName.InternalCertificateAuthority}.certificateAuthorityId`
|
||||||
)
|
)
|
||||||
|
.leftJoin(
|
||||||
|
TableName.ExternalCertificateAuthority,
|
||||||
|
`${TableName.CertificateAuthority}.id`,
|
||||||
|
`${TableName.ExternalCertificateAuthority}.certificateAuthorityId`
|
||||||
|
)
|
||||||
.where(`${TableName.CertificateAuthority}.id`, caId)
|
.where(`${TableName.CertificateAuthority}.id`, caId)
|
||||||
.select(selectAllTableCols(TableName.CertificateAuthority))
|
.select(selectAllTableCols(TableName.CertificateAuthority))
|
||||||
.select(
|
.select(
|
||||||
@@ -47,6 +52,18 @@ export const certificateAuthorityDALFactory = (db: TDbClient) => {
|
|||||||
.withSchema(TableName.InternalCertificateAuthority)
|
.withSchema(TableName.InternalCertificateAuthority)
|
||||||
.as("internalCertificateAuthorityId")
|
.as("internalCertificateAuthorityId")
|
||||||
)
|
)
|
||||||
|
.select(
|
||||||
|
db.ref("id").withSchema(TableName.ExternalCertificateAuthority).as("externalCaId"),
|
||||||
|
db.ref("name").withSchema(TableName.ExternalCertificateAuthority).as("externalName"),
|
||||||
|
db.ref("type").withSchema(TableName.ExternalCertificateAuthority).as("externalType"),
|
||||||
|
db.ref("status").withSchema(TableName.ExternalCertificateAuthority).as("externalStatus"),
|
||||||
|
db.ref("configuration").withSchema(TableName.ExternalCertificateAuthority).as("externalConfiguration"),
|
||||||
|
db
|
||||||
|
.ref("dnsAppConnectionId")
|
||||||
|
.withSchema(TableName.ExternalCertificateAuthority)
|
||||||
|
.as("externalDnsAppConnectionId"),
|
||||||
|
db.ref("appConnectionId").withSchema(TableName.ExternalCertificateAuthority).as("externalAppConnectionId")
|
||||||
|
)
|
||||||
.first();
|
.first();
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
@@ -73,6 +90,17 @@ export const certificateAuthorityDALFactory = (db: TDbClient) => {
|
|||||||
activeCaCertId: result.internalActiveCaCertId,
|
activeCaCertId: result.internalActiveCaCertId,
|
||||||
certificateAuthorityId: result.internalCertificateAuthorityId
|
certificateAuthorityId: result.internalCertificateAuthorityId
|
||||||
}
|
}
|
||||||
|
: undefined,
|
||||||
|
externalCa: result
|
||||||
|
? {
|
||||||
|
id: result.externalCaId,
|
||||||
|
name: result.externalName,
|
||||||
|
type: result.externalType,
|
||||||
|
status: result.externalStatus,
|
||||||
|
configuration: result.externalConfiguration,
|
||||||
|
dnsAppConnectionId: result.externalDnsAppConnectionId,
|
||||||
|
appConnectionId: result.externalAppConnectionId
|
||||||
|
}
|
||||||
: undefined
|
: undefined
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -158,11 +186,15 @@ export const certificateAuthorityDALFactory = (db: TDbClient) => {
|
|||||||
)
|
)
|
||||||
.select(
|
.select(
|
||||||
db.ref("id").withSchema(TableName.ExternalCertificateAuthority).as("externalCaId"),
|
db.ref("id").withSchema(TableName.ExternalCertificateAuthority).as("externalCaId"),
|
||||||
|
db.ref("name").withSchema(TableName.ExternalCertificateAuthority).as("externalName"),
|
||||||
db.ref("type").withSchema(TableName.ExternalCertificateAuthority).as("externalType"),
|
db.ref("type").withSchema(TableName.ExternalCertificateAuthority).as("externalType"),
|
||||||
|
db.ref("status").withSchema(TableName.ExternalCertificateAuthority).as("externalStatus"),
|
||||||
|
db.ref("configuration").withSchema(TableName.ExternalCertificateAuthority).as("externalConfiguration"),
|
||||||
db
|
db
|
||||||
.ref("certificateAuthorityId")
|
.ref("dnsAppConnectionId")
|
||||||
.withSchema(TableName.ExternalCertificateAuthority)
|
.withSchema(TableName.ExternalCertificateAuthority)
|
||||||
.as("externalCertificateAuthorityId")
|
.as("externalDnsAppConnectionId"),
|
||||||
|
db.ref("appConnectionId").withSchema(TableName.ExternalCertificateAuthority).as("externalAppConnectionId")
|
||||||
);
|
);
|
||||||
|
|
||||||
if (limit) void query.limit(limit);
|
if (limit) void query.limit(limit);
|
||||||
@@ -201,6 +233,17 @@ export const certificateAuthorityDALFactory = (db: TDbClient) => {
|
|||||||
activeCaCertId: ca.internalActiveCaCertId,
|
activeCaCertId: ca.internalActiveCaCertId,
|
||||||
certificateAuthorityId: ca.internalCertificateAuthorityId
|
certificateAuthorityId: ca.internalCertificateAuthorityId
|
||||||
}
|
}
|
||||||
|
: undefined,
|
||||||
|
externalCa: ca
|
||||||
|
? {
|
||||||
|
id: ca.externalCaId,
|
||||||
|
name: ca.externalName,
|
||||||
|
type: ca.externalType,
|
||||||
|
status: ca.externalStatus,
|
||||||
|
configuration: ca.externalConfiguration,
|
||||||
|
dnsAppConnectionId: ca.externalDnsAppConnectionId,
|
||||||
|
appConnectionId: ca.externalAppConnectionId
|
||||||
|
}
|
||||||
: undefined
|
: undefined
|
||||||
}));
|
}));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import z from "zod";
|
|||||||
import { CertificateAuthoritiesSchema } from "@app/db/schemas";
|
import { CertificateAuthoritiesSchema } from "@app/db/schemas";
|
||||||
import { slugSchema } from "@app/server/lib/schemas";
|
import { slugSchema } from "@app/server/lib/schemas";
|
||||||
|
|
||||||
import { CaType } from "./certificate-authority-enums";
|
import { CaStatus, CaType } from "./certificate-authority-enums";
|
||||||
|
|
||||||
// SHEEN TODO: add description mapping using type
|
// SHEEN TODO: add description mapping using type
|
||||||
export const BaseCertificateAuthoritySchema = (type: CaType) =>
|
export const BaseCertificateAuthoritySchema = (type: CaType) =>
|
||||||
@@ -12,18 +12,21 @@ export const BaseCertificateAuthoritySchema = (type: CaType) =>
|
|||||||
disableDirectIssuance: true,
|
disableDirectIssuance: true,
|
||||||
id: true
|
id: true
|
||||||
}).extend({
|
}).extend({
|
||||||
name: z.string()
|
name: z.string(),
|
||||||
|
status: z.nativeEnum(CaStatus)
|
||||||
});
|
});
|
||||||
|
|
||||||
export const GenericCreateCertificateAuthorityFieldsSchema = (type: CaType) =>
|
export const GenericCreateCertificateAuthorityFieldsSchema = (type: CaType) =>
|
||||||
z.object({
|
z.object({
|
||||||
name: slugSchema({ field: "name" }),
|
name: slugSchema({ field: "name" }),
|
||||||
projectId: z.string().trim().min(1, "Project ID required"),
|
projectId: z.string().trim().min(1, "Project ID required"),
|
||||||
disableDirectIssuance: z.boolean()
|
disableDirectIssuance: z.boolean(),
|
||||||
|
status: z.nativeEnum(CaStatus)
|
||||||
});
|
});
|
||||||
|
|
||||||
export const GenericUpdateCertificateAuthorityFieldsSchema = (type: CaType) =>
|
export const GenericUpdateCertificateAuthorityFieldsSchema = (type: CaType) =>
|
||||||
z.object({
|
z.object({
|
||||||
name: slugSchema({ field: "name" }).optional(),
|
name: slugSchema({ field: "name" }).optional(),
|
||||||
disableDirectIssuance: z.boolean().optional()
|
disableDirectIssuance: z.boolean().optional(),
|
||||||
|
status: z.nativeEnum(CaStatus).optional()
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,7 +6,14 @@ import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services
|
|||||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { OrgServiceActor } from "@app/lib/types";
|
import { OrgServiceActor } from "@app/lib/types";
|
||||||
|
|
||||||
|
import { TAppConnectionDALFactory } from "../app-connection/app-connection-dal";
|
||||||
|
import { TAppConnectionServiceFactory } from "../app-connection/app-connection-service";
|
||||||
import { TProjectDALFactory } from "../project/project-dal";
|
import { TProjectDALFactory } from "../project/project-dal";
|
||||||
|
import { AcmeCertificateAuthorityFns } from "./acme/acme-certificate-authority-fns";
|
||||||
|
import {
|
||||||
|
TCreateAcmeCertificateAuthorityDTO,
|
||||||
|
TUpdateAcmeCertificateAuthorityDTO
|
||||||
|
} from "./acme/acme-certificate-authority-types";
|
||||||
import { TCertificateAuthorityDALFactory } from "./certificate-authority-dal";
|
import { TCertificateAuthorityDALFactory } from "./certificate-authority-dal";
|
||||||
import { CaType } from "./certificate-authority-enums";
|
import { CaType } from "./certificate-authority-enums";
|
||||||
import {
|
import {
|
||||||
@@ -14,9 +21,13 @@ import {
|
|||||||
TCreateCertificateAuthorityDTO,
|
TCreateCertificateAuthorityDTO,
|
||||||
TUpdateCertificateAuthorityDTO
|
TUpdateCertificateAuthorityDTO
|
||||||
} from "./certificate-authority-types";
|
} from "./certificate-authority-types";
|
||||||
|
import { TExternalCertificateAuthorityDALFactory } from "./external-certificate-authority-dal";
|
||||||
import { TInternalCertificateAuthorityServiceFactory } from "./internal/internal-certificate-authority-service";
|
import { TInternalCertificateAuthorityServiceFactory } from "./internal/internal-certificate-authority-service";
|
||||||
|
import { TCreateInternalCertificateAuthorityDTO } from "./internal/internal-certificate-authority-types";
|
||||||
|
|
||||||
type TCertificateAuthorityServiceFactoryDep = {
|
type TCertificateAuthorityServiceFactoryDep = {
|
||||||
|
appConnectionDAL: Pick<TAppConnectionDALFactory, "findById" | "update">;
|
||||||
|
appConnectionService: Pick<TAppConnectionServiceFactory, "connectAppConnectionById">;
|
||||||
certificateAuthorityDAL: Pick<
|
certificateAuthorityDAL: Pick<
|
||||||
TCertificateAuthorityDALFactory,
|
TCertificateAuthorityDALFactory,
|
||||||
| "transaction"
|
| "transaction"
|
||||||
@@ -28,6 +39,7 @@ type TCertificateAuthorityServiceFactoryDep = {
|
|||||||
| "findByIdWithAssociatedCa"
|
| "findByIdWithAssociatedCa"
|
||||||
| "findWithAssociatedCa"
|
| "findWithAssociatedCa"
|
||||||
>;
|
>;
|
||||||
|
externalCertificateAuthorityDAL: Pick<TExternalCertificateAuthorityDALFactory, "create" | "update">;
|
||||||
internalCertificateAuthorityService: TInternalCertificateAuthorityServiceFactory;
|
internalCertificateAuthorityService: TInternalCertificateAuthorityServiceFactory;
|
||||||
projectDAL: Pick<
|
projectDAL: Pick<
|
||||||
TProjectDALFactory,
|
TProjectDALFactory,
|
||||||
@@ -42,10 +54,20 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
certificateAuthorityDAL,
|
certificateAuthorityDAL,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
permissionService,
|
permissionService,
|
||||||
internalCertificateAuthorityService
|
internalCertificateAuthorityService,
|
||||||
|
appConnectionDAL,
|
||||||
|
appConnectionService,
|
||||||
|
externalCertificateAuthorityDAL
|
||||||
}: TCertificateAuthorityServiceFactoryDep) => {
|
}: TCertificateAuthorityServiceFactoryDep) => {
|
||||||
|
const acmeFns = AcmeCertificateAuthorityFns({
|
||||||
|
appConnectionDAL,
|
||||||
|
appConnectionService,
|
||||||
|
certificateAuthorityDAL,
|
||||||
|
externalCertificateAuthorityDAL
|
||||||
|
});
|
||||||
|
|
||||||
const createCertificateAuthority = async (
|
const createCertificateAuthority = async (
|
||||||
{ type, projectId, configuration, disableDirectIssuance }: TCreateCertificateAuthorityDTO,
|
{ type, projectId, name, disableDirectIssuance, configuration, status }: TCreateCertificateAuthorityDTO,
|
||||||
actor: OrgServiceActor
|
actor: OrgServiceActor
|
||||||
) => {
|
) => {
|
||||||
let finalProjectId: string = projectId;
|
let finalProjectId: string = projectId;
|
||||||
@@ -74,7 +96,7 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
|
|
||||||
if (type === CaType.INTERNAL) {
|
if (type === CaType.INTERNAL) {
|
||||||
const ca = await internalCertificateAuthorityService.createCa({
|
const ca = await internalCertificateAuthorityService.createCa({
|
||||||
...configuration,
|
...(configuration as TCreateInternalCertificateAuthorityDTO["configuration"]),
|
||||||
isInternal: true,
|
isInternal: true,
|
||||||
projectId: finalProjectId,
|
projectId: finalProjectId,
|
||||||
requireTemplateForIssuance: disableDirectIssuance
|
requireTemplateForIssuance: disableDirectIssuance
|
||||||
@@ -92,9 +114,21 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
disableDirectIssuance: ca.disableDirectIssuance,
|
disableDirectIssuance: ca.disableDirectIssuance,
|
||||||
name: ca.internalCa?.friendlyName,
|
name: ca.internalCa?.friendlyName,
|
||||||
projectId,
|
projectId,
|
||||||
|
status,
|
||||||
configuration: ca.internalCa
|
configuration: ca.internalCa
|
||||||
} as TCertificateAuthority;
|
} as TCertificateAuthority;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (type === CaType.ACME) {
|
||||||
|
return acmeFns.createCertificateAuthority({
|
||||||
|
name,
|
||||||
|
projectId: finalProjectId,
|
||||||
|
configuration: configuration as TCreateAcmeCertificateAuthorityDTO["configuration"],
|
||||||
|
disableDirectIssuance,
|
||||||
|
status,
|
||||||
|
actor
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const findCertificateAuthorityById = async (
|
const findCertificateAuthorityById = async (
|
||||||
@@ -135,9 +169,25 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
disableDirectIssuance: certificateAuthority.disableDirectIssuance,
|
disableDirectIssuance: certificateAuthority.disableDirectIssuance,
|
||||||
name: certificateAuthority.internalCa.friendlyName,
|
name: certificateAuthority.internalCa.friendlyName,
|
||||||
projectId: certificateAuthority.projectId,
|
projectId: certificateAuthority.projectId,
|
||||||
configuration: certificateAuthority.internalCa
|
configuration: certificateAuthority.internalCa,
|
||||||
|
status: certificateAuthority.internalCa.status
|
||||||
} as TCertificateAuthority;
|
} as TCertificateAuthority;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (certificateAuthority.externalCa?.type !== type) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Could not find external certificate authority with ID "${certificateAuthorityId}" and type "${type}"`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: certificateAuthority.id,
|
||||||
|
type,
|
||||||
|
disableDirectIssuance: certificateAuthority.disableDirectIssuance,
|
||||||
|
name: certificateAuthority.externalCa.name,
|
||||||
|
projectId: certificateAuthority.projectId,
|
||||||
|
configuration: certificateAuthority.externalCa.configuration
|
||||||
|
} as TCertificateAuthority;
|
||||||
};
|
};
|
||||||
|
|
||||||
const listCertificateAuthoritiesByProjectId = async (
|
const listCertificateAuthoritiesByProjectId = async (
|
||||||
@@ -187,13 +237,26 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
disableDirectIssuance: ca.disableDirectIssuance,
|
disableDirectIssuance: ca.disableDirectIssuance,
|
||||||
name: ca.internalCa.friendlyName,
|
name: ca.internalCa.friendlyName,
|
||||||
projectId: ca.projectId,
|
projectId: ca.projectId,
|
||||||
configuration: ca.internalCa
|
configuration: ca.internalCa,
|
||||||
|
status: ca.internalCa.status
|
||||||
})) as TCertificateAuthority[];
|
})) as TCertificateAuthority[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return cas
|
||||||
|
.filter((ca): ca is typeof ca & { externalCa: NonNullable<typeof ca.externalCa> } => Boolean(ca.externalCa))
|
||||||
|
.map((ca) => ({
|
||||||
|
id: ca.id,
|
||||||
|
type,
|
||||||
|
disableDirectIssuance: ca.disableDirectIssuance,
|
||||||
|
name: ca.externalCa.name,
|
||||||
|
projectId: ca.projectId,
|
||||||
|
configuration: ca.externalCa.configuration,
|
||||||
|
status: ca.externalCa.status
|
||||||
|
})) as TCertificateAuthority[];
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateCertificateAuthority = async (
|
const updateCertificateAuthority = async (
|
||||||
{ id, type, configuration, disableDirectIssuance }: TUpdateCertificateAuthorityDTO,
|
{ id, type, configuration, disableDirectIssuance, status }: TUpdateCertificateAuthorityDTO,
|
||||||
actor: OrgServiceActor
|
actor: OrgServiceActor
|
||||||
) => {
|
) => {
|
||||||
const certificateAuthority = await certificateAuthorityDAL.findByIdWithAssociatedCa(id);
|
const certificateAuthority = await certificateAuthorityDAL.findByIdWithAssociatedCa(id);
|
||||||
@@ -243,9 +306,22 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
disableDirectIssuance: updatedCa.disableDirectIssuance,
|
disableDirectIssuance: updatedCa.disableDirectIssuance,
|
||||||
name: updatedCa.internalCa?.friendlyName,
|
name: updatedCa.internalCa?.friendlyName,
|
||||||
projectId: updatedCa.projectId,
|
projectId: updatedCa.projectId,
|
||||||
configuration: updatedCa.internalCa
|
configuration: updatedCa.internalCa,
|
||||||
|
status: updatedCa.internalCa?.status
|
||||||
} as TCertificateAuthority;
|
} as TCertificateAuthority;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (type === CaType.ACME) {
|
||||||
|
return acmeFns.updateCertificateAuthority({
|
||||||
|
id,
|
||||||
|
configuration: configuration as TUpdateAcmeCertificateAuthorityDTO["configuration"],
|
||||||
|
disableDirectIssuance,
|
||||||
|
actor,
|
||||||
|
status
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new BadRequestError({ message: "Invalid certificate authority type" });
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteCertificateAuthority = async ({ id, type }: { id: string; type: CaType }, actor: OrgServiceActor) => {
|
const deleteCertificateAuthority = async ({ id, type }: { id: string; type: CaType }, actor: OrgServiceActor) => {
|
||||||
@@ -276,6 +352,12 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (certificateAuthority.externalCa && certificateAuthority.externalCa.type !== type) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Certificate authority cannot be deleted due to mismatching type"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
await certificateAuthorityDAL.deleteById(id);
|
await certificateAuthorityDAL.deleteById(id);
|
||||||
|
|
||||||
if (type === CaType.INTERNAL) {
|
if (type === CaType.INTERNAL) {
|
||||||
@@ -285,9 +367,20 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
disableDirectIssuance: certificateAuthority.disableDirectIssuance,
|
disableDirectIssuance: certificateAuthority.disableDirectIssuance,
|
||||||
name: certificateAuthority.internalCa?.friendlyName,
|
name: certificateAuthority.internalCa?.friendlyName,
|
||||||
projectId: certificateAuthority.projectId,
|
projectId: certificateAuthority.projectId,
|
||||||
configuration: certificateAuthority.internalCa
|
configuration: certificateAuthority.internalCa,
|
||||||
|
status: certificateAuthority.internalCa?.status
|
||||||
} as TCertificateAuthority;
|
} as TCertificateAuthority;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: certificateAuthority.id,
|
||||||
|
type,
|
||||||
|
disableDirectIssuance: certificateAuthority.disableDirectIssuance,
|
||||||
|
name: certificateAuthority.externalCa?.name,
|
||||||
|
projectId: certificateAuthority.projectId,
|
||||||
|
configuration: certificateAuthority.externalCa?.configuration,
|
||||||
|
status: certificateAuthority.externalCa?.status
|
||||||
|
} as TCertificateAuthority;
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
|
import { TAcmeCertificateAuthority, TAcmeCertificateAuthorityInput } from "./acme/acme-certificate-authority-types";
|
||||||
import { CaType } from "./certificate-authority-enums";
|
import { CaType } from "./certificate-authority-enums";
|
||||||
import {
|
import {
|
||||||
TInternalCertificateAuthority,
|
TInternalCertificateAuthority,
|
||||||
TInternalCertificateAuthorityInput
|
TInternalCertificateAuthorityInput
|
||||||
} from "./internal/internal-certificate-authority-types";
|
} from "./internal/internal-certificate-authority-types";
|
||||||
|
|
||||||
export type TCertificateAuthority = TInternalCertificateAuthority;
|
export type TCertificateAuthority = TInternalCertificateAuthority | TAcmeCertificateAuthority;
|
||||||
|
|
||||||
export type TCertificateAuthorityInput = TInternalCertificateAuthorityInput;
|
export type TCertificateAuthorityInput = TInternalCertificateAuthorityInput | TAcmeCertificateAuthorityInput;
|
||||||
|
|
||||||
export type TCreateCertificateAuthorityDTO = Omit<TCertificateAuthority, "type" | "id"> & {
|
export type TCreateCertificateAuthorityDTO = Omit<TCertificateAuthority, "type" | "id"> & {
|
||||||
type: CaType;
|
type: CaType;
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { TDbClient } from "@app/db";
|
||||||
|
import { TableName } from "@app/db/schemas";
|
||||||
|
import { ormify } from "@app/lib/knex";
|
||||||
|
|
||||||
|
export type TExternalCertificateAuthorityDALFactory = ReturnType<typeof externalCertificateAuthorityDALFactory>;
|
||||||
|
|
||||||
|
export const externalCertificateAuthorityDALFactory = (db: TDbClient) => {
|
||||||
|
const caOrm = ormify(db, TableName.ExternalCertificateAuthority);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...caOrm
|
||||||
|
};
|
||||||
|
};
|
||||||
+6
-1
@@ -13,13 +13,18 @@ import { CaRenewalType, CaStatus, InternalCaType } from "../certificate-authorit
|
|||||||
import { TCertificateAuthoritySecretDALFactory } from "../certificate-authority-secret-dal";
|
import { TCertificateAuthoritySecretDALFactory } from "../certificate-authority-secret-dal";
|
||||||
import {
|
import {
|
||||||
CreateInternalCertificateAuthoritySchema,
|
CreateInternalCertificateAuthoritySchema,
|
||||||
InternalCertificateAuthoritySchema
|
InternalCertificateAuthoritySchema,
|
||||||
|
UpdateInternalCertificateAuthoritySchema
|
||||||
} from "./internal-certificate-authority-schemas";
|
} from "./internal-certificate-authority-schemas";
|
||||||
|
|
||||||
export type TInternalCertificateAuthority = z.infer<typeof InternalCertificateAuthoritySchema>;
|
export type TInternalCertificateAuthority = z.infer<typeof InternalCertificateAuthoritySchema>;
|
||||||
|
|
||||||
export type TInternalCertificateAuthorityInput = z.infer<typeof CreateInternalCertificateAuthoritySchema>;
|
export type TInternalCertificateAuthorityInput = z.infer<typeof CreateInternalCertificateAuthoritySchema>;
|
||||||
|
|
||||||
|
export type TCreateInternalCertificateAuthorityDTO = z.infer<typeof CreateInternalCertificateAuthoritySchema>;
|
||||||
|
|
||||||
|
export type TUpdateInternalCertificateAuthorityDTO = z.infer<typeof UpdateInternalCertificateAuthoritySchema>;
|
||||||
|
|
||||||
export type TCreateCaDTO =
|
export type TCreateCaDTO =
|
||||||
| {
|
| {
|
||||||
isInternal: true;
|
isInternal: true;
|
||||||
|
|||||||
+1
-1
@@ -17,7 +17,7 @@ export const CertificateAuthoritiesPage = () => {
|
|||||||
<div className="mx-auto mb-6 w-full max-w-7xl">
|
<div className="mx-auto mb-6 w-full max-w-7xl">
|
||||||
<PageHeader
|
<PageHeader
|
||||||
title="Certificate Authorities"
|
title="Certificate Authorities"
|
||||||
description="Manage internal private certificate authorities for issuing and signing certificates, including root and intermediate CAs."
|
description="Manage certificate authorities for issuing and signing certificates"
|
||||||
/>
|
/>
|
||||||
<ProjectPermissionCan
|
<ProjectPermissionCan
|
||||||
renderGuardBanner
|
renderGuardBanner
|
||||||
|
|||||||
Reference in New Issue
Block a user