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