feat(cert-manager): Vorlagen für Zielsysteme, Modulversion 1.2.0 und Anleitungen

- Sieben Vorlagen (Nginx, Apache ab/vor 2.4.8, Windows/IIS, Nginx Proxy Manager, HAProxy, Tomcat) mit Dateien und Einrichtungszeilen
- Reiter „Vorlagen“ mit ZIP samt Anleitung, Schnipsel und Kopieren
- Modulversion 1.2.0, Modul-Changelog, CHANGELOG und drei Anleitungen

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-10-09 15:57:48 +02:00
parent 65a1dca80f
commit 47b26219b2
19 changed files with 1434 additions and 14 deletions
+23
View File
@@ -6,6 +6,7 @@ import { exportKey, type KeyExportFormat } from './cert-keys';
import { certItemFromDer } from './cert-model';
import { safeBaseName } from './cert-names';
import { writePkcs12 } from './cert-pkcs12';
import { buildTemplate, isTemplateId } from './cert-templates';
import {
type BuildContent,
type BuildFile,
@@ -44,6 +45,7 @@ const FORMATS: Record<BuildContent, readonly string[]> = {
pfx: ['pfx'],
key: ['pkcs8', 'traditional', 'pkcs8-der'],
csr: ['pem', 'der'],
template: ['template'],
};
function parseCertificate(pem: unknown): CertItem {
@@ -172,6 +174,7 @@ export function buildOutput(input: BuildInput): BuildResult {
const base = safeBaseName(input.baseName ?? '', head.baseName);
let files: BuildFile[];
let snippet: string | null = null;
switch (input.content) {
case 'leaf':
if (format === 'der') files = [file(`${base}.cer`, derOf(head), MIME.der)];
@@ -208,6 +211,25 @@ export function buildOutput(input: BuildInput): BuildResult {
files = [file(`${base}.pfx`, der, MIME.pfx)];
break;
}
case 'template': {
if (!isTemplateId(input.template)) certError('invalidInput', 400, 'Unknown template');
// Eine Vorlage ohne passenden Schluessel gibt es nicht (templateNeedsKey); ein falscher Schluessel ist keyMismatch.
if (!input.keyPem || input.keyPem.trim() === '') {
certError('templateNeedsKey', 400, 'The template needs the matching private key');
}
const key = matchingKey(head, input.keyPem);
const built = buildTemplate(input.template, {
head,
shown,
key,
base,
password: input.password,
pfxProfile: input.pfxEncryption === 'modern' ? 'modern' : 'compat',
});
files = built.files;
snippet = built.snippet;
break;
}
default:
return certError('invalidInput', 400, 'Unknown content');
}
@@ -216,6 +238,7 @@ export function buildOutput(input: BuildInput): BuildResult {
files,
chainComplete: chain.complete,
missingIssuerCn: chain.gap?.missingIssuerCn ?? null,
...(input.content === 'template' ? { snippet } : {}),
};
}