mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-12 20:18:52 +00:00
feat(M48): continuous TLS health monitoring — endpoint state machine, shared tlsprobe, 8 API endpoints, GUI
Adds continuous TLS endpoint health monitoring that closes the deploy→verify→monitor loop. After M25 verifies a deployment succeeded once, M48 continuously confirms it stays healthy. Key components: - Shared `internal/tlsprobe/` package extracted from network scanner for reuse - Health status state machine: healthy → degraded (2 failures) → down (5 failures), plus cert_mismatch when served fingerprint differs from expected - 8th scheduler loop (60s tick, per-endpoint configurable intervals) - PostgreSQL migration 000011: endpoint_health_checks + endpoint_health_history tables - 8 REST API endpoints (CRUD, history, acknowledge, summary) - Health Monitor GUI page with summary bar, status table, create modal, auto-refresh - 38 new tests (5 tlsprobe + 11 domain + 10 service + 8 handler + 4 frontend) - All coverage thresholds maintained (service 68%, handler 83%, domain 87%, middleware 63%) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,9 +2,6 @@ package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
@@ -16,6 +13,7 @@ import (
|
||||
|
||||
"github.com/shankar0123/certctl/internal/domain"
|
||||
"github.com/shankar0123/certctl/internal/repository"
|
||||
"github.com/shankar0123/certctl/internal/tlsprobe"
|
||||
)
|
||||
|
||||
// SentinelAgentID is the agent ID used for network-discovered certificates.
|
||||
@@ -469,16 +467,15 @@ func (s *NetworkScanService) probeTLS(ctx context.Context, address string, timeo
|
||||
|
||||
// tlsCertToEntry converts an x509.Certificate from a TLS handshake into a DiscoveredCertEntry.
|
||||
func tlsCertToEntry(cert *x509.Certificate, address string) domain.DiscoveredCertEntry {
|
||||
// Compute SHA-256 fingerprint
|
||||
fingerprintBytes := sha256.Sum256(cert.Raw)
|
||||
fingerprint := fmt.Sprintf("%x", fingerprintBytes)
|
||||
// Compute SHA-256 fingerprint using shared tlsprobe package
|
||||
fingerprint := tlsprobe.CertFingerprint(cert)
|
||||
|
||||
// Encode as PEM
|
||||
pemBlock := &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}
|
||||
pemData := string(pem.EncodeToMemory(pemBlock))
|
||||
|
||||
// Key algorithm and size
|
||||
keyAlg, keySize := tlsCertKeyInfo(cert)
|
||||
// Key algorithm and size using shared tlsprobe package
|
||||
keyAlg, keySize := tlsprobe.CertKeyInfo(cert)
|
||||
|
||||
return domain.DiscoveredCertEntry{
|
||||
FingerprintSHA256: fingerprint,
|
||||
@@ -497,20 +494,3 @@ func tlsCertToEntry(cert *x509.Certificate, address string) domain.DiscoveredCer
|
||||
SourceFormat: "network",
|
||||
}
|
||||
}
|
||||
|
||||
// tlsCertKeyInfo extracts key algorithm name and size from a certificate.
|
||||
func tlsCertKeyInfo(cert *x509.Certificate) (string, int) {
|
||||
switch pub := cert.PublicKey.(type) {
|
||||
case *rsa.PublicKey:
|
||||
return "RSA", pub.N.BitLen()
|
||||
case *ecdsa.PublicKey:
|
||||
return "ECDSA", pub.Curve.Params().BitSize
|
||||
default:
|
||||
switch cert.PublicKeyAlgorithm {
|
||||
case x509.Ed25519:
|
||||
return "Ed25519", 256
|
||||
default:
|
||||
return cert.PublicKeyAlgorithm.String(), 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user