- Go Agent (FreeBSD/amd64) fuer OPNsense - Go Backend (Linux/amd64) mit REST API + SQLite - Collector: Hardware, CPU, Memory, Disks, Network, Services, WireGuard, DHCP (KEA/ISC/dnsmasq), Routes, Gateways, Certificates, Plugins, Updates - Persistente Agent-ID - TLS + API-Key Auth - WebSocket-Infrastruktur (WIP): bidirektionaler Command-Kanal, TCP-Tunnel fuer Remote-Zugriff auf Firewall-WebUI - Lastenheft und README
204 lines
5.7 KiB
Go
204 lines
5.7 KiB
Go
package collector
|
|
|
|
import (
|
|
"crypto/x509"
|
|
"encoding/pem"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type Certificate struct {
|
|
Name string `json:"name"`
|
|
Subject string `json:"subject"`
|
|
Issuer string `json:"issuer"`
|
|
NotBefore string `json:"not_before"`
|
|
NotAfter string `json:"not_after"`
|
|
DaysLeft int `json:"days_left"`
|
|
Serial string `json:"serial"`
|
|
SAN string `json:"san,omitempty"`
|
|
KeyUsage string `json:"key_usage,omitempty"`
|
|
IsCA bool `json:"is_ca"`
|
|
Status string `json:"status"` // "valid", "expiring", "expired"
|
|
Source string `json:"source"` // "opnsense", "file"
|
|
}
|
|
|
|
// CollectCertificates liest Zertifikate aus OPNsense config.xml und gaengigen Pfaden
|
|
func CollectCertificates() []Certificate {
|
|
var certs []Certificate
|
|
|
|
// 1. OPNsense config.xml Zertifikate
|
|
certs = append(certs, collectOPNsenseCerts()...)
|
|
|
|
// 2. Zertifikate aus /var/etc/cert/ (aktiv genutzte)
|
|
certs = append(certs, collectCertDir("/var/etc/cert/")...)
|
|
|
|
return certs
|
|
}
|
|
|
|
func collectOPNsenseCerts() []Certificate {
|
|
// configctl: Zertifikate als Liste ausgeben
|
|
// Alternativ: direkt config.xml parsen
|
|
out, err := exec.Command("grep", "-A2", "<crt>", "/conf/config.xml").Output()
|
|
if err != nil || len(out) == 0 {
|
|
return collectFromConfigXML()
|
|
}
|
|
return collectFromConfigXML()
|
|
}
|
|
|
|
func collectFromConfigXML() []Certificate {
|
|
// PHP-basiertes Auslesen der Zertifikate (OPNsense hat php verfuegbar)
|
|
// Skript via stdin uebergeben, da -r bei langen Strings Probleme macht
|
|
script := `<?php
|
|
require_once("config.inc");
|
|
$config = parse_config();
|
|
if (isset($config['cert'])) {
|
|
foreach ($config['cert'] as $cert) {
|
|
$x509 = openssl_x509_parse(base64_decode($cert['crt']));
|
|
if ($x509 === false) continue;
|
|
$name = isset($cert['descr']) ? $cert['descr'] : 'unnamed';
|
|
$subject = isset($x509['subject']['CN']) ? $x509['subject']['CN'] : '';
|
|
$issuer = isset($x509['issuer']['CN']) ? $x509['issuer']['CN'] : '';
|
|
$notBefore = date('Y-m-d H:i:s', $x509['validFrom_time_t']);
|
|
$notAfter = date('Y-m-d H:i:s', $x509['validTo_time_t']);
|
|
$serial = $x509['serialNumberHex'];
|
|
$san = '';
|
|
if (isset($x509['extensions']['subjectAltName'])) {
|
|
$san = $x509['extensions']['subjectAltName'];
|
|
}
|
|
$isCA = isset($x509['extensions']['basicConstraints']) &&
|
|
strpos($x509['extensions']['basicConstraints'], 'CA:TRUE') !== false;
|
|
echo "$name|$subject|$issuer|$notBefore|$notAfter|$serial|$san|" . ($isCA ? "1" : "0") . "\n";
|
|
}
|
|
}
|
|
if (isset($config['ca'])) {
|
|
foreach ($config['ca'] as $ca) {
|
|
$x509 = openssl_x509_parse(base64_decode($ca['crt']));
|
|
if ($x509 === false) continue;
|
|
$name = isset($ca['descr']) ? $ca['descr'] : 'unnamed-ca';
|
|
$subject = isset($x509['subject']['CN']) ? $x509['subject']['CN'] : '';
|
|
$issuer = isset($x509['issuer']['CN']) ? $x509['issuer']['CN'] : '';
|
|
$notBefore = date('Y-m-d H:i:s', $x509['validFrom_time_t']);
|
|
$notAfter = date('Y-m-d H:i:s', $x509['validTo_time_t']);
|
|
$serial = $x509['serialNumberHex'];
|
|
echo "$name|$subject|$issuer|$notBefore|$notAfter|$serial||1\n";
|
|
}
|
|
}
|
|
`
|
|
|
|
cmd := exec.Command("/usr/local/bin/php")
|
|
cmd.Stdin = strings.NewReader(script)
|
|
out, err := cmd.Output()
|
|
if err != nil {
|
|
// Fallback: PEM-Dateien scannen
|
|
return collectCertDir("/var/etc/cert/")
|
|
}
|
|
|
|
var certs []Certificate
|
|
for _, line := range strings.Split(strings.TrimSpace(string(out)), "\n") {
|
|
if line == "" {
|
|
continue
|
|
}
|
|
fields := strings.SplitN(line, "|", 8)
|
|
if len(fields) < 8 {
|
|
continue
|
|
}
|
|
|
|
cert := Certificate{
|
|
Name: fields[0],
|
|
Subject: fields[1],
|
|
Issuer: fields[2],
|
|
NotBefore: fields[3],
|
|
NotAfter: fields[4],
|
|
Serial: fields[5],
|
|
SAN: fields[6],
|
|
IsCA: fields[7] == "1",
|
|
Source: "opnsense",
|
|
}
|
|
|
|
// Status berechnen
|
|
if notAfter, err := time.Parse("2006-01-02 15:04:05", cert.NotAfter); err == nil {
|
|
daysLeft := int(time.Until(notAfter).Hours() / 24)
|
|
cert.DaysLeft = daysLeft
|
|
if daysLeft < 0 {
|
|
cert.Status = "expired"
|
|
} else if daysLeft < 30 {
|
|
cert.Status = "expiring"
|
|
} else {
|
|
cert.Status = "valid"
|
|
}
|
|
}
|
|
|
|
certs = append(certs, cert)
|
|
}
|
|
|
|
return certs
|
|
}
|
|
|
|
func collectCertDir(dir string) []Certificate {
|
|
matches, err := filepath.Glob(filepath.Join(dir, "*.crt"))
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
// Auch .pem Dateien
|
|
pemMatches, _ := filepath.Glob(filepath.Join(dir, "*.pem"))
|
|
matches = append(matches, pemMatches...)
|
|
|
|
var certs []Certificate
|
|
for _, path := range matches {
|
|
out, err := exec.Command("cat", path).Output()
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
block, _ := pem.Decode(out)
|
|
if block == nil || block.Type != "CERTIFICATE" {
|
|
continue
|
|
}
|
|
|
|
x509Cert, err := x509.ParseCertificate(block.Bytes)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
name := filepath.Base(path)
|
|
name = strings.TrimSuffix(name, filepath.Ext(name))
|
|
|
|
daysLeft := int(time.Until(x509Cert.NotAfter).Hours() / 24)
|
|
status := "valid"
|
|
if daysLeft < 0 {
|
|
status = "expired"
|
|
} else if daysLeft < 30 {
|
|
status = "expiring"
|
|
}
|
|
|
|
var sans []string
|
|
for _, dns := range x509Cert.DNSNames {
|
|
sans = append(sans, "DNS:"+dns)
|
|
}
|
|
for _, ip := range x509Cert.IPAddresses {
|
|
sans = append(sans, "IP:"+ip.String())
|
|
}
|
|
|
|
cert := Certificate{
|
|
Name: name,
|
|
Subject: x509Cert.Subject.CommonName,
|
|
Issuer: x509Cert.Issuer.CommonName,
|
|
NotBefore: x509Cert.NotBefore.Format("2006-01-02 15:04:05"),
|
|
NotAfter: x509Cert.NotAfter.Format("2006-01-02 15:04:05"),
|
|
DaysLeft: daysLeft,
|
|
Serial: x509Cert.SerialNumber.Text(16),
|
|
SAN: strings.Join(sans, ", "),
|
|
IsCA: x509Cert.IsCA,
|
|
Status: status,
|
|
Source: "file",
|
|
}
|
|
|
|
certs = append(certs, cert)
|
|
}
|
|
|
|
return certs
|
|
}
|