home: tls manager web api

This commit is contained in:
Stanislav Chzhen 2025-03-18 17:53:17 +03:00
parent 85a4de7931
commit e35f742e42
3 changed files with 71 additions and 31 deletions

View file

@ -677,6 +677,8 @@ func run(opts options, clientBuildFS fs.FS, done chan struct{}, sigHdlr *signalH
globalContext.web, err = initWeb(ctx, opts, clientBuildFS, upd, slogLogger, tlsMgr, customURL) globalContext.web, err = initWeb(ctx, opts, clientBuildFS, upd, slogLogger, tlsMgr, customURL)
fatalOnError(err) fatalOnError(err)
tlsMgr.setWebAPI(globalContext.web)
statsDir, querylogDir, err := checkStatsAndQuerylogDirs(&globalContext, config) statsDir, querylogDir, err := checkStatsAndQuerylogDirs(&globalContext, config)
fatalOnError(err) fatalOnError(err)

View file

@ -44,6 +44,12 @@ type tlsManager struct {
// tlsRoots is a pool of root CAs for TLSv1.2. // tlsRoots is a pool of root CAs for TLSv1.2.
tlsRoots *x509.CertPool tlsRoots *x509.CertPool
// webAPI is the web UI and API server.
//
// TODO(s.chzhen): Temporary cyclic dependency due to ongoing refactoring.
// Resolve it.
webAPI *webAPI
// configModified is called when the TLS configuration is changed via an // configModified is called when the TLS configuration is changed via an
// HTTP request. // HTTP request.
configModified func() configModified func()
@ -116,6 +122,14 @@ func newTLSManager(ctx context.Context, conf *tlsManagerConfig) (m *tlsManager,
return m, nil return m, nil
} }
// setWebAPI stores the provided web API. It must be called before
// [tlsManager.start], [tlsManager.reload], or [tlsManager.handleTLSConfigure].
//
// TODO(s.chzhen): Remove it once cyclic dependency is resolved.
func (m *tlsManager) setWebAPI(webAPI *webAPI) {
m.webAPI = webAPI
}
// load reloads the TLS configuration from files or data from the config file. // load reloads the TLS configuration from files or data from the config file.
func (m *tlsManager) load(ctx context.Context) (err error) { func (m *tlsManager) load(ctx context.Context) (err error) {
err = m.loadTLSConf(ctx, &m.conf, m.status) err = m.loadTLSConf(ctx, &m.conf, m.status)
@ -163,7 +177,7 @@ func (m *tlsManager) start(_ context.Context) {
// The background context is used because the TLSConfigChanged wraps context // The background context is used because the TLSConfigChanged wraps context
// with timeout on its own and shuts down the server, which handles current // with timeout on its own and shuts down the server, which handles current
// request. // request.
globalContext.web.tlsConfigChanged(context.Background(), tlsConf) m.webAPI.tlsConfigChanged(context.Background(), tlsConf)
} }
// reload updates the configuration and restarts the TLS manager. // reload updates the configuration and restarts the TLS manager.
@ -215,7 +229,7 @@ func (m *tlsManager) reload(ctx context.Context) {
// The background context is used because the TLSConfigChanged wraps context // The background context is used because the TLSConfigChanged wraps context
// with timeout on its own and shuts down the server, which handles current // with timeout on its own and shuts down the server, which handles current
// request. // request.
globalContext.web.tlsConfigChanged(context.Background(), tlsConf) m.webAPI.tlsConfigChanged(context.Background(), tlsConf)
} }
// reconfigureDNSServer updates the DNS server configuration using the stored // reconfigureDNSServer updates the DNS server configuration using the stored
@ -554,7 +568,7 @@ func (m *tlsManager) handleTLSConfigure(w http.ResponseWriter, r *http.Request)
// same reason. // same reason.
if restartHTTPS { if restartHTTPS {
go func() { go func() {
globalContext.web.tlsConfigChanged(context.Background(), req.tlsConfigSettings) m.webAPI.tlsConfigChanged(context.Background(), req.tlsConfigSettings)
}() }()
} }
} }
@ -756,27 +770,12 @@ func (m *tlsManager) validateCertificates(
) (err error) { ) (err error) {
// Check only the public certificate separately from the key. // Check only the public certificate separately from the key.
if len(certChain) > 0 { if len(certChain) > 0 {
var certs []*x509.Certificate var ok bool
certs, status.ValidCert, err = m.parseCertChain(ctx, certChain) ok, err = m.validateCertificate(ctx, status, certChain, serverName)
if !status.ValidCert { if !ok {
// Don't wrap the error, since it's informative enough as is. // Don't wrap the error, since it's informative enough as is.
return err return err
} }
mainCert := certs[0]
status.Subject = mainCert.Subject.String()
status.Issuer = mainCert.Issuer.String()
status.NotAfter = mainCert.NotAfter
status.NotBefore = mainCert.NotBefore
status.DNSNames = mainCert.DNSNames
if chainErr := m.validateCertChain(ctx, certs, serverName); chainErr != nil {
// Let self-signed certs through and don't return this error to set
// its message into the status.WarningValidation afterwards.
err = chainErr
} else {
status.ValidChain = true
}
} }
// Validate the private key by parsing it. // Validate the private key by parsing it.
@ -804,6 +803,41 @@ func (m *tlsManager) validateCertificates(
return err return err
} }
// validateCertificate processes certificate data. status must not be nil, as
// it is used to accumulate the validation results. Other parameters are
// optional. If ok is true, the returned error, if any, is not critical.
func (m *tlsManager) validateCertificate(
ctx context.Context,
status *tlsConfigStatus,
certChain []byte,
serverName string,
) (ok bool, err error) {
var certs []*x509.Certificate
certs, status.ValidCert, err = m.parseCertChain(ctx, certChain)
if !status.ValidCert {
// Don't wrap the error, since it's informative enough as is.
return false, err
}
mainCert := certs[0]
status.Subject = mainCert.Subject.String()
status.Issuer = mainCert.Issuer.String()
status.NotAfter = mainCert.NotAfter
status.NotBefore = mainCert.NotBefore
status.DNSNames = mainCert.DNSNames
err = m.validateCertChain(ctx, certs, serverName)
if err != nil {
// Let self-signed certs through and don't return this error to set
// its message into the status.WarningValidation afterwards.
return true, err
}
status.ValidChain = true
return true, nil
}
// Key types. // Key types.
const ( const (
keyTypeECDSA = "ECDSA" keyTypeECDSA = "ECDSA"
@ -866,7 +900,10 @@ func unmarshalTLS(r *http.Request) (tlsConfigSettingsExt, error) {
} }
} }
if data.PrivateKey != "" { if data.PrivateKey == "" {
return data, nil
}
var key []byte var key []byte
key, err = base64.StdEncoding.DecodeString(data.PrivateKey) key, err = base64.StdEncoding.DecodeString(data.PrivateKey)
if err != nil { if err != nil {
@ -877,7 +914,6 @@ func unmarshalTLS(r *http.Request) (tlsConfigSettingsExt, error) {
if data.PrivateKeyPath != "" { if data.PrivateKeyPath != "" {
return data, fmt.Errorf("private key data and file can't be set together") return data, fmt.Errorf("private key data and file can't be set together")
} }
}
return data, nil return data, nil
} }

View file

@ -261,6 +261,7 @@ func TestTLSManager_Reload(t *testing.T) {
certDER, key = newCertAndKey(t, snAfter) certDER, key = newCertAndKey(t, snAfter)
writeCertAndKey(t, certDER, certPath, key, keyPath) writeCertAndKey(t, certDER, certPath, key, keyPath)
m.setWebAPI(globalContext.web)
m.reload(ctx) m.reload(ctx)
m.WriteDiskConfig(conf) m.WriteDiskConfig(conf)
@ -511,6 +512,7 @@ func TestTLSManager_HandleTLSConfigure(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
// Reconfigure the TLS manager. // Reconfigure the TLS manager.
m.setWebAPI(globalContext.web)
m.handleTLSConfigure(w, r) m.handleTLSConfigure(w, r)
// The [tlsManager.handleTLSConfigure] method will start the DNS server and // The [tlsManager.handleTLSConfigure] method will start the DNS server and