feat: add create-only root certificate API (#7033)
* feat: add create-only root certificate API [skip ci] * fix: harden root certificate API handling
This commit is contained in:
1 parent
67cff1fbc0
commit
a17845aa1c
8 files changed
+542
-2
No files matched your search
@@ -4,6 +4,7 @@ import { API_KEY_HEADER_NAME } from "@homarr/auth/api-key";
|
||||
|
||||
import { appRouter } from "./router/app";
|
||||
import { boardRouter } from "./router/board";
|
||||
import { ensureRootCertificateProcedure, getCertificateProcedure } from "./router/certificates/certificate-router";
|
||||
import { integrationRequestProcedure } from "./router/integration/integration-request";
|
||||
import { integrationTestStoredConnectionProcedure } from "./router/integration/integration-test-stored-connection";
|
||||
import { infoRouter } from "./router/info";
|
||||
@@ -21,6 +22,10 @@ export const openApiRouter = createTRPCRouter({
|
||||
inviteRouter,
|
||||
serverSettingsRouter,
|
||||
userRouter,
|
||||
certificates: createTRPCRouter({
|
||||
getCertificate: getCertificateProcedure,
|
||||
ensureRootCertificate: ensureRootCertificateProcedure,
|
||||
}),
|
||||
integration: createTRPCRouter({
|
||||
request: integrationRequestProcedure,
|
||||
testConnection: integrationTestStoredConnectionProcedure,
|
||||
|
||||
@@ -5,18 +5,83 @@ import { z } from "zod/v4";
|
||||
|
||||
import {
|
||||
addCustomRootCertificateAsync,
|
||||
ensureCustomRootCertificateAsync,
|
||||
getCustomRootCertificateAsync,
|
||||
removeCustomRootCertificateAsync,
|
||||
RootCertificateError,
|
||||
} from "@homarr/core/infrastructure/certificates";
|
||||
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||
import { and, eq } from "@homarr/db";
|
||||
import { trustedCertificateHostnames } from "@homarr/db/schema";
|
||||
import { certificateValidFileNameSchema, checkCertificateFile } from "@homarr/validation/certificates";
|
||||
import {
|
||||
certificateValidFileNameSchema,
|
||||
checkCertificateFile,
|
||||
rootCertificateFileNameSchema,
|
||||
rootCertificateMetadataSchema,
|
||||
} from "@homarr/validation/certificates";
|
||||
|
||||
import { createTRPCRouter, permissionRequiredProcedure } from "../../trpc";
|
||||
|
||||
const logger = createLogger({ module: "certificateRouter" });
|
||||
|
||||
const withCertificateErrorsAsync = async <T>(operation: () => Promise<T>) => {
|
||||
try {
|
||||
return await operation();
|
||||
} catch (error) {
|
||||
if (error instanceof RootCertificateError) {
|
||||
throw new TRPCError({ code: error.code, message: error.message });
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const getCertificateProcedure = permissionRequiredProcedure
|
||||
.requiresPermission("admin")
|
||||
.meta({
|
||||
openapi: {
|
||||
method: "GET",
|
||||
path: "/api/certificates/{fileName}",
|
||||
tags: ["certificates"],
|
||||
protect: true,
|
||||
summary: "Inspect a named custom root CA certificate",
|
||||
description:
|
||||
"Return public metadata or null when absent. Requires admin permission. Does not return PEM contents.",
|
||||
},
|
||||
})
|
||||
.input(z.object({ fileName: rootCertificateFileNameSchema }))
|
||||
.output(rootCertificateMetadataSchema.nullable())
|
||||
.query(({ input }) => withCertificateErrorsAsync(() => getCustomRootCertificateAsync(input.fileName)));
|
||||
|
||||
export const ensureRootCertificateProcedure = permissionRequiredProcedure
|
||||
.requiresPermission("admin")
|
||||
.meta({
|
||||
openapi: {
|
||||
method: "POST",
|
||||
path: "/api/certificates/root",
|
||||
tags: ["certificates"],
|
||||
protect: true,
|
||||
summary: "Ensure a named custom root CA certificate without overwriting",
|
||||
description:
|
||||
"Accept one currently valid self-signed PEM root CA (64 KiB maximum). Identical bytes are idempotent; different existing bytes return CONFLICT. Requires admin permission.",
|
||||
},
|
||||
})
|
||||
.input(
|
||||
z.object({
|
||||
fileName: rootCertificateFileNameSchema,
|
||||
certificate: z
|
||||
.string()
|
||||
.min(1)
|
||||
.max(64 * 1024),
|
||||
}),
|
||||
)
|
||||
.output(z.object({ created: z.boolean(), certificate: rootCertificateMetadataSchema }))
|
||||
.mutation(({ input }) =>
|
||||
withCertificateErrorsAsync(() => ensureCustomRootCertificateAsync(input.fileName, input.certificate)),
|
||||
);
|
||||
|
||||
export const certificateRouter = createTRPCRouter({
|
||||
getCertificate: getCertificateProcedure,
|
||||
ensureRootCertificate: ensureRootCertificateProcedure,
|
||||
addCertificate: permissionRequiredProcedure
|
||||
.requiresPermission("admin")
|
||||
.input(
|
||||
|
||||
Reference in new issue
Block a user