refactor: remove deprecated TLS config fields, forceHSTS, and self-signed cert generation
- commit
- 16594159cbfa0be125b32dc10b5c1cbdf2d8ea45
- author
- brice · · 2026-03-29 16:53 UTC
- parent
- 848492c
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
3 files · +32 -243
M internal/adapters/in/http/middleware/security.go +14 -31
| @@ -1,38 +1,21 @@ | |||
| 1 | 1 | package middleware | |
| 2 | 2 | ||
| 3 | - | import ( | |
| 4 | - | "net/http" | |
| 5 | - | ) | |
| 3 | + | import "net/http" | |
| 6 | 4 | ||
| 7 | - | // SecurityHeaders middleware adds standard security headers to HTTP responses. | |
| 5 | + | // SecurityHeaders adds standard security headers to HTTP responses. | |
| 6 | + | // HSTS is set automatically when the connection uses TLS (r.TLS != nil). | |
| 8 | 7 | func SecurityHeaders(next http.Handler) http.Handler { | |
| 9 | - | return SecurityHeadersWithOptions(false)(next) | |
| 10 | - | } | |
| 11 | - | ||
| 12 | - | // SecurityHeadersWithOptions returns a middleware that adds standard security headers. | |
| 13 | - | // When forceHSTS is true, the HSTS header is always set regardless of TLS state. | |
| 14 | - | // This is needed when Gordon runs behind a TLS-terminating proxy (e.g. Cloudflare) | |
| 15 | - | // where r.TLS is nil but the client connection is always HTTPS. | |
| 16 | - | func SecurityHeadersWithOptions(forceHSTS bool) func(http.Handler) http.Handler { | |
| 17 | - | return func(next http.Handler) http.Handler { | |
| 18 | - | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |
| 19 | - | w.Header().Set("X-Content-Type-Options", "nosniff") | |
| 20 | - | w.Header().Set("X-Frame-Options", "DENY") | |
| 21 | - | w.Header().Set("X-XSS-Protection", "1; mode=block") | |
| 22 | - | w.Header().Set("Referrer-Policy", "strict-origin-when-cross-origin") | |
| 23 | - | w.Header().Set("Permissions-Policy", "geolocation=(), microphone=(), camera=()") | |
| 24 | - | ||
| 25 | - | // Only set when TLS is active or force_hsts is configured, | |
| 26 | - | // since HSTS over plain HTTP would lock out clients. | |
| 27 | - | if r.TLS != nil || forceHSTS { | |
| 28 | - | w.Header().Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains") | |
| 29 | - | } | |
| 8 | + | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |
| 9 | + | w.Header().Set("X-Content-Type-Options", "nosniff") | |
| 10 | + | w.Header().Set("X-Frame-Options", "DENY") | |
| 11 | + | w.Header().Set("X-XSS-Protection", "1; mode=block") | |
| 12 | + | w.Header().Set("Referrer-Policy", "strict-origin-when-cross-origin") | |
| 13 | + | w.Header().Set("Permissions-Policy", "geolocation=(), microphone=(), camera=()") | |
| 30 | 14 | ||
| 31 | - | // CSP is intentionally NOT set here — Gordon proxies arbitrary web apps, | |
| 32 | - | // so a blanket policy would break proxied sites. CSP is set only on | |
| 33 | - | // Gordon-generated error responses (see proxy error handlers). | |
| 15 | + | if r.TLS != nil { | |
| 16 | + | w.Header().Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains") | |
| 17 | + | } | |
| 34 | 18 | ||
| 35 | - | next.ServeHTTP(w, r) | |
| 36 | - | }) | |
| 37 | - | } | |
| 19 | + | next.ServeHTTP(w, r) | |
| 20 | + | }) | |
| 38 | 21 | } | |
| @@ -1,38 +1,21 @@ | |||||
| 1 | package middleware | 1 | package middleware | ||
| 2 | 2 | ||||
| 3 | - | import ( | 3 | + | import "net/http" |
| 4 | - | "net/http" | |||
| 5 | - | ) | |||
| 6 | 4 | ||||
| 7 | - | // SecurityHeaders middleware adds standard security headers to HTTP responses. | 5 | + | // SecurityHeaders adds standard security headers to HTTP responses. |
| 6 | + | // HSTS is set automatically when the connection uses TLS (r.TLS != nil). | |||
| 8 | func SecurityHeaders(next http.Handler) http.Handler { | 7 | func SecurityHeaders(next http.Handler) http.Handler { | ||
| 9 | - | return SecurityHeadersWithOptions(false)(next) | 8 | + | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| 10 | - | } | 9 | + | w.Header().Set("X-Content-Type-Options", "nosniff") |
| 11 | - | 10 | + | w.Header().Set("X-Frame-Options", "DENY") | |
| 12 | - | // SecurityHeadersWithOptions returns a middleware that adds standard security headers. | 11 | + | w.Header().Set("X-XSS-Protection", "1; mode=block") |
| 13 | - | // When forceHSTS is true, the HSTS header is always set regardless of TLS state. | 12 | + | w.Header().Set("Referrer-Policy", "strict-origin-when-cross-origin") |
| 14 | - | // This is needed when Gordon runs behind a TLS-terminating proxy (e.g. Cloudflare) | 13 | + | w.Header().Set("Permissions-Policy", "geolocation=(), microphone=(), camera=()") |
| 15 | - | // where r.TLS is nil but the client connection is always HTTPS. | |||
| 16 | - | func SecurityHeadersWithOptions(forceHSTS bool) func(http.Handler) http.Handler { | |||
| 17 | - | return func(next http.Handler) http.Handler { | |||
| 18 | - | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |||
| 19 | - | w.Header().Set("X-Content-Type-Options", "nosniff") | |||
| 20 | - | w.Header().Set("X-Frame-Options", "DENY") | |||
| 21 | - | w.Header().Set("X-XSS-Protection", "1; mode=block") | |||
| 22 | - | w.Header().Set("Referrer-Policy", "strict-origin-when-cross-origin") | |||
| 23 | - | w.Header().Set("Permissions-Policy", "geolocation=(), microphone=(), camera=()") | |||
| 24 | - | ||||
| 25 | - | // Only set when TLS is active or force_hsts is configured, | |||
| 26 | - | // since HSTS over plain HTTP would lock out clients. | |||
| 27 | - | if r.TLS != nil || forceHSTS { | |||
| 28 | - | w.Header().Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains") | |||
| 29 | - | } | |||
| 30 | 14 | ||||
| 31 | - | // CSP is intentionally NOT set here — Gordon proxies arbitrary web apps, | 15 | + | if r.TLS != nil { |
| 32 | - | // so a blanket policy would break proxied sites. CSP is set only on | 16 | + | w.Header().Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains") |
| 33 | - | // Gordon-generated error responses (see proxy error handlers). | 17 | + | } |
| 34 | 18 | ||||
| 35 | - | next.ServeHTTP(w, r) | 19 | + | next.ServeHTTP(w, r) |
| 36 | - | }) | 20 | + | }) |
| 37 | - | } | |||
| 38 | } | 21 | } | ||
M internal/adapters/in/http/middleware/security_test.go +0 -16
| @@ -51,22 +51,6 @@ func TestSecurityHeaders_HSTS_WithTLS(t *testing.T) { | |||
| 51 | 51 | assert.Equal(t, "max-age=31536000; includeSubDomains", rec.Header().Get("Strict-Transport-Security")) | |
| 52 | 52 | } | |
| 53 | 53 | ||
| 54 | - | func TestSecurityHeaders_HSTS_ForceWithoutTLS(t *testing.T) { | |
| 55 | - | handler := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { | |
| 56 | - | w.WriteHeader(http.StatusOK) | |
| 57 | - | }) | |
| 58 | - | ||
| 59 | - | wrapped := SecurityHeadersWithOptions(true)(handler) | |
| 60 | - | ||
| 61 | - | req := httptest.NewRequest(http.MethodGet, "/", nil) | |
| 62 | - | // r.TLS is nil — no TLS on the connection, but forceHSTS is true | |
| 63 | - | rec := httptest.NewRecorder() | |
| 64 | - | wrapped.ServeHTTP(rec, req) | |
| 65 | - | ||
| 66 | - | assert.Equal(t, "max-age=31536000; includeSubDomains", rec.Header().Get("Strict-Transport-Security"), | |
| 67 | - | "HSTS should be set when forceHSTS is true even without TLS") | |
| 68 | - | } | |
| 69 | - | ||
| 70 | 54 | func TestSecurityHeaders_HSTS_WithForwardedProto_NotTrusted(t *testing.T) { | |
| 71 | 55 | handler := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { | |
| 72 | 56 | w.WriteHeader(http.StatusOK) | |
| @@ -51,22 +51,6 @@ func TestSecurityHeaders_HSTS_WithTLS(t *testing.T) { | |||||
| 51 | assert.Equal(t, "max-age=31536000; includeSubDomains", rec.Header().Get("Strict-Transport-Security")) | 51 | assert.Equal(t, "max-age=31536000; includeSubDomains", rec.Header().Get("Strict-Transport-Security")) | ||
| 52 | } | 52 | } | ||
| 53 | 53 | ||||
| 54 | - | func TestSecurityHeaders_HSTS_ForceWithoutTLS(t *testing.T) { | |||
| 55 | - | handler := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { | |||
| 56 | - | w.WriteHeader(http.StatusOK) | |||
| 57 | - | }) | |||
| 58 | - | ||||
| 59 | - | wrapped := SecurityHeadersWithOptions(true)(handler) | |||
| 60 | - | ||||
| 61 | - | req := httptest.NewRequest(http.MethodGet, "/", nil) | |||
| 62 | - | // r.TLS is nil — no TLS on the connection, but forceHSTS is true | |||
| 63 | - | rec := httptest.NewRecorder() | |||
| 64 | - | wrapped.ServeHTTP(rec, req) | |||
| 65 | - | ||||
| 66 | - | assert.Equal(t, "max-age=31536000; includeSubDomains", rec.Header().Get("Strict-Transport-Security"), | |||
| 67 | - | "HSTS should be set when forceHSTS is true even without TLS") | |||
| 68 | - | } | |||
| 69 | - | ||||
| 70 | func TestSecurityHeaders_HSTS_WithForwardedProto_NotTrusted(t *testing.T) { | 54 | func TestSecurityHeaders_HSTS_WithForwardedProto_NotTrusted(t *testing.T) { | ||
| 71 | handler := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { | 55 | handler := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { | ||
| 72 | w.WriteHeader(http.StatusOK) | 56 | w.WriteHeader(http.StatusOK) | ||
M internal/app/run.go +18 -196
| @@ -3,18 +3,12 @@ package app | |||
| 3 | 3 | ||
| 4 | 4 | import ( | |
| 5 | 5 | "context" | |
| 6 | - | "crypto/ecdsa" | |
| 7 | - | "crypto/elliptic" | |
| 8 | 6 | "crypto/rand" | |
| 9 | 7 | "crypto/tls" | |
| 10 | - | "crypto/x509" | |
| 11 | - | "crypto/x509/pkix" | |
| 12 | 8 | "encoding/hex" | |
| 13 | 9 | "encoding/json" | |
| 14 | - | "encoding/pem" | |
| 15 | 10 | "errors" | |
| 16 | 11 | "fmt" | |
| 17 | - | "math/big" | |
| 18 | 12 | "net" | |
| 19 | 13 | "net/http" | |
| 20 | 14 | "os" | |
| @@ -91,10 +85,7 @@ type Config struct { | |||
| 91 | 85 | RegistryPort int `mapstructure:"registry_port"` | |
| 92 | 86 | GordonDomain string `mapstructure:"gordon_domain"` | |
| 93 | 87 | RegistryDomain string `mapstructure:"registry_domain"` // Deprecated: use gordon_domain | |
| 94 | - | TLSEnabled bool `mapstructure:"tls_enabled"` | |
| 95 | 88 | TLSPort int `mapstructure:"tls_port"` | |
| 96 | - | TLSCertFile string `mapstructure:"tls_cert_file"` | |
| 97 | - | TLSKeyFile string `mapstructure:"tls_key_file"` | |
| 98 | 89 | DataDir string `mapstructure:"data_dir"` | |
| 99 | 90 | MaxProxyBodySize string `mapstructure:"max_proxy_body_size"` // e.g., "512MB", "1GB" | |
| 100 | 91 | MaxBlobChunkSize string `mapstructure:"max_blob_chunk_size"` // e.g., "512MB", "1GB" | |
| @@ -103,7 +94,6 @@ type Config struct { | |||
| 103 | 94 | RegistryAllowedIPs []string `mapstructure:"registry_allowed_ips"` | |
| 104 | 95 | ProxyAllowedIPs []string `mapstructure:"proxy_allowed_ips"` | |
| 105 | 96 | RegistryListenAddr string `mapstructure:"registry_listen_address"` | |
| 106 | - | ForceHSTS bool `mapstructure:"force_hsts"` | |
| 107 | 97 | } `mapstructure:"server"` | |
| 108 | 98 | ||
| 109 | 99 | Logging struct { | |
| @@ -252,8 +242,11 @@ func Run(ctx context.Context, configPath string) error { | |||
| 252 | 242 | ||
| 253 | 243 | log.Info().Msg("Gordon starting") | |
| 254 | 244 | ||
| 255 | - | if err := ensureTLSConfig(&cfg, log); err != nil { | |
| 256 | - | return err | |
| 245 | + | // Deprecation warnings for removed TLS config fields | |
| 246 | + | for _, key := range []string{"server.tls_enabled", "server.tls_cert_file", "server.tls_key_file", "server.force_hsts"} { | |
| 247 | + | if v.IsSet(key) { | |
| 248 | + | log.Warn().Str("key", key).Msg("deprecated config key — Gordon now uses an internal CA with automatic TLS; remove this from your config") | |
| 249 | + | } | |
| 257 | 250 | } | |
| 258 | 251 | ||
| 259 | 252 | // Create PID file | |
| @@ -292,123 +285,6 @@ func Run(ctx context.Context, configPath string) error { | |||
| 292 | 285 | return runServers(ctx, v, cfg, registryHandler, proxyHandler, proxyCIDRMiddleware, svc, cleanupHandlers, log) | |
| 293 | 286 | } | |
| 294 | 287 | ||
| 295 | - | func ensureTLSConfig(cfg *Config, log zerowrap.Logger) error { | |
| 296 | - | if !cfg.Server.TLSEnabled { | |
| 297 | - | return nil | |
| 298 | - | } | |
| 299 | - | ||
| 300 | - | if cfg.Server.TLSCertFile != "" && cfg.Server.TLSKeyFile != "" { | |
| 301 | - | return nil | |
| 302 | - | } | |
| 303 | - | ||
| 304 | - | dataDir := resolveDataDir(cfg.Server.DataDir) | |
| 305 | - | tlsDir := filepath.Join(dataDir, "tls") | |
| 306 | - | certPath := filepath.Join(tlsDir, "cert.pem") | |
| 307 | - | keyPath := filepath.Join(tlsDir, "key.pem") | |
| 308 | - | ||
| 309 | - | domainName := cfg.Server.GordonDomain | |
| 310 | - | if domainName == "" { | |
| 311 | - | domainName = cfg.Server.RegistryDomain | |
| 312 | - | } | |
| 313 | - | if domainName == "" { | |
| 314 | - | domainName = "localhost" | |
| 315 | - | } | |
| 316 | - | ||
| 317 | - | if err := generateSelfSignedTLSCert(certPath, keyPath, domainName); err != nil { | |
| 318 | - | return fmt.Errorf("failed to generate self-signed TLS certificate: %w", err) | |
| 319 | - | } | |
| 320 | - | ||
| 321 | - | cfg.Server.TLSCertFile = certPath | |
| 322 | - | cfg.Server.TLSKeyFile = keyPath | |
| 323 | - | ||
| 324 | - | log.Warn(). | |
| 325 | - | Str("cert_file", certPath). | |
| 326 | - | Str("key_file", keyPath). | |
| 327 | - | Str("domain", domainName). | |
| 328 | - | Msg("server.tls_enabled=true with empty tls_cert_file/tls_key_file; using auto-generated self-signed certificate") | |
| 329 | - | ||
| 330 | - | return nil | |
| 331 | - | } | |
| 332 | - | ||
| 333 | - | func generateSelfSignedTLSCert(certPath, keyPath, domainName string) error { | |
| 334 | - | // Reuse existing pair if already generated. | |
| 335 | - | if _, err := os.Stat(certPath); err == nil { | |
| 336 | - | if _, keyErr := os.Stat(keyPath); keyErr == nil { | |
| 337 | - | return nil | |
| 338 | - | } | |
| 339 | - | } | |
| 340 | - | ||
| 341 | - | if err := os.MkdirAll(filepath.Dir(certPath), 0700); err != nil { | |
| 342 | - | return err | |
| 343 | - | } | |
| 344 | - | if err := os.MkdirAll(filepath.Dir(keyPath), 0700); err != nil { | |
| 345 | - | return err | |
| 346 | - | } | |
| 347 | - | ||
| 348 | - | priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) | |
| 349 | - | if err != nil { | |
| 350 | - | return err | |
| 351 | - | } | |
| 352 | - | ||
| 353 | - | now := time.Now() | |
| 354 | - | serialLimit := new(big.Int).Lsh(big.NewInt(1), 128) | |
| 355 | - | serialNumber, err := rand.Int(rand.Reader, serialLimit) | |
| 356 | - | if err != nil { | |
| 357 | - | return err | |
| 358 | - | } | |
| 359 | - | ||
| 360 | - | template := x509.Certificate{ | |
| 361 | - | SerialNumber: serialNumber, | |
| 362 | - | Subject: pkix.Name{ | |
| 363 | - | CommonName: domainName, | |
| 364 | - | Organization: []string{"Gordon Auto-Generated TLS"}, | |
| 365 | - | }, | |
| 366 | - | NotBefore: now.Add(-5 * time.Minute), | |
| 367 | - | NotAfter: now.AddDate(1, 0, 0), | |
| 368 | - | KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment, | |
| 369 | - | ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, | |
| 370 | - | BasicConstraintsValid: true, | |
| 371 | - | } | |
| 372 | - | ||
| 373 | - | if ip := net.ParseIP(domainName); ip != nil { | |
| 374 | - | template.IPAddresses = []net.IP{ip} | |
| 375 | - | } else { | |
| 376 | - | template.DNSNames = []string{domainName} | |
| 377 | - | } | |
| 378 | - | ||
| 379 | - | derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) | |
| 380 | - | if err != nil { | |
| 381 | - | return err | |
| 382 | - | } | |
| 383 | - | ||
| 384 | - | certOut, err := os.OpenFile(certPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) | |
| 385 | - | if err != nil { | |
| 386 | - | return err | |
| 387 | - | } | |
| 388 | - | defer certOut.Close() | |
| 389 | - | ||
| 390 | - | if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil { | |
| 391 | - | return err | |
| 392 | - | } | |
| 393 | - | ||
| 394 | - | keyBytes, err := x509.MarshalECPrivateKey(priv) | |
| 395 | - | if err != nil { | |
| 396 | - | return err | |
| 397 | - | } | |
| 398 | - | ||
| 399 | - | keyOut, err := os.OpenFile(keyPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) | |
| 400 | - | if err != nil { | |
| 401 | - | return err | |
| 402 | - | } | |
| 403 | - | defer keyOut.Close() | |
| 404 | - | ||
| 405 | - | if err := pem.Encode(keyOut, &pem.Block{Type: "EC PRIVATE KEY", Bytes: keyBytes}); err != nil { | |
| 406 | - | return err | |
| 407 | - | } | |
| 408 | - | ||
| 409 | - | return nil | |
| 410 | - | } | |
| 411 | - | ||
| 412 | 288 | // initConfig loads configuration from file. | |
| 413 | 289 | func initConfig(configPath string) (*viper.Viper, Config, error) { | |
| 414 | 290 | v := viper.New() | |
| @@ -1696,7 +1572,7 @@ func createHTTPHandlers(svc *services, cfg Config, log zerowrap.Logger) (http.Ha | |||
| 1696 | 1572 | proxyMiddlewares := []func(http.Handler) http.Handler{ | |
| 1697 | 1573 | middleware.PanicRecovery(log), | |
| 1698 | 1574 | middleware.RequestLogger(log, trustedNets), | |
| 1699 | - | middleware.SecurityHeadersWithOptions(cfg.Server.ForceHSTS), | |
| 1575 | + | middleware.SecurityHeaders, | |
| 1700 | 1576 | } | |
| 1701 | 1577 | ||
| 1702 | 1578 | // SECURITY: When proxy_allowed_ips is set, only connections from those CIDRs | |
| @@ -1970,7 +1846,7 @@ func runServers(ctx context.Context, v *viper.Viper, cfg Config, registryHandler | |||
| 1970 | 1846 | registryAddr := net.JoinHostPort(cfg.Server.RegistryListenAddr, strconv.Itoa(cfg.Server.RegistryPort)) | |
| 1971 | 1847 | registrySrv, registryReady := startServer(registryAddr, registryHandler, "registry", nil, errChan, log) | |
| 1972 | 1848 | ||
| 1973 | - | proxySrv, proxyReady, tlsSrv, tlsReady, err := startProxyServers(cfg, proxyHandler, proxyCIDRMiddleware, errChan, log) | |
| 1849 | + | proxySrv, proxyReady, _, _, err := startProxyServers(cfg, proxyHandler, proxyCIDRMiddleware, errChan, log) | |
| 1974 | 1850 | if err != nil { | |
| 1975 | 1851 | return err | |
| 1976 | 1852 | } | |
| @@ -1983,19 +1859,11 @@ func runServers(ctx context.Context, v *viper.Viper, cfg Config, registryHandler | |||
| 1983 | 1859 | if err := waitForServerReady(proxyReady, errChan); err != nil { | |
| 1984 | 1860 | return err | |
| 1985 | 1861 | } | |
| 1986 | - | if cfg.Server.TLSEnabled { | |
| 1987 | - | if err := waitForServerReady(tlsReady, errChan); err != nil { | |
| 1988 | - | return err | |
| 1989 | - | } | |
| 1990 | - | } | |
| 1991 | 1862 | ||
| 1992 | - | logEvent := log.Info(). | |
| 1863 | + | log.Info(). | |
| 1993 | 1864 | Int("proxy_port", cfg.Server.Port). | |
| 1994 | - | Int("registry_port", cfg.Server.RegistryPort) | |
| 1995 | - | if cfg.Server.TLSEnabled { | |
| 1996 | - | logEvent = logEvent.Int("tls_port", cfg.Server.TLSPort) | |
| 1997 | - | } | |
| 1998 | - | logEvent.Msg("Gordon is running") | |
| 1865 | + | Int("registry_port", cfg.Server.RegistryPort). | |
| 1866 | + | Msg("Gordon is running") | |
| 1999 | 1867 | ||
| 2000 | 1868 | schedulerCleanup, err := startOptionalSchedulers(ctx, cfg, svc, log, v) | |
| 2001 | 1869 | if err != nil { | |
| @@ -2010,7 +1878,7 @@ func runServers(ctx context.Context, v *viper.Viper, cfg Config, registryHandler | |||
| 2010 | 1878 | ||
| 2011 | 1879 | waitForShutdown(ctx, errChan, reloadChan, deployChan, svc.eventBus, log) | |
| 2012 | 1880 | cleanupHandlers() // Stop debounce timers before draining containers | |
| 2013 | - | gracefulShutdown(registrySrv, proxySrv, tlsSrv, svc.containerSvc, svc.proxySvc, log) | |
| 1881 | + | gracefulShutdown(registrySrv, proxySrv, nil, svc.containerSvc, svc.proxySvc, log) | |
| 2014 | 1882 | return nil | |
| 2015 | 1883 | } | |
| 2016 | 1884 | ||
| @@ -2253,56 +2121,14 @@ func gracefulShutdown(registrySrv, proxySrv, tlsSrv *http.Server, containerSvc * | |||
| 2253 | 2121 | log.Info().Msg("Gordon stopped") | |
| 2254 | 2122 | } | |
| 2255 | 2123 | ||
| 2256 | - | // startProxyServers sets up proxy server(s) depending on TLS configuration. | |
| 2257 | - | // With TLS enabled, the HTTP port becomes a redirect-only server and a separate TLS server handles traffic. | |
| 2258 | - | // Without TLS, a single server handles both HTTP/1.1 and cleartext HTTP/2. | |
| 2259 | - | // proxyCIDRMiddleware, when non-nil, is also applied to the HTTP redirect listener | |
| 2260 | - | // so that proxy_allowed_ips cannot be bypassed via the plain-HTTP port. | |
| 2124 | + | // startProxyServers sets up the proxy HTTP server. | |
| 2125 | + | // Task 9 will add the TLS server back with PKI-based certificates. | |
| 2261 | 2126 | func startProxyServers(cfg Config, proxyHandler http.Handler, proxyCIDRMiddleware func(http.Handler) http.Handler, errChan chan<- error, log zerowrap.Logger) (*http.Server, <-chan struct{}, *http.Server, <-chan struct{}, error) { | |
| 2262 | - | if !cfg.Server.TLSEnabled { | |
| 2263 | - | var proxyProtos http.Protocols | |
| 2264 | - | proxyProtos.SetHTTP1(true) | |
| 2265 | - | proxyProtos.SetUnencryptedHTTP2(true) | |
| 2266 | - | srv, ready := startServer(fmt.Sprintf(":%d", cfg.Server.Port), proxyHandler, "proxy", &proxyProtos, errChan, log) | |
| 2267 | - | return srv, ready, nil, nil, nil | |
| 2268 | - | } | |
| 2269 | - | ||
| 2270 | - | if cfg.Server.TLSCertFile == "" || cfg.Server.TLSKeyFile == "" { | |
| 2271 | - | return nil, nil, nil, nil, fmt.Errorf("server.tls_enabled=true requires both server.tls_cert_file and server.tls_key_file") | |
| 2272 | - | } | |
| 2273 | - | ||
| 2274 | - | tlsPort := cfg.Server.TLSPort | |
| 2275 | - | redirectHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |
| 2276 | - | host := r.Host | |
| 2277 | - | if h, _, err := net.SplitHostPort(host); err == nil { | |
| 2278 | - | host = h | |
| 2279 | - | } else if strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]") { | |
| 2280 | - | host = strings.TrimSuffix(strings.TrimPrefix(host, "["), "]") | |
| 2281 | - | } | |
| 2282 | - | if tlsPort != 443 { | |
| 2283 | - | host = net.JoinHostPort(host, strconv.Itoa(tlsPort)) | |
| 2284 | - | } else if strings.Contains(host, ":") { | |
| 2285 | - | host = "[" + host + "]" | |
| 2286 | - | } | |
| 2287 | - | target := "https://" + host + r.RequestURI | |
| 2288 | - | http.Redirect(w, r, target, http.StatusPermanentRedirect) | |
| 2289 | - | }) | |
| 2290 | - | ||
| 2291 | - | var redirectWithMiddleware http.Handler = redirectHandler | |
| 2292 | - | if proxyCIDRMiddleware != nil { | |
| 2293 | - | redirectWithMiddleware = proxyCIDRMiddleware(redirectHandler) | |
| 2294 | - | } | |
| 2295 | - | proxySrv, proxyReady := startServer(fmt.Sprintf(":%d", cfg.Server.Port), redirectWithMiddleware, "proxy-redirect", nil, errChan, log) | |
| 2296 | - | tlsSrv, tlsReady := startTLSServer( | |
| 2297 | - | fmt.Sprintf(":%d", cfg.Server.TLSPort), | |
| 2298 | - | proxyHandler, | |
| 2299 | - | "proxy-tls", | |
| 2300 | - | cfg.Server.TLSCertFile, | |
| 2301 | - | cfg.Server.TLSKeyFile, | |
| 2302 | - | errChan, | |
| 2303 | - | log, | |
| 2304 | - | ) | |
| 2305 | - | return proxySrv, proxyReady, tlsSrv, tlsReady, nil | |
| 2127 | + | var proxyProtos http.Protocols | |
| 2128 | + | proxyProtos.SetHTTP1(true) | |
| 2129 | + | proxyProtos.SetUnencryptedHTTP2(true) | |
| 2130 | + | srv, ready := startServer(fmt.Sprintf(":%d", cfg.Server.Port), proxyHandler, "proxy", &proxyProtos, errChan, log) | |
| 2131 | + | return srv, ready, nil, nil, nil | |
| 2306 | 2132 | } | |
| 2307 | 2133 | ||
| 2308 | 2134 | // startServer starts an HTTP server, returning the server instance and a channel | |
| @@ -2635,10 +2461,7 @@ func isProcessAlive(pid int) bool { | |||
| 2635 | 2461 | func loadConfig(v *viper.Viper, configPath string) error { | |
| 2636 | 2462 | v.SetDefault("server.port", 80) | |
| 2637 | 2463 | v.SetDefault("server.registry_port", 5000) | |
| 2638 | - | v.SetDefault("server.tls_enabled", false) | |
| 2639 | 2464 | v.SetDefault("server.tls_port", 443) | |
| 2640 | - | v.SetDefault("server.tls_cert_file", "") | |
| 2641 | - | v.SetDefault("server.tls_key_file", "") | |
| 2642 | 2465 | v.SetDefault("server.data_dir", DefaultDataDir()) | |
| 2643 | 2466 | v.SetDefault("server.runtime", "auto") | |
| 2644 | 2467 | v.SetDefault("logging.level", "info") | |
| @@ -2690,7 +2513,6 @@ func loadConfig(v *viper.Viper, configPath string) error { | |||
| 2690 | 2513 | v.SetDefault("server.registry_allowed_ips", []string{}) | |
| 2691 | 2514 | v.SetDefault("server.proxy_allowed_ips", []string{}) | |
| 2692 | 2515 | v.SetDefault("server.registry_listen_address", "") | |
| 2693 | - | v.SetDefault("server.force_hsts", false) | |
| 2694 | 2516 | v.SetDefault("deploy.readiness_delay", "5s") | |
| 2695 | 2517 | v.SetDefault("deploy.readiness_mode", "auto") | |
| 2696 | 2518 | v.SetDefault("deploy.health_timeout", "90s") | |
| @@ -3,18 +3,12 @@ package app | |||||
| 3 | 3 | ||||
| 4 | import ( | 4 | import ( | ||
| 5 | "context" | 5 | "context" | ||
| 6 | - | "crypto/ecdsa" | |||
| 7 | - | "crypto/elliptic" | |||
| 8 | "crypto/rand" | 6 | "crypto/rand" | ||
| 9 | "crypto/tls" | 7 | "crypto/tls" | ||
| 10 | - | "crypto/x509" | |||
| 11 | - | "crypto/x509/pkix" | |||
| 12 | "encoding/hex" | 8 | "encoding/hex" | ||
| 13 | "encoding/json" | 9 | "encoding/json" | ||
| 14 | - | "encoding/pem" | |||
| 15 | "errors" | 10 | "errors" | ||
| 16 | "fmt" | 11 | "fmt" | ||
| 17 | - | "math/big" | |||
| 18 | "net" | 12 | "net" | ||
| 19 | "net/http" | 13 | "net/http" | ||
| 20 | "os" | 14 | "os" | ||
| @@ -91,10 +85,7 @@ type Config struct { | |||||
| 91 | RegistryPort int `mapstructure:"registry_port"` | 85 | RegistryPort int `mapstructure:"registry_port"` | ||
| 92 | GordonDomain string `mapstructure:"gordon_domain"` | 86 | GordonDomain string `mapstructure:"gordon_domain"` | ||
| 93 | RegistryDomain string `mapstructure:"registry_domain"` // Deprecated: use gordon_domain | 87 | RegistryDomain string `mapstructure:"registry_domain"` // Deprecated: use gordon_domain | ||
| 94 | - | TLSEnabled bool `mapstructure:"tls_enabled"` | |||
| 95 | TLSPort int `mapstructure:"tls_port"` | 88 | TLSPort int `mapstructure:"tls_port"` | ||
| 96 | - | TLSCertFile string `mapstructure:"tls_cert_file"` | |||
| 97 | - | TLSKeyFile string `mapstructure:"tls_key_file"` | |||
| 98 | DataDir string `mapstructure:"data_dir"` | 89 | DataDir string `mapstructure:"data_dir"` | ||
| 99 | MaxProxyBodySize string `mapstructure:"max_proxy_body_size"` // e.g., "512MB", "1GB" | 90 | MaxProxyBodySize string `mapstructure:"max_proxy_body_size"` // e.g., "512MB", "1GB" | ||
| 100 | MaxBlobChunkSize string `mapstructure:"max_blob_chunk_size"` // e.g., "512MB", "1GB" | 91 | MaxBlobChunkSize string `mapstructure:"max_blob_chunk_size"` // e.g., "512MB", "1GB" | ||
| @@ -103,7 +94,6 @@ type Config struct { | |||||
| 103 | RegistryAllowedIPs []string `mapstructure:"registry_allowed_ips"` | 94 | RegistryAllowedIPs []string `mapstructure:"registry_allowed_ips"` | ||
| 104 | ProxyAllowedIPs []string `mapstructure:"proxy_allowed_ips"` | 95 | ProxyAllowedIPs []string `mapstructure:"proxy_allowed_ips"` | ||
| 105 | RegistryListenAddr string `mapstructure:"registry_listen_address"` | 96 | RegistryListenAddr string `mapstructure:"registry_listen_address"` | ||
| 106 | - | ForceHSTS bool `mapstructure:"force_hsts"` | |||
| 107 | } `mapstructure:"server"` | 97 | } `mapstructure:"server"` | ||
| 108 | 98 | ||||
| 109 | Logging struct { | 99 | Logging struct { | ||
| @@ -252,8 +242,11 @@ func Run(ctx context.Context, configPath string) error { | |||||
| 252 | 242 | ||||
| 253 | log.Info().Msg("Gordon starting") | 243 | log.Info().Msg("Gordon starting") | ||
| 254 | 244 | ||||
| 255 | - | if err := ensureTLSConfig(&cfg, log); err != nil { | 245 | + | // Deprecation warnings for removed TLS config fields |
| 256 | - | return err | 246 | + | for _, key := range []string{"server.tls_enabled", "server.tls_cert_file", "server.tls_key_file", "server.force_hsts"} { |
| 247 | + | if v.IsSet(key) { | |||
| 248 | + | log.Warn().Str("key", key).Msg("deprecated config key — Gordon now uses an internal CA with automatic TLS; remove this from your config") | |||
| 249 | + | } | |||
| 257 | } | 250 | } | ||
| 258 | 251 | ||||
| 259 | // Create PID file | 252 | // Create PID file | ||
| @@ -292,123 +285,6 @@ func Run(ctx context.Context, configPath string) error { | |||||
| 292 | return runServers(ctx, v, cfg, registryHandler, proxyHandler, proxyCIDRMiddleware, svc, cleanupHandlers, log) | 285 | return runServers(ctx, v, cfg, registryHandler, proxyHandler, proxyCIDRMiddleware, svc, cleanupHandlers, log) | ||
| 293 | } | 286 | } | ||
| 294 | 287 | ||||
| 295 | - | func ensureTLSConfig(cfg *Config, log zerowrap.Logger) error { | |||
| 296 | - | if !cfg.Server.TLSEnabled { | |||
| 297 | - | return nil | |||
| 298 | - | } | |||
| 299 | - | ||||
| 300 | - | if cfg.Server.TLSCertFile != "" && cfg.Server.TLSKeyFile != "" { | |||
| 301 | - | return nil | |||
| 302 | - | } | |||
| 303 | - | ||||
| 304 | - | dataDir := resolveDataDir(cfg.Server.DataDir) | |||
| 305 | - | tlsDir := filepath.Join(dataDir, "tls") | |||
| 306 | - | certPath := filepath.Join(tlsDir, "cert.pem") | |||
| 307 | - | keyPath := filepath.Join(tlsDir, "key.pem") | |||
| 308 | - | ||||
| 309 | - | domainName := cfg.Server.GordonDomain | |||
| 310 | - | if domainName == "" { | |||
| 311 | - | domainName = cfg.Server.RegistryDomain | |||
| 312 | - | } | |||
| 313 | - | if domainName == "" { | |||
| 314 | - | domainName = "localhost" | |||
| 315 | - | } | |||
| 316 | - | ||||
| 317 | - | if err := generateSelfSignedTLSCert(certPath, keyPath, domainName); err != nil { | |||
| 318 | - | return fmt.Errorf("failed to generate self-signed TLS certificate: %w", err) | |||
| 319 | - | } | |||
| 320 | - | ||||
| 321 | - | cfg.Server.TLSCertFile = certPath | |||
| 322 | - | cfg.Server.TLSKeyFile = keyPath | |||
| 323 | - | ||||
| 324 | - | log.Warn(). | |||
| 325 | - | Str("cert_file", certPath). | |||
| 326 | - | Str("key_file", keyPath). | |||
| 327 | - | Str("domain", domainName). | |||
| 328 | - | Msg("server.tls_enabled=true with empty tls_cert_file/tls_key_file; using auto-generated self-signed certificate") | |||
| 329 | - | ||||
| 330 | - | return nil | |||
| 331 | - | } | |||
| 332 | - | ||||
| 333 | - | func generateSelfSignedTLSCert(certPath, keyPath, domainName string) error { | |||
| 334 | - | // Reuse existing pair if already generated. | |||
| 335 | - | if _, err := os.Stat(certPath); err == nil { | |||
| 336 | - | if _, keyErr := os.Stat(keyPath); keyErr == nil { | |||
| 337 | - | return nil | |||
| 338 | - | } | |||
| 339 | - | } | |||
| 340 | - | ||||
| 341 | - | if err := os.MkdirAll(filepath.Dir(certPath), 0700); err != nil { | |||
| 342 | - | return err | |||
| 343 | - | } | |||
| 344 | - | if err := os.MkdirAll(filepath.Dir(keyPath), 0700); err != nil { | |||
| 345 | - | return err | |||
| 346 | - | } | |||
| 347 | - | ||||
| 348 | - | priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) | |||
| 349 | - | if err != nil { | |||
| 350 | - | return err | |||
| 351 | - | } | |||
| 352 | - | ||||
| 353 | - | now := time.Now() | |||
| 354 | - | serialLimit := new(big.Int).Lsh(big.NewInt(1), 128) | |||
| 355 | - | serialNumber, err := rand.Int(rand.Reader, serialLimit) | |||
| 356 | - | if err != nil { | |||
| 357 | - | return err | |||
| 358 | - | } | |||
| 359 | - | ||||
| 360 | - | template := x509.Certificate{ | |||
| 361 | - | SerialNumber: serialNumber, | |||
| 362 | - | Subject: pkix.Name{ | |||
| 363 | - | CommonName: domainName, | |||
| 364 | - | Organization: []string{"Gordon Auto-Generated TLS"}, | |||
| 365 | - | }, | |||
| 366 | - | NotBefore: now.Add(-5 * time.Minute), | |||
| 367 | - | NotAfter: now.AddDate(1, 0, 0), | |||
| 368 | - | KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment, | |||
| 369 | - | ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, | |||
| 370 | - | BasicConstraintsValid: true, | |||
| 371 | - | } | |||
| 372 | - | ||||
| 373 | - | if ip := net.ParseIP(domainName); ip != nil { | |||
| 374 | - | template.IPAddresses = []net.IP{ip} | |||
| 375 | - | } else { | |||
| 376 | - | template.DNSNames = []string{domainName} | |||
| 377 | - | } | |||
| 378 | - | ||||
| 379 | - | derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) | |||
| 380 | - | if err != nil { | |||
| 381 | - | return err | |||
| 382 | - | } | |||
| 383 | - | ||||
| 384 | - | certOut, err := os.OpenFile(certPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) | |||
| 385 | - | if err != nil { | |||
| 386 | - | return err | |||
| 387 | - | } | |||
| 388 | - | defer certOut.Close() | |||
| 389 | - | ||||
| 390 | - | if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil { | |||
| 391 | - | return err | |||
| 392 | - | } | |||
| 393 | - | ||||
| 394 | - | keyBytes, err := x509.MarshalECPrivateKey(priv) | |||
| 395 | - | if err != nil { | |||
| 396 | - | return err | |||
| 397 | - | } | |||
| 398 | - | ||||
| 399 | - | keyOut, err := os.OpenFile(keyPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) | |||
| 400 | - | if err != nil { | |||
| 401 | - | return err | |||
| 402 | - | } | |||
| 403 | - | defer keyOut.Close() | |||
| 404 | - | ||||
| 405 | - | if err := pem.Encode(keyOut, &pem.Block{Type: "EC PRIVATE KEY", Bytes: keyBytes}); err != nil { | |||
| 406 | - | return err | |||
| 407 | - | } | |||
| 408 | - | ||||
| 409 | - | return nil | |||
| 410 | - | } | |||
| 411 | - | ||||
| 412 | // initConfig loads configuration from file. | 288 | // initConfig loads configuration from file. | ||
| 413 | func initConfig(configPath string) (*viper.Viper, Config, error) { | 289 | func initConfig(configPath string) (*viper.Viper, Config, error) { | ||
| 414 | v := viper.New() | 290 | v := viper.New() | ||
| @@ -1696,7 +1572,7 @@ func createHTTPHandlers(svc *services, cfg Config, log zerowrap.Logger) (http.Ha | |||||
| 1696 | proxyMiddlewares := []func(http.Handler) http.Handler{ | 1572 | proxyMiddlewares := []func(http.Handler) http.Handler{ | ||
| 1697 | middleware.PanicRecovery(log), | 1573 | middleware.PanicRecovery(log), | ||
| 1698 | middleware.RequestLogger(log, trustedNets), | 1574 | middleware.RequestLogger(log, trustedNets), | ||
| 1699 | - | middleware.SecurityHeadersWithOptions(cfg.Server.ForceHSTS), | 1575 | + | middleware.SecurityHeaders, |
| 1700 | } | 1576 | } | ||
| 1701 | 1577 | ||||
| 1702 | // SECURITY: When proxy_allowed_ips is set, only connections from those CIDRs | 1578 | // SECURITY: When proxy_allowed_ips is set, only connections from those CIDRs | ||
| @@ -1970,7 +1846,7 @@ func runServers(ctx context.Context, v *viper.Viper, cfg Config, registryHandler | |||||
| 1970 | registryAddr := net.JoinHostPort(cfg.Server.RegistryListenAddr, strconv.Itoa(cfg.Server.RegistryPort)) | 1846 | registryAddr := net.JoinHostPort(cfg.Server.RegistryListenAddr, strconv.Itoa(cfg.Server.RegistryPort)) | ||
| 1971 | registrySrv, registryReady := startServer(registryAddr, registryHandler, "registry", nil, errChan, log) | 1847 | registrySrv, registryReady := startServer(registryAddr, registryHandler, "registry", nil, errChan, log) | ||
| 1972 | 1848 | ||||
| 1973 | - | proxySrv, proxyReady, tlsSrv, tlsReady, err := startProxyServers(cfg, proxyHandler, proxyCIDRMiddleware, errChan, log) | 1849 | + | proxySrv, proxyReady, _, _, err := startProxyServers(cfg, proxyHandler, proxyCIDRMiddleware, errChan, log) |
| 1974 | if err != nil { | 1850 | if err != nil { | ||
| 1975 | return err | 1851 | return err | ||
| 1976 | } | 1852 | } | ||
| @@ -1983,19 +1859,11 @@ func runServers(ctx context.Context, v *viper.Viper, cfg Config, registryHandler | |||||
| 1983 | if err := waitForServerReady(proxyReady, errChan); err != nil { | 1859 | if err := waitForServerReady(proxyReady, errChan); err != nil { | ||
| 1984 | return err | 1860 | return err | ||
| 1985 | } | 1861 | } | ||
| 1986 | - | if cfg.Server.TLSEnabled { | |||
| 1987 | - | if err := waitForServerReady(tlsReady, errChan); err != nil { | |||
| 1988 | - | return err | |||
| 1989 | - | } | |||
| 1990 | - | } | |||
| 1991 | 1862 | ||||
| 1992 | - | logEvent := log.Info(). | 1863 | + | log.Info(). |
| 1993 | Int("proxy_port", cfg.Server.Port). | 1864 | Int("proxy_port", cfg.Server.Port). | ||
| 1994 | - | Int("registry_port", cfg.Server.RegistryPort) | 1865 | + | Int("registry_port", cfg.Server.RegistryPort). |
| 1995 | - | if cfg.Server.TLSEnabled { | 1866 | + | Msg("Gordon is running") |
| 1996 | - | logEvent = logEvent.Int("tls_port", cfg.Server.TLSPort) | |||
| 1997 | - | } | |||
| 1998 | - | logEvent.Msg("Gordon is running") | |||
| 1999 | 1867 | ||||
| 2000 | schedulerCleanup, err := startOptionalSchedulers(ctx, cfg, svc, log, v) | 1868 | schedulerCleanup, err := startOptionalSchedulers(ctx, cfg, svc, log, v) | ||
| 2001 | if err != nil { | 1869 | if err != nil { | ||
| @@ -2010,7 +1878,7 @@ func runServers(ctx context.Context, v *viper.Viper, cfg Config, registryHandler | |||||
| 2010 | 1878 | ||||
| 2011 | waitForShutdown(ctx, errChan, reloadChan, deployChan, svc.eventBus, log) | 1879 | waitForShutdown(ctx, errChan, reloadChan, deployChan, svc.eventBus, log) | ||
| 2012 | cleanupHandlers() // Stop debounce timers before draining containers | 1880 | cleanupHandlers() // Stop debounce timers before draining containers | ||
| 2013 | - | gracefulShutdown(registrySrv, proxySrv, tlsSrv, svc.containerSvc, svc.proxySvc, log) | 1881 | + | gracefulShutdown(registrySrv, proxySrv, nil, svc.containerSvc, svc.proxySvc, log) |
| 2014 | return nil | 1882 | return nil | ||
| 2015 | } | 1883 | } | ||
| 2016 | 1884 | ||||
| @@ -2253,56 +2121,14 @@ func gracefulShutdown(registrySrv, proxySrv, tlsSrv *http.Server, containerSvc * | |||||
| 2253 | log.Info().Msg("Gordon stopped") | 2121 | log.Info().Msg("Gordon stopped") | ||
| 2254 | } | 2122 | } | ||
| 2255 | 2123 | ||||
| 2256 | - | // startProxyServers sets up proxy server(s) depending on TLS configuration. | 2124 | + | // startProxyServers sets up the proxy HTTP server. |
| 2257 | - | // With TLS enabled, the HTTP port becomes a redirect-only server and a separate TLS server handles traffic. | 2125 | + | // Task 9 will add the TLS server back with PKI-based certificates. |
| 2258 | - | // Without TLS, a single server handles both HTTP/1.1 and cleartext HTTP/2. | |||
| 2259 | - | // proxyCIDRMiddleware, when non-nil, is also applied to the HTTP redirect listener | |||
| 2260 | - | // so that proxy_allowed_ips cannot be bypassed via the plain-HTTP port. | |||
| 2261 | func startProxyServers(cfg Config, proxyHandler http.Handler, proxyCIDRMiddleware func(http.Handler) http.Handler, errChan chan<- error, log zerowrap.Logger) (*http.Server, <-chan struct{}, *http.Server, <-chan struct{}, error) { | 2126 | func startProxyServers(cfg Config, proxyHandler http.Handler, proxyCIDRMiddleware func(http.Handler) http.Handler, errChan chan<- error, log zerowrap.Logger) (*http.Server, <-chan struct{}, *http.Server, <-chan struct{}, error) { | ||
| 2262 | - | if !cfg.Server.TLSEnabled { | 2127 | + | var proxyProtos http.Protocols |
| 2263 | - | var proxyProtos http.Protocols | 2128 | + | proxyProtos.SetHTTP1(true) |
| 2264 | - | proxyProtos.SetHTTP1(true) | 2129 | + | proxyProtos.SetUnencryptedHTTP2(true) |
| 2265 | - | proxyProtos.SetUnencryptedHTTP2(true) | 2130 | + | srv, ready := startServer(fmt.Sprintf(":%d", cfg.Server.Port), proxyHandler, "proxy", &proxyProtos, errChan, log) |
| 2266 | - | srv, ready := startServer(fmt.Sprintf(":%d", cfg.Server.Port), proxyHandler, "proxy", &proxyProtos, errChan, log) | 2131 | + | return srv, ready, nil, nil, nil |
| 2267 | - | return srv, ready, nil, nil, nil | |||
| 2268 | - | } | |||
| 2269 | - | ||||
| 2270 | - | if cfg.Server.TLSCertFile == "" || cfg.Server.TLSKeyFile == "" { | |||
| 2271 | - | return nil, nil, nil, nil, fmt.Errorf("server.tls_enabled=true requires both server.tls_cert_file and server.tls_key_file") | |||
| 2272 | - | } | |||
| 2273 | - | ||||
| 2274 | - | tlsPort := cfg.Server.TLSPort | |||
| 2275 | - | redirectHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |||
| 2276 | - | host := r.Host | |||
| 2277 | - | if h, _, err := net.SplitHostPort(host); err == nil { | |||
| 2278 | - | host = h | |||
| 2279 | - | } else if strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]") { | |||
| 2280 | - | host = strings.TrimSuffix(strings.TrimPrefix(host, "["), "]") | |||
| 2281 | - | } | |||
| 2282 | - | if tlsPort != 443 { | |||
| 2283 | - | host = net.JoinHostPort(host, strconv.Itoa(tlsPort)) | |||
| 2284 | - | } else if strings.Contains(host, ":") { | |||
| 2285 | - | host = "[" + host + "]" | |||
| 2286 | - | } | |||
| 2287 | - | target := "https://" + host + r.RequestURI | |||
| 2288 | - | http.Redirect(w, r, target, http.StatusPermanentRedirect) | |||
| 2289 | - | }) | |||
| 2290 | - | ||||
| 2291 | - | var redirectWithMiddleware http.Handler = redirectHandler | |||
| 2292 | - | if proxyCIDRMiddleware != nil { | |||
| 2293 | - | redirectWithMiddleware = proxyCIDRMiddleware(redirectHandler) | |||
| 2294 | - | } | |||
| 2295 | - | proxySrv, proxyReady := startServer(fmt.Sprintf(":%d", cfg.Server.Port), redirectWithMiddleware, "proxy-redirect", nil, errChan, log) | |||
| 2296 | - | tlsSrv, tlsReady := startTLSServer( | |||
| 2297 | - | fmt.Sprintf(":%d", cfg.Server.TLSPort), | |||
| 2298 | - | proxyHandler, | |||
| 2299 | - | "proxy-tls", | |||
| 2300 | - | cfg.Server.TLSCertFile, | |||
| 2301 | - | cfg.Server.TLSKeyFile, | |||
| 2302 | - | errChan, | |||
| 2303 | - | log, | |||
| 2304 | - | ) | |||
| 2305 | - | return proxySrv, proxyReady, tlsSrv, tlsReady, nil | |||
| 2306 | } | 2132 | } | ||
| 2307 | 2133 | ||||
| 2308 | // startServer starts an HTTP server, returning the server instance and a channel | 2134 | // startServer starts an HTTP server, returning the server instance and a channel | ||
| @@ -2635,10 +2461,7 @@ func isProcessAlive(pid int) bool { | |||||
| 2635 | func loadConfig(v *viper.Viper, configPath string) error { | 2461 | func loadConfig(v *viper.Viper, configPath string) error { | ||
| 2636 | v.SetDefault("server.port", 80) | 2462 | v.SetDefault("server.port", 80) | ||
| 2637 | v.SetDefault("server.registry_port", 5000) | 2463 | v.SetDefault("server.registry_port", 5000) | ||
| 2638 | - | v.SetDefault("server.tls_enabled", false) | |||
| 2639 | v.SetDefault("server.tls_port", 443) | 2464 | v.SetDefault("server.tls_port", 443) | ||
| 2640 | - | v.SetDefault("server.tls_cert_file", "") | |||
| 2641 | - | v.SetDefault("server.tls_key_file", "") | |||
| 2642 | v.SetDefault("server.data_dir", DefaultDataDir()) | 2465 | v.SetDefault("server.data_dir", DefaultDataDir()) | ||
| 2643 | v.SetDefault("server.runtime", "auto") | 2466 | v.SetDefault("server.runtime", "auto") | ||
| 2644 | v.SetDefault("logging.level", "info") | 2467 | v.SetDefault("logging.level", "info") | ||
| @@ -2690,7 +2513,6 @@ func loadConfig(v *viper.Viper, configPath string) error { | |||||
| 2690 | v.SetDefault("server.registry_allowed_ips", []string{}) | 2513 | v.SetDefault("server.registry_allowed_ips", []string{}) | ||
| 2691 | v.SetDefault("server.proxy_allowed_ips", []string{}) | 2514 | v.SetDefault("server.proxy_allowed_ips", []string{}) | ||
| 2692 | v.SetDefault("server.registry_listen_address", "") | 2515 | v.SetDefault("server.registry_listen_address", "") | ||
| 2693 | - | v.SetDefault("server.force_hsts", false) | |||
| 2694 | v.SetDefault("deploy.readiness_delay", "5s") | 2516 | v.SetDefault("deploy.readiness_delay", "5s") | ||
| 2695 | v.SetDefault("deploy.readiness_mode", "auto") | 2517 | v.SetDefault("deploy.readiness_mode", "auto") | ||
| 2696 | v.SetDefault("deploy.health_timeout", "90s") | 2518 | v.SetDefault("deploy.health_timeout", "90s") | ||