1
0
mirror of https://github.com/quay/quay.git synced 2026-10-23 09:49:06 +03:00
Files
quay/internal/server/server.go
Harish Govindarajulu cb863eb428 PROJQUAY-12511: feat(tls): add SSL_PROTOCOLS config support (#6737)
* PROJQUAY-12511: feat(tls): add SSL_PROTOCOLS config support

Wire the SSL_PROTOCOLS config key through to Go's crypto/tls MinVersion.
When set to ["TLSv1.3"], the server rejects TLS 1.2 connections. When
unset or including "TLSv1.2", the existing TLS 1.2 minimum is preserved.

- Add SSLProtocols []string to config.Server struct
- Validate each element: only "TLSv1.2" and "TLSv1.3" accepted
- Update SecureTLSConfig(protocols []string) to set MinVersion
- Thread protocols through server.Config → ensureTLS → SecureTLSConfig
- Remove SSL_PROTOCOLS from knownUnmapped in schema_test.go
- Update installer health check to pass nil (accept any version)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* PROJQUAY-12511: fix(tls): use slices.Contains instead of hand-rolled helper

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-08-05 14:30:10 -05:00

128 lines
2.7 KiB
Go

// Package server provides HTTP server construction and lifecycle management
// for the OCI container registry.
package server
import (
"context"
"errors"
"log/slog"
"net"
"net/http"
"time"
)
const (
schemeHTTPS = "https"
readHeaderTimeout = 10 * time.Second
idleTimeout = 120 * time.Second
shutdownTimeout = 10 * time.Second
)
// Config holds the parameters needed to build an HTTP server.
type Config struct {
ListenAddr string
Hostname string
PreferredScheme string
CertDir string
SSLProtocols []string
}
// Server wraps an *http.Server with registry-specific lifecycle.
type Server struct {
srv *http.Server
useHTTPS bool
certPath string
keyPath string
}
// New creates an HTTP server with the given handler and optional TLS.
func New(ctx context.Context, handler http.Handler, cfg *Config, opts ...Option) (*Server, error) {
var o options
for _, fn := range opts {
fn(&o)
}
mux := http.NewServeMux()
for _, r := range o.extraRoutes {
mux.Handle(r.pattern, r.handler)
}
mux.Handle("/", handler)
var wrapped http.Handler = mux
if o.middleware != nil {
wrapped = o.middleware(mux)
}
srv := &http.Server{
Addr: cfg.ListenAddr,
Handler: wrapped,
ReadHeaderTimeout: readHeaderTimeout,
IdleTimeout: idleTimeout,
BaseContext: func(_ net.Listener) context.Context { return ctx },
}
s := &Server{srv: srv}
if cfg.PreferredScheme == schemeHTTPS {
certPath, keyPath, err := ensureTLS(cfg.Hostname, cfg.CertDir, cfg.SSLProtocols, srv)
if err != nil {
return nil, err
}
s.useHTTPS = true
s.certPath = certPath
s.keyPath = keyPath
}
return s, nil
}
// ListenAndServe starts the server and blocks until the context is canceled
// or the server encounters a fatal error. It handles graceful shutdown.
func (s *Server) ListenAndServe(ctx context.Context) int {
errCh := make(chan error, 1)
go func() {
if s.useHTTPS {
errCh <- s.srv.ListenAndServeTLS(s.certPath, s.keyPath)
} else {
errCh <- s.srv.ListenAndServe()
}
}()
select {
case err := <-errCh:
if !errors.Is(err, http.ErrServerClosed) {
slog.Error("server error", "err", err)
return 1
}
return 0
case <-ctx.Done():
}
slog.Info("shutting down")
shutdownCtx, cancel := context.WithTimeout(context.Background(), shutdownTimeout)
defer cancel()
if err := s.srv.Shutdown(shutdownCtx); err != nil {
slog.Error("shutdown error", "err", err)
return 1
}
slog.Info("stopped")
return 0
}
// Addr returns the configured listen address.
func (s *Server) Addr() string {
return s.srv.Addr
}
// Scheme returns "https" if TLS is enabled, "http" otherwise.
func (s *Server) Scheme() string {
if s.useHTTPS {
return schemeHTTPS
}
return "http"
}