diff --git a/backend/src/services/acme-cloudflare.ts b/backend/src/services/acme-cloudflare.ts index 1f4dd4b..c0baafb 100644 --- a/backend/src/services/acme-cloudflare.ts +++ b/backend/src/services/acme-cloudflare.ts @@ -228,6 +228,12 @@ async function ensureAcmeAccount(client: acme.Client): Promise { } } +function splitPemCertificates(pem: string): string[] { + return [...pem.matchAll(/-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g)] + .map((match) => match[0].trim()) + .filter(Boolean) +} + export async function issueCertificateWithCloudflareDns(params: { server: Server certName: string @@ -300,9 +306,12 @@ export async function issueCertificateWithCloudflareDns(params: { params.onStep?.("import") const safeBase = params.certName.replace(/[^a-zA-Z0-9._-]+/g, "_") + const chain = splitPemCertificates(certPem) + const leafPem = chain[0] ?? certPem + const chainRest = chain.slice(1) const certFile = `${safeBase}.crt` const keyFile = `${safeBase}.key` - const certRouterFile = await clientRos.uploadTextFile(certFile, certPem) + const certRouterFile = await clientRos.uploadTextFile(certFile, leafPem) const keyRouterFile = await clientRos.uploadTextFile(keyFile, privateKey.toString("utf8")) await clientRos.importCertificate({ fileName: certRouterFile, @@ -316,7 +325,21 @@ export async function issueCertificateWithCloudflareDns(params: { trusted: true, trustStore: trustStoreCsv, }) - await clientRos.applyCertificateToServices(params.certName, effectiveTrustStores) + + for (let index = 0; index < chainRest.length; index += 1) { + const isRoot = index === chainRest.length - 1 + const caName = isRoot ? `root_${params.certName}` : `root_${params.certName}_${index + 1}` + const caFile = `${safeBase}_ca_${index + 1}.crt` + const caRouterFile = await clientRos.uploadTextFile(caFile, chainRest[index]!) + await clientRos.importCertificate({ + fileName: caRouterFile, + name: caName, + trusted: true, + trustStore: trustStoreCsv, + }) + } + + await clientRos.applyCertificateToServices(params.certName) } finally { params.onStep?.("cleanup") for (const item of txtCleanups) { diff --git a/backend/src/services/mikrotik.ts b/backend/src/services/mikrotik.ts index 7b8970e..7696797 100644 --- a/backend/src/services/mikrotik.ts +++ b/backend/src/services/mikrotik.ts @@ -571,21 +571,12 @@ export class MikrotikClient { return this.post("/certificate/import", body, 60_000) } - async applyCertificateToServices( - certName: string, - trustStores: string[] = ["www", "api"], - ): Promise { - const stores = trustStores.length > 0 ? trustStores : ["www", "api"] - const targets: string[] = [] - if (stores.includes("www")) targets.push("www-ssl") - if (stores.includes("api")) targets.push("api-ssl") - if (targets.length === 0) targets.push("www-ssl") - + async applyCertificateToServices(certName: string): Promise { const body = { disabled: "no", certificate: certName, } - for (const serviceName of targets) { + for (const serviceName of ["www-ssl", "api-ssl"]) { await this.patchIpService(serviceName, body) } }