Files
wx-win-agent/control-plane/tls.go
T
rogee 0d29b828bb
Build web service image / build (push) Successful in 48s
feat: harden control-plane deployment
2026-09-12 11:09:42 +08:00

95 lines
2.8 KiB
Go

package controlplane
import (
"crypto/sha256"
"crypto/tls"
"crypto/x509"
"encoding/hex"
"errors"
"fmt"
"net/http"
"os"
"strings"
)
func newTLSConfig(config ServerConfig) (*tls.Config, error) {
if (config.TLSCertFile == "") != (config.TLSKeyFile == "") {
return nil, errors.New("TLS cert and key must be configured together")
}
if config.TLSCertFile == "" {
if config.MTLSClientCAFile != "" || config.MTLSRequireNodeCert || config.MTLSRevokedCertsFile != "" {
return nil, errors.New("mTLS settings require TLS cert and key")
}
return nil, nil
}
certificate, err := tls.LoadX509KeyPair(config.TLSCertFile, config.TLSKeyFile)
if err != nil {
return nil, fmt.Errorf("load TLS certificate: %w", err)
}
result := &tls.Config{
MinVersion: tls.VersionTLS13,
Certificates: []tls.Certificate{certificate},
}
if config.MTLSClientCAFile == "" {
if config.MTLSRequireNodeCert || config.MTLSRevokedCertsFile != "" {
return nil, errors.New("mTLS client CA is required for node certificates or revocation")
}
return result, nil
}
caBytes, err := os.ReadFile(config.MTLSClientCAFile)
if err != nil {
return nil, fmt.Errorf("read mTLS client CA: %w", err)
}
pool := x509.NewCertPool()
if !pool.AppendCertsFromPEM(caBytes) {
return nil, errors.New("mTLS client CA does not contain a PEM certificate")
}
result.ClientCAs = pool
// Web users may use HTTPS without a client certificate. Node routes enforce
// a certificate separately, so one TLS listener serves both audiences.
result.ClientAuth = tls.VerifyClientCertIfGiven
if config.MTLSRevokedCertsFile != "" {
if _, err := os.Stat(config.MTLSRevokedCertsFile); err != nil {
return nil, fmt.Errorf("check revoked certificate file: %w", err)
}
}
return result, nil
}
func (s *Server) clientCertificateAllowed(r *http.Request) bool {
if r.TLS == nil || len(r.TLS.PeerCertificates) == 0 {
return !s.config.MTLSRequireNodeCert
}
if s.config.MTLSRevokedCertsFile == "" {
return true
}
revoked, err := readRevokedFingerprints(s.config.MTLSRevokedCertsFile)
if err != nil {
return false
}
fingerprint := sha256.Sum256(r.TLS.PeerCertificates[0].Raw)
_, isRevoked := revoked[hex.EncodeToString(fingerprint[:])]
return !isRevoked
}
func readRevokedFingerprints(path string) (map[string]struct{}, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, err
}
result := make(map[string]struct{})
for _, line := range strings.Split(string(data), "\n") {
line = strings.TrimSpace(strings.SplitN(line, "#", 2)[0])
if line == "" {
continue
}
line = strings.ReplaceAll(strings.ToLower(line), ":", "")
decoded, err := hex.DecodeString(line)
if err != nil || len(decoded) != sha256.Size {
return nil, errors.New("revoked certificate list contains an invalid SHA-256 fingerprint")
}
result[line] = struct{}{}
}
return result, nil
}