/admin/security mostrava "Nao foi possivel carregar os dados" porque /api/v1/admin/tls respondia 503. A rota so era ligada quando o certificado vinha do watcher do acme.json do Traefik, o unico provider que tinha metodos de estado proprios. Nesta instalacao o TRAEFIK_ACME_DIR nao esta definido e o STATIC_CERT_PATH tambem nao, portanto o servidor cai no certificado autoassinado efemero -- ou seja, o painel calava-se exactamente no estado em que o operador mais precisa de ser avisado. - ProviderStatus deriva o estado de qualquer port.CertProvider, lendo a folha do certificado que ele realmente serve - o painel passa a ser ligado sempre que ha JWT_SECRET - passa a dizer quem emitiu e se e autoassinado, com um aviso proprio: nas portas de correio um autoassinado e recusado por qualquer cliente que verifique, o que pesa mais do que uma validade ainda longe CheckSignatureFrom nao serve para detectar isto: tambem exige que o emissor seja uma CA, coisa que uma folha descartavel nunca declara, e por isso dava a fallback degradada como correctamente emitida. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
97 lines
3.2 KiB
Go
97 lines
3.2 KiB
Go
package tlsprovider
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"time"
|
|
|
|
"lambdamail/protocols/internal/application/port"
|
|
)
|
|
|
|
// ProviderStatus reports on whatever certificate a provider is actually
|
|
// serving, by asking it for one and reading the leaf.
|
|
//
|
|
// The TLS panel used to be wired only when the certificate came from the
|
|
// Traefik acme.json watcher, the single provider that carried status methods
|
|
// of its own. Every other mode answered 503 "the TLS panel needs a certificate
|
|
// watcher" - including the degraded fallback, where the server serves an
|
|
// ephemeral self-signed certificate and the operator most needs to be told.
|
|
type ProviderStatus struct {
|
|
provider port.CertProvider
|
|
}
|
|
|
|
func NewProviderStatus(provider port.CertProvider) *ProviderStatus {
|
|
return &ProviderStatus{provider: provider}
|
|
}
|
|
|
|
// leafFor returns the parsed leaf the provider would serve for a host.
|
|
func (s *ProviderStatus) leafFor(host string) *x509.Certificate {
|
|
if s == nil || s.provider == nil {
|
|
return nil
|
|
}
|
|
cert, err := s.provider.GetCertificate(&tls.ClientHelloInfo{ServerName: host})
|
|
if err != nil || cert == nil {
|
|
return nil
|
|
}
|
|
if cert.Leaf != nil {
|
|
return cert.Leaf
|
|
}
|
|
if len(cert.Certificate) == 0 {
|
|
return nil
|
|
}
|
|
leaf, err := x509.ParseCertificate(cert.Certificate[0])
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
return leaf
|
|
}
|
|
|
|
func (s *ProviderStatus) HasCertificateFor(host string) bool {
|
|
return s.leafFor(host) != nil
|
|
}
|
|
|
|
// EarliestExpiry reports the expiry of the certificate served for the host it
|
|
// was asked about. A single-certificate provider has only one to report.
|
|
func (s *ProviderStatus) EarliestExpiry() (string, time.Time, bool) {
|
|
leaf := s.leafFor("")
|
|
if leaf == nil {
|
|
return "", time.Time{}, false
|
|
}
|
|
host := leaf.Subject.CommonName
|
|
if host == "" && len(leaf.DNSNames) > 0 {
|
|
host = leaf.DNSNames[0]
|
|
}
|
|
return host, leaf.NotAfter, true
|
|
}
|
|
|
|
// LastReload and LastChange are unknown for a provider that loads once. The
|
|
// panel already treats a zero time as "not reported" rather than as an error.
|
|
func (s *ProviderStatus) LastReload() time.Time { return time.Time{} }
|
|
func (s *ProviderStatus) LastChange() time.Time { return time.Time{} }
|
|
|
|
// UnknownSNICount is a watcher-only measure; nothing here counts SNI misses.
|
|
func (s *ProviderStatus) UnknownSNICount() int64 { return 0 }
|
|
|
|
// CertificateSummary names the issuer and says whether the certificate is
|
|
// self-signed, which is the difference between a working mail server and one
|
|
// every verifying client refuses.
|
|
func (s *ProviderStatus) CertificateSummary(host string) (string, bool, bool) {
|
|
leaf := s.leafFor(host)
|
|
if leaf == nil {
|
|
return "", false, false
|
|
}
|
|
issuer := leaf.Issuer.CommonName
|
|
if issuer == "" {
|
|
issuer = leaf.Issuer.String()
|
|
}
|
|
// Equal subject and issuer is what self-signed means on the wire, and
|
|
// verifying the signature with the certificate's own key confirms it.
|
|
// CheckSignatureFrom is the wrong tool: it also demands the issuer be a
|
|
// CA, which a throwaway leaf never claims to be, so it reported the
|
|
// degraded fallback as properly issued.
|
|
selfSigned := bytes.Equal(leaf.RawSubject, leaf.RawIssuer) &&
|
|
leaf.CheckSignature(leaf.SignatureAlgorithm, leaf.RawTBSCertificate, leaf.Signature) == nil
|
|
return issuer, selfSigned, true
|
|
}
|