feat(caddy): add caddy_list_certs whitelist entry and cert parser
- Extend commands.yml.example with caddy_list_certs (sh loop over /data/caddy/certificates/*/*.crt using openssl) - Add lib/parse-caddy.ts: parseCertList() parses CERTFILE/CERTEND delimited openssl output - Add shiki ^1.29.2 dependency for server-side Caddyfile syntax highlighting Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
c12e36e0a4
commit
1c51a0868f
3 changed files with 62 additions and 0 deletions
54
lib/parse-caddy.ts
Normal file
54
lib/parse-caddy.ts
Normal file
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue