Continue cert mgmt mvp

This commit is contained in:
Tuan Dang
2024-05-28 17:00:48 -07:00
parent 26ea949a4e
commit b8516da90f
57 changed files with 3339 additions and 467 deletions
+34 -11
View File
@@ -71,8 +71,11 @@ import { authPaswordServiceFactory } from "@app/services/auth/auth-password-serv
import { authSignupServiceFactory } from "@app/services/auth/auth-signup-service";
import { tokenDALFactory } from "@app/services/auth-token/auth-token-dal";
import { tokenServiceFactory } from "@app/services/auth-token/auth-token-service";
import { certificateCertDALFactory } from "@app/services/certificate/certificate-cert-dal";
import { certificateDALFactory } from "@app/services/certificate/certificate-dal";
import { certificateSecretDALFactory } from "@app/services/certificate/certificate-secret-dal";
import { certificateServiceFactory } from "@app/services/certificate/certificate-service";
import { certificateAuthorityCertDALFactory } from "@app/services/certificate-authority/certificate-authority-cert-dal";
// ca / certs
import { certificateAuthorityDALFactory } from "@app/services/certificate-authority/certificate-authority-dal";
import { certificateAuthorityServiceFactory } from "@app/services/certificate-authority/certificate-authority-service";
import { certificateAuthoritySkDALFactory } from "@app/services/certificate-authority/certificate-authority-sk-dal";
@@ -226,16 +229,6 @@ export const registerRoutes = async (
const trustedIpDAL = trustedIpDALFactory(db);
const telemetryDAL = telemetryDALFactory(db);
const certificateAuthorityDAL = certificateAuthorityDALFactory(db);
const certificateAuthorityCertDAL = certificateAuthorityCertDALFactory(db);
const certificateAuthoritySkDAL = certificateAuthoritySkDALFactory(db);
const certificateAuthorityService = certificateAuthorityServiceFactory({
certificateAuthorityDAL,
certificateAuthorityCertDAL,
certificateAuthoritySkDAL,
projectDAL
});
// ee db layer ops
const permissionDAL = permissionDALFactory(db);
const samlConfigDAL = samlConfigDALFactory(db);
@@ -503,6 +496,34 @@ export const registerRoutes = async (
projectUserMembershipRoleDAL
});
const certificateAuthorityDAL = certificateAuthorityDALFactory(db);
const certificateAuthorityCertDAL = certificateAuthorityCertDALFactory(db);
const certificateAuthoritySkDAL = certificateAuthoritySkDALFactory(db);
const certificateDAL = certificateDALFactory(db);
const certificateCertDAL = certificateCertDALFactory(db);
const certificateSecretDAL = certificateSecretDALFactory(db);
const certificateService = certificateServiceFactory({
certificateDAL,
certificateCertDAL,
certificateSecretDAL,
certificateAuthorityDAL,
certificateAuthorityCertDAL,
permissionService
});
const certificateAuthorityService = certificateAuthorityServiceFactory({
certificateAuthorityDAL,
certificateAuthorityCertDAL,
certificateAuthoritySkDAL,
certificateDAL,
certificateCertDAL,
certificateSecretDAL,
projectDAL,
permissionService
});
const projectService = projectServiceFactory({
permissionService,
projectDAL,
@@ -520,6 +541,7 @@ export const registerRoutes = async (
folderDAL,
licenseService,
certificateAuthorityDAL,
certificateDAL,
projectUserMembershipRoleDAL,
identityProjectMembershipRoleDAL,
keyStore
@@ -842,6 +864,7 @@ export const registerRoutes = async (
ldap: ldapService,
auditLog: auditLogService,
auditLogStream: auditLogStreamService,
certificate: certificateService,
certificateAuthority: certificateAuthorityService,
secretScanning: secretScanningService,
license: licenseService,
@@ -4,7 +4,8 @@ import { CertificateAuthoritiesSchema } from "@app/db/schemas";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
import { CAType } from "@app/services/certificate-authority/certificate-authority-types";
import { CaStatus, CaType } from "@app/services/certificate-authority/certificate-authority-types";
import { validateCaDateField } from "@app/services/certificate-authority/certificate-authority-validators";
export const registerCaRouter = async (server: FastifyZodProvider) => {
server.route({
@@ -16,18 +17,38 @@ export const registerCaRouter = async (server: FastifyZodProvider) => {
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Create CA",
body: z.object({
projectSlug: z.string().trim(),
type: z.enum([CAType.ROOT, CAType.INTERMEDIATE]),
commonName: z.string().trim(),
organization: z.string().trim(),
ou: z.string().trim(),
country: z.string().trim(),
province: z.string().trim(),
locality: z.string().trim()
}),
body: z
.object({
projectSlug: z.string().trim(),
type: z.enum([CaType.ROOT, CaType.INTERMEDIATE]),
commonName: z.string().trim(),
organization: z.string().trim(),
ou: z.string().trim(),
country: z.string().trim(),
province: z.string().trim(),
locality: z.string().trim(),
// format: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date#date_time_string_format
notBefore: validateCaDateField.optional(),
notAfter: validateCaDateField.optional(),
maxPathLength: z.number().min(-1).optional()
})
.refine(
(data) => {
// Check that at least one of the specified fields is non-empty
return [data.commonName, data.organization, data.ou, data.country, data.province, data.locality].some(
(field) => field !== ""
);
},
{
message:
"At least one of the fields commonName, organization, ou, country, province, or locality must be non-empty",
path: []
}
),
response: {
200: CertificateAuthoritiesSchema
200: z.object({
ca: CertificateAuthoritiesSchema
})
}
},
handler: async (req) => {
@@ -38,7 +59,109 @@ export const registerCaRouter = async (server: FastifyZodProvider) => {
actorOrgId: req.permission.orgId,
...req.body
});
return ca;
return {
ca
};
}
});
server.route({
method: "GET",
url: "/:caId",
config: {
rateLimit: readLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Get CA",
params: z.object({
caId: z.string().trim()
}),
response: {
200: z.object({
ca: CertificateAuthoritiesSchema
})
}
},
handler: async (req) => {
const ca = await server.services.certificateAuthority.getCaById({
caId: req.params.caId,
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId
});
return {
ca
};
}
});
server.route({
method: "PATCH",
url: "/:caId",
config: {
rateLimit: readLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Update CA",
params: z.object({
caId: z.string().trim()
}),
body: z.object({
status: z.enum([CaStatus.ACTIVE, CaStatus.DISABLED]).optional()
}),
response: {
200: z.object({
ca: CertificateAuthoritiesSchema
})
}
},
handler: async (req) => {
const ca = await server.services.certificateAuthority.updateCaById({
caId: req.params.caId,
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.body
});
return {
ca
};
}
});
server.route({
method: "DELETE",
url: "/:caId",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Delete CA",
params: z.object({
caId: z.string().trim()
}),
response: {
200: z.object({
ca: CertificateAuthoritiesSchema
})
}
},
handler: async (req) => {
const ca = await server.services.certificateAuthority.deleteCaById({
caId: req.params.caId,
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId
});
return {
ca
};
}
});
@@ -76,7 +199,7 @@ export const registerCaRouter = async (server: FastifyZodProvider) => {
server.route({
method: "GET",
url: ":caId/certificate",
url: "/:caId/certificate",
config: {
rateLimit: readLimit
},
@@ -89,12 +212,13 @@ export const registerCaRouter = async (server: FastifyZodProvider) => {
response: {
200: z.object({
certificate: z.string(),
certificateChain: z.string()
certificateChain: z.string(),
serialNumber: z.string()
})
}
},
handler: async (req) => {
const { certificate, certificateChain } = await server.services.certificateAuthority.getCaCert({
const { certificate, certificateChain, serialNumber } = await server.services.certificateAuthority.getCaCert({
caId: req.params.caId,
actor: req.permission.type,
actorId: req.permission.id,
@@ -103,49 +227,155 @@ export const registerCaRouter = async (server: FastifyZodProvider) => {
});
return {
certificate,
certificateChain
certificateChain,
serialNumber
};
}
});
server.route({
method: "POST",
url: ":caId/issue-certificate",
url: "/:caId/sign-intermediate",
config: {
rateLimit: readLimit
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Issue (leaf) certificate from CA",
description: "Create intermediate CA certificate from parent CA",
params: z.object({
caId: z.string().trim()
}),
body: z.object({
csr: z.string().trim(),
notBefore: z.string().trim(),
notAfter: z.string().trim()
notBefore: validateCaDateField.optional(),
notAfter: validateCaDateField,
maxPathLength: z.number().min(-1).default(-1)
}),
response: {
200: z.object({
certificateId: z.string().trim()
certificate: z.string().trim(),
certificateChain: z.string().trim(),
issuingCaCertificate: z.string().trim(),
serialNumber: z.string().trim()
})
}
},
handler: async () => {
// await server.services.certificateAuthority.issueCertFromCa({
// caId: req.params.caId,
// actor: req.permission.type,
// actorId: req.permission.id,
// actorAuthMethod: req.permission.authMethod,
// actorOrgId: req.permission.orgId,
// ...req.body
// });
handler: async (req) => {
const { certificate, certificateChain, issuingCaCertificate, serialNumber } =
await server.services.certificateAuthority.signIntermediate({
caId: req.params.caId,
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.body
});
return {
certificateId: "111"
certificate,
certificateChain,
issuingCaCertificate,
serialNumber
};
}
});
// TODO 2: logic for creating intermediary ca
// TODO 1: get certificate + certificate chain for the root and intermediary CA GET /ca/:caId/certificate
server.route({
method: "POST",
url: "/:caId/import-certificate",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Import certificate and chain to CA",
params: z.object({
caId: z.string().trim()
}),
body: z.object({
certificate: z.string().trim(),
certificateChain: z.string().trim()
}),
response: {
200: z.object({
message: z.string().trim(),
caId: z.string().trim()
})
}
},
handler: async (req) => {
await server.services.certificateAuthority.importCertToCa({
caId: req.params.caId,
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.body
});
return {
message: "Successfully imported certificate to CA",
caId: req.params.caId
};
}
});
server.route({
method: "POST",
url: "/:caId/issue-certificate",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Issue certificate from CA",
params: z.object({
caId: z.string().trim()
}),
body: z
.object({
commonName: z.string().trim().min(1),
ttl: z.number().int().min(0).optional(),
notBefore: validateCaDateField.optional(),
notAfter: validateCaDateField.optional()
})
.refine(
(data) => {
const { ttl, notAfter } = data;
return (ttl !== undefined && notAfter === undefined) || (ttl === undefined && notAfter !== undefined);
},
{
message: "Either ttl or notAfter must be present, but not both",
path: ["ttl", "notAfter"]
}
),
response: {
200: z.object({
certificate: z.string().trim(),
issuingCaCertificate: z.string().trim(),
certificateChain: z.string().trim(),
privateKey: z.string().trim(),
serialNumber: z.string().trim()
})
}
},
handler: async (req) => {
const { certificate, certificateChain, issuingCaCertificate, privateKey, serialNumber } =
await server.services.certificateAuthority.issueCertFromCa({
caId: req.params.caId,
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.body
});
return {
certificate,
certificateChain,
issuingCaCertificate,
privateKey,
serialNumber
};
}
});
};
@@ -0,0 +1,113 @@
import { z } from "zod";
import { CertificatesSchema } from "@app/db/schemas";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
export const registerCertRouter = async (server: FastifyZodProvider) => {
server.route({
method: "GET",
url: "/:certId",
config: {
rateLimit: readLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Get certificate",
params: z.object({
certId: z.string().trim()
}),
response: {
200: z.object({
certificate: CertificatesSchema
})
}
},
handler: async (req) => {
const certificate = await server.services.certificate.getCertById({
certId: req.params.certId,
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId
});
return {
certificate
};
}
});
server.route({
method: "DELETE",
url: "/:certId",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Delete certificate",
params: z.object({
certId: z.string().trim()
}),
response: {
200: z.object({
certificate: CertificatesSchema
})
}
},
handler: async (req) => {
const certificate = await server.services.certificate.deleteCertById({
certId: req.params.certId,
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId
});
return {
certificate
};
}
});
server.route({
method: "GET",
url: "/:certId/certificate",
config: {
rateLimit: readLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Get certificate of certificate",
params: z.object({
certId: z.string().trim()
}),
response: {
200: z.object({
certificate: z.string().trim(),
certificateChain: z.string().trim(),
issuingCaCertificate: z.string().trim(),
privateKey: z.string().trim(),
serialNumber: z.string().trim()
})
}
},
handler: async (req) => {
const { certificate, certificateChain, issuingCaCertificate, privateKey, serialNumber } =
await server.services.certificate.getCertCert({
certId: req.params.certId,
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId
});
return {
certificate,
certificateChain,
issuingCaCertificate,
privateKey,
serialNumber
};
}
});
};
+3 -6
View File
@@ -2,6 +2,7 @@ import { registerAdminRouter } from "./admin-router";
import { registerAuthRoutes } from "./auth-router";
import { registerProjectBotRouter } from "./bot-router";
import { registerCaRouter } from "./certificate-authority-router";
import { registerCertRouter } from "./certificate-router";
import { registerIdentityAccessTokenRouter } from "./identity-access-token-router";
import { registerIdentityAwsAuthRouter } from "./identity-aws-iam-auth-router";
import { registerIdentityGcpAuthRouter } from "./identity-gcp-auth-router";
@@ -59,12 +60,8 @@ export const registerV1Routes = async (server: FastifyZodProvider) => {
{ prefix: "/workspace" }
);
await server.register(
async (certRouter) => {
await certRouter.register(registerCaRouter);
},
{ prefix: "/ca" }
);
await server.register(registerCaRouter, { prefix: "/ca" });
await server.register(registerCertRouter, { prefix: "/certificates" });
await server.register(registerProjectBotRouter, { prefix: "/bot" });
await server.register(registerIntegrationRouter, { prefix: "/integration" });
+41 -4
View File
@@ -1,13 +1,14 @@
import slugify from "@sindresorhus/slugify";
import { z } from "zod";
import { CertificateAuthoritiesSchema, ProjectKeysSchema, ProjectsSchema } from "@app/db/schemas";
import { CertificateAuthoritiesSchema, CertificatesSchema, ProjectKeysSchema, ProjectsSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { PROJECTS } from "@app/lib/api-docs";
import { creationLimit, readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
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 { ProjectFilterType } from "@app/services/project/project-types";
import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types";
@@ -312,22 +313,58 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
method: "GET",
url: "/:slug/cas",
config: {
rateLimit: writeLimit
rateLimit: readLimit
},
schema: {
params: z.object({
slug: slugSchema.describe("The slug of the project to list CAs.")
}),
querystring: z.object({
status: z.enum([CaStatus.ACTIVE, CaStatus.PENDING_CERTIFICATE]).optional()
}),
response: {
200: z.object({
cas: z.array(CertificateAuthoritiesSchema)
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const cas = await server.services.project.listProjectCas({
filter: {
slug: req.params.slug,
orgId: req.permission.orgId,
type: ProjectFilterType.SLUG
},
status: req.query.status,
actorId: req.permission.id,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
actor: req.permission.type
});
return { cas };
}
});
server.route({
method: "GET",
url: "/:slug/certificates",
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
slug: slugSchema.describe("The slug of the project to list certificates.")
}),
response: {
200: z.object({
certificates: z.array(CertificatesSchema)
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const certificates = await server.services.project.listProjectCertificates({
filter: {
slug: req.params.slug,
orgId: req.permission.orgId,
@@ -338,7 +375,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
actorAuthMethod: req.permission.authMethod,
actor: req.permission.type
});
return { cas };
return { certificates };
}
});
};