diff --git a/lib/parse-caddy.ts b/lib/parse-caddy.ts new file mode 100644 index 0000000..5744308 --- /dev/null +++ b/lib/parse-caddy.ts @@ -0,0 +1,54 @@ +export interface CertInfo { + domain: string + subject: string + issuer: string + issuerCN: string + notBefore: string + notAfter: string + expiringWarning: boolean + expired: boolean +} + +export function parseCertList(output: string): CertInfo[] { + const certs: CertInfo[] = [] + const blocks = output.split('CERTEND') + + for (const block of blocks) { + const fileMatch = block.match(/CERTFILE:(.+)/) + if (!fileMatch) continue + + const filePath = fileMatch[1].trim() + const domain = filePath.split('/').filter(Boolean).pop()?.replace(/\.crt$/, '') ?? '' + + let subject = '' + let issuer = '' + let notBefore = '' + let notAfter = '' + + for (const line of block.split('\n')) { + const subjectMatch = line.match(/^subject=(.+)/) + if (subjectMatch) subject = subjectMatch[1].trim() + + const issuerMatch = line.match(/^issuer=(.+)/) + if (issuerMatch) issuer = issuerMatch[1].trim() + + const notBeforeMatch = line.match(/^notBefore=(.+)/) + if (notBeforeMatch) notBefore = notBeforeMatch[1].trim() + + const notAfterMatch = line.match(/^notAfter=(.+)/) + if (notAfterMatch) notAfter = notAfterMatch[1].trim() + } + + const issuerCN = issuer.match(/CN\s*=\s*([^,]+)/)?.[1]?.trim() ?? issuer + + const now = Date.now() + const notAfterDate = notAfter ? new Date(notAfter) : null + const notAfterMs = notAfterDate?.getTime() ?? Infinity + const expiringWarning = notAfterMs - now < 30 * 24 * 60 * 60 * 1000 + const expired = notAfterMs < now + + certs.push({ domain, subject, issuer, issuerCN, notBefore, notAfter, expiringWarning, expired }) + } + + return certs +} diff --git a/ops-agent/commands.yml.example b/ops-agent/commands.yml.example index 96e2559..1414578 100644 --- a/ops-agent/commands.yml.example +++ b/ops-agent/commands.yml.example @@ -63,3 +63,10 @@ commands: caddy_show_config: cmd: ["caddy", "fmt", "/etc/caddy/Caddyfile"] description: "Print the formatted Caddy config" + + caddy_list_certs: + cmd: + - sh + - -c + - "for f in /data/caddy/certificates/*/*.crt; do [ -f \"$f\" ] || continue; echo \"CERTFILE:$f\"; openssl x509 -noout -subject -issuer -dates -in \"$f\" 2>&1; echo \"CERTEND\"; done" + description: "List TLS cert info (subject, issuer, validity dates) from Caddy certificate store" diff --git a/package.json b/package.json index 268d3c3..a9c9481 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "react": "19.2.4", "react-dom": "19.2.4", "shadcn": "^4.7.0", + "shiki": "^1.29.2", "tailwind-merge": "^3.6.0", "tw-animate-css": "^1.4.0" },