Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions web/flags_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,25 @@ func TestCheckFlags(t *testing.T) {
},
want: ErrNoListeners,
},
{
name: "both web config file and injected web config set",
flags: &FlagConfig{
WebListenAddresses: &[]string{":9100"},
WebSystemdSocket: OfBool(false),
WebConfigFile: OfString("testdata/web_config_noAuth.good.yml"),
WebConfig: &Config{},
},
want: ErrConflictingFlagsInConfigs,
},
{
name: "only injected web config set",
flags: &FlagConfig{
WebListenAddresses: &[]string{":9100"},
WebSystemdSocket: OfBool(false),
WebConfig: &Config{},
},
want: nil,
},
} {
t.Run(tc.name, func(t *testing.T) {
if err := tc.flags.checkFlags(); !errors.Is(err, tc.want) {
Expand Down
27 changes: 15 additions & 12 deletions web/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,9 @@ var extraHTTPHeaders = map[string][]string{
"Content-Security-Policy": nil,
}

func validateUsers(configPath string) error {
c, err := getConfig(configPath)
if err != nil {
return err
}

func validateUsers(c *Config) error {
for _, p := range c.Users {
_, err = bcrypt.Cost([]byte(p))
_, err := bcrypt.Cost([]byte(p))
if err != nil {
return err
}
Expand Down Expand Up @@ -78,6 +73,7 @@ HeadersLoop:

type webHandler struct {
tlsConfigPath string
config *Config
handler http.Handler
logger *slog.Logger
cache *cache
Expand All @@ -88,11 +84,18 @@ type webHandler struct {
}

func (u *webHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
c, err := getConfig(u.tlsConfigPath)
if err != nil {
u.logger.Error("Unable to parse configuration", "err", err.Error())
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
var c *Config
var err error

if u.config == nil {
c, err = getConfig(u.tlsConfigPath)
if err != nil {
u.logger.Error("Unable to parse configuration", "err", err.Error())
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
} else {
c = u.config
Comment thread
peppi-lotta marked this conversation as resolved.
}

if u.limiter != nil && !u.limiter.Allow() {
Expand Down
145 changes: 107 additions & 38 deletions web/tls_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,10 @@
)

var (
errNoTLSConfig = errors.New("TLS config is not present")
ErrMissingFlag = errors.New("missing required flag configuration")
ErrNoListeners = errors.New("no web listen address or systemd socket flag specified")
errNoTLSConfig = errors.New("TLS config is not present")
ErrMissingFlag = errors.New("missing required flag configuration")
ErrNoListeners = errors.New("no web listen address or systemd socket flag specified")
ErrConflictingFlagsInConfigs = errors.New("conflicting flag configuration in TLS config. Both a config file path and an injected config cannot be provided at the same time.")

Check failure on line 44 in web/tls_config.go

View workflow job for this annotation

GitHub Actions / lint

error-strings: error strings should not be capitalized or end with punctuation or a newline (revive)
)

type Config struct {
Expand Down Expand Up @@ -77,8 +78,19 @@
WebListenAddresses *[]string
// WebSystemdSocket enables systemd socket activation listeners.
WebSystemdSocket *bool
// WebConfigFile points to the TLS and authentication configuration file.
// WebConfigFile is the optional path to the TLS config file. Either this or
// WebConfig must be set.
//
// TLS MinVersion and MaxVersion default to TLS 1.2 and TLS 1.3 when unset.
// HTTP2 is enabled if the HTTPConfig.HTTP2 field is left unset.
WebConfigFile *string
// WebConfig is an optional configuration. Either this or WebConfigFile must be set.
//
// TLS MinVersion and MaxVersion default to TLS 1.2 and TLS 1.3 when unset.
// Other fields are used as provided (in particular HTTP/2 is only enabled
// when HTTPConfig.HTTP2 is set to true, unlike WebConfigFile which enables
// HTTP/2 automatically if left unset).
WebConfig *Config
}

// checkFlags validates that the flag configuration contains the required
Expand All @@ -87,9 +99,14 @@
if c == nil {
return ErrMissingFlag
}
if c.WebConfigFile == nil {
// Either a config file path or an injected config must be provided.
if c.WebConfigFile == nil && c.WebConfig == nil {
return ErrMissingFlag
}
// Both a config file path and an injected config cannot be provided at the same time.
if c.WebConfigFile != nil && c.WebConfig != nil {
return ErrConflictingFlagsInConfigs
}
// Listen addresses are only optional when systemd socket activation is
// actually enabled. Checking that WebSystemdSocket is non-nil is not
// enough: kingpinflag.AddFlags always hands out a non-nil pointer, so a
Expand Down Expand Up @@ -167,9 +184,8 @@
}
c := &Config{
TLSConfig: TLSConfig{
MinVersion: tls.VersionTLS12,
MaxVersion: tls.VersionTLS13,
PreferServerCipherSuites: true,
MinVersion: tls.VersionTLS12,
MaxVersion: tls.VersionTLS13,
},
HTTPConfig: HTTPConfig{HTTP2: true},
}
Expand All @@ -186,6 +202,11 @@
if err != nil {
return nil, err
}

if err := ValidateWebConfig(c); err != nil {
return nil, err
}

return ConfigToTLSConfig(&c.TLSConfig)
}

Expand Down Expand Up @@ -245,11 +266,21 @@
return nil, err
}

// c.PreferServerCipherSuites is deliberately not passed on: the tls.Config
// field of that name has had no effect since Go 1.17.
// Default the TLS versions without mutating the caller's config. An unset
// (zero) MinVersion/MaxVersion means "use the default" rather than crypto/tls'
// own zero-value defaults, so resolve them here into the derived tls.Config.
minVersion := c.MinVersion
if minVersion == 0 {
minVersion = tls.VersionTLS12
}
maxVersion := c.MaxVersion
if maxVersion == 0 {
maxVersion = tls.VersionTLS13
}

cfg := &tls.Config{
MinVersion: (uint16)(c.MinVersion),
MaxVersion: (uint16)(c.MaxVersion),
MinVersion: (uint16)(minVersion),
MaxVersion: (uint16)(maxVersion),
}

cfg.GetCertificate = func(*tls.ClientHelloInfo) (*tls.Certificate, error) {
Expand Down Expand Up @@ -393,13 +424,27 @@
// WebConfigFile in the FlagConfig, TLS or basic auth could be enabled.
func Serve(l net.Listener, server *http.Server, flags *FlagConfig, logger *slog.Logger) error {
logger.Info("Listening on", "address", l.Addr().String())
tlsConfigPath := *flags.WebConfigFile
if tlsConfigPath == "" {
logger.Info("TLS is disabled.", "http2", false, "address", l.Addr().String())
return server.Serve(l)
var c *Config
var err error

// Determine which configuration to use based on the provided flags.
// checkConfigFlags has ensured exactly one of WebConfigFile / WebConfig is set.
if flags.WebConfigFile != nil {
tlsConfigPath := *flags.WebConfigFile
if tlsConfigPath == "" {
logger.Info("TLS is disabled.", "http2", false, "address", l.Addr().String())
return server.Serve(l)
}

c, err = getConfig(tlsConfigPath)
if err != nil {
return err
}
} else {
c = flags.WebConfig
Comment thread
peppi-lotta marked this conversation as resolved.
}

if err := validateUsers(tlsConfigPath); err != nil {
if err := ValidateWebConfig(c); err != nil {
return err
}

Expand All @@ -409,23 +454,18 @@
handler = server.Handler
}

c, err := getConfig(tlsConfigPath)
if err != nil {
return err
}

var limiter *rate.Limiter
if c.RateLimiterConfig.Interval != 0 {
limiter = rate.NewLimiter(rate.Every(c.RateLimiterConfig.Interval), c.RateLimiterConfig.Burst)
logger.Info("Rate Limiter is enabled.", "burst", c.RateLimiterConfig.Burst, "interval", c.RateLimiterConfig.Interval)
}

server.Handler = &webHandler{
tlsConfigPath: tlsConfigPath,
logger: logger,
handler: handler,
cache: newCache(),
limiter: limiter,
config: c,
logger: logger,
handler: handler,
cache: newCache(),
limiter: limiter,
}

config, err := ConfigToTLSConfig(&c.TLSConfig)
Expand All @@ -450,12 +490,31 @@
// Set the GetConfigForClient method of the HTTPS server so that the config
// and certs are reloaded on new connections.
server.TLSConfig.GetConfigForClient = func(*tls.ClientHelloInfo) (*tls.Config, error) {
config, err := getTLSConfig(tlsConfigPath)
if err != nil {
return nil, err
var tlsConfig *tls.Config
var err error
// Determine which configuration to use based on the provided flags.
// checkFlags has ensured exactly one of
// WebConfigFile / WebConfig is set.
if flags.WebConfigFile != nil {
tlsConfigPath := *flags.WebConfigFile

tlsConfig, err = getTLSConfig(tlsConfigPath)
if err != nil {
return nil, err
}
} else {
err = ValidateWebConfig(flags.WebConfig)
if err != nil {
return nil, err
}
// Use the provided config.
tlsConfig, err = ConfigToTLSConfig(&flags.WebConfig.TLSConfig)
if err != nil {
return nil, err
}
}
config.NextProtos = tlsNextProtos(server, c.HTTPConfig.HTTP2)
return config, nil
tlsConfig.NextProtos = tlsNextProtos(server, c.HTTPConfig.HTTP2)
return tlsConfig, nil
}
return server.ServeTLS(l, "", "")
}
Expand Down Expand Up @@ -493,18 +552,28 @@
if tlsConfigPath == "" {
return nil
}
if err := validateUsers(tlsConfigPath); err != nil {
return err
}
c, err := getConfig(tlsConfigPath)
if err != nil {
return err
}
_, err = ConfigToTLSConfig(&c.TLSConfig)
if err == errNoTLSConfig {
return ValidateWebConfig(c)
}

// ValidateWebConfig validates the web configuration, including the TLS config and HTTP headers.
func ValidateWebConfig(config *Config) error {
if config == nil {
return nil
}
return err
if err := validateUsers(config); err != nil {
return err
}
if err := validateHeaderConfig(config.HTTPConfig.Header); err != nil {
return err
}
if _, err := ConfigToTLSConfig(&config.TLSConfig); err != nil && err != errNoTLSConfig {
return err
}
return nil
}

type Cipher uint16
Expand Down
Loading
Loading