feat: auto-reapply SSL cert to all managed proxies on change
When the SSL certificate is changed in settings, automatically updates all existing NPM proxy hosts managed by Docker Watcher in the background. Clears SSL if cert is removed.
This commit is contained in:
+101
-1
@@ -1,12 +1,15 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/alexei/docker-watcher/internal/crypto"
|
||||
"github.com/alexei/docker-watcher/internal/npm"
|
||||
"github.com/alexei/docker-watcher/internal/store"
|
||||
"github.com/alexei/docker-watcher/internal/webhook"
|
||||
)
|
||||
|
||||
@@ -93,14 +96,22 @@ func (s *Server) updateSettings(w http.ResponseWriter, r *http.Request) {
|
||||
if req.PollingInterval != "" {
|
||||
updated.PollingInterval = req.PollingInterval
|
||||
}
|
||||
if req.SSLCertificateID != nil {
|
||||
sslChanged := false
|
||||
if req.SSLCertificateID != nil && *req.SSLCertificateID != updated.SSLCertificateID {
|
||||
updated.SSLCertificateID = *req.SSLCertificateID
|
||||
sslChanged = true
|
||||
}
|
||||
|
||||
if err := s.store.UpdateSettings(updated); err != nil {
|
||||
respondError(w, http.StatusInternalServerError, "failed to update settings: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// If SSL cert changed, update all existing NPM proxy hosts in the background.
|
||||
if sslChanged {
|
||||
go s.reapplySSLToAllProxies(updated)
|
||||
}
|
||||
|
||||
respondJSON(w, http.StatusOK, map[string]string{"status": "updated"})
|
||||
}
|
||||
|
||||
@@ -204,3 +215,92 @@ func isWildcardCert(cert npm.Certificate) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// reapplySSLToAllProxies updates all existing NPM proxy hosts managed by Docker Watcher
|
||||
// to use the new SSL certificate. Runs in the background after settings change.
|
||||
func (s *Server) reapplySSLToAllProxies(settings store.Settings) {
|
||||
ctx := context.Background()
|
||||
|
||||
npmPassword, err := crypto.Decrypt(s.encKey, settings.NpmPassword)
|
||||
if err != nil {
|
||||
slog.Error("reapply SSL: decrypt npm password", "error", err)
|
||||
return
|
||||
}
|
||||
|
||||
npmClient := npm.New(settings.NpmURL)
|
||||
if err := npmClient.Authenticate(ctx, settings.NpmEmail, npmPassword); err != nil {
|
||||
slog.Error("reapply SSL: authenticate to NPM", "error", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Get all proxy hosts from NPM.
|
||||
hosts, err := npmClient.ListProxyHosts(ctx)
|
||||
if err != nil {
|
||||
slog.Error("reapply SSL: list proxy hosts", "error", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Get all our managed instances to identify which proxy hosts are ours.
|
||||
projects, err := s.store.GetAllProjects()
|
||||
if err != nil {
|
||||
slog.Error("reapply SSL: get projects", "error", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Build a set of NPM proxy IDs that belong to our instances.
|
||||
managedProxyIDs := make(map[int]bool)
|
||||
for _, p := range projects {
|
||||
stages, err := s.store.GetStagesByProjectID(p.ID)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for _, st := range stages {
|
||||
instances, err := s.store.GetInstancesByStageID(st.ID)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for _, inst := range instances {
|
||||
if inst.NpmProxyID > 0 {
|
||||
managedProxyIDs[inst.NpmProxyID] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updated := 0
|
||||
for _, host := range hosts {
|
||||
if !managedProxyIDs[host.ID] {
|
||||
continue
|
||||
}
|
||||
|
||||
config := npm.ProxyHostConfig{
|
||||
DomainNames: host.DomainNames,
|
||||
ForwardScheme: host.ForwardScheme,
|
||||
ForwardHost: host.ForwardHost,
|
||||
ForwardPort: host.ForwardPort,
|
||||
BlockExploits: true,
|
||||
AllowWebsocket: true,
|
||||
HTTP2Support: true,
|
||||
Meta: npm.Meta{},
|
||||
Locations: []any{},
|
||||
}
|
||||
|
||||
if settings.SSLCertificateID > 0 {
|
||||
config.CertificateID = settings.SSLCertificateID
|
||||
config.SSLForced = true
|
||||
config.HSTSEnabled = true
|
||||
} else {
|
||||
config.CertificateID = 0
|
||||
config.SSLForced = false
|
||||
config.HSTSEnabled = false
|
||||
}
|
||||
|
||||
if _, err := npmClient.UpdateProxyHost(ctx, host.ID, config); err != nil {
|
||||
slog.Warn("reapply SSL: update proxy host failed", "host_id", host.ID, "error", err)
|
||||
continue
|
||||
}
|
||||
updated++
|
||||
}
|
||||
|
||||
slog.Info("reapply SSL: completed", "updated", updated, "total_managed", len(managedProxyIDs))
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user