Make the HSTS max-age configurable with HSTS_MAX_AGE (closes #271)
check / check (push) Successful in 13m19s
check / check (push) Successful in 13m19s
HSTS_MAX_AGE, a Go duration, sets the max-age of the Strict-Transport-Security header. Unset, it is ten years (87600h); 0 sends no header at all. A negative value, or one that is not a whole number of seconds, stops startup like an unparseable one. includeSubDomains and preload stay in the header. The value is shown on /settings and in the startup log, and the README's HSTS section now says what each part of the header commits the operator to. Model: opus-5-5
This commit is contained in:
@@ -68,6 +68,10 @@ const (
|
||||
// authenticated activity before it expires.
|
||||
defaultSessionIdleTimeout = 24 * time.Hour
|
||||
|
||||
// defaultHSTSMaxAge is the max-age of the Strict-Transport-Security
|
||||
// header when HSTS_MAX_AGE is unset: ten years.
|
||||
defaultHSTSMaxAge = 87600 * time.Hour
|
||||
|
||||
// defaultReceiverRateLimit is the default number of requests
|
||||
// per minute each client IP may send to a single webhook
|
||||
// receiver entrypoint. Generous for legitimate webhook
|
||||
@@ -120,6 +124,10 @@ var ErrInvalidBindAddress = errors.New("invalid bind address")
|
||||
// the Sentry SDK cannot parse as a DSN.
|
||||
var ErrInvalidSentryDSN = errors.New("invalid Sentry DSN")
|
||||
|
||||
// ErrInvalidHSTSMaxAge is returned when HSTS_MAX_AGE is set to a
|
||||
// negative duration or one that is not a whole number of seconds.
|
||||
var ErrInvalidHSTSMaxAge = errors.New("invalid HSTS max-age")
|
||||
|
||||
// ErrDotEnvUnreadable is returned when the optional .env file exists
|
||||
// but cannot be read or parsed. A file that is not there is not an
|
||||
// error; a file that is there and broken is.
|
||||
@@ -169,6 +177,10 @@ type Config struct {
|
||||
// which a session expires. Non-positive disables idle expiry.
|
||||
SessionIdleTimeout time.Duration
|
||||
|
||||
// HSTSMaxAge is the max-age of the Strict-Transport-Security
|
||||
// header. Zero means the header is not sent at all.
|
||||
HSTSMaxAge time.Duration
|
||||
|
||||
// ReceiverRateLimit is the number of requests per minute each
|
||||
// client IP may send to a single webhook receiver entrypoint.
|
||||
ReceiverRateLimit int
|
||||
@@ -624,6 +636,27 @@ func resolveEnvironment() (string, error) {
|
||||
return environment, nil
|
||||
}
|
||||
|
||||
// resolveHSTSMaxAge reads HSTS_MAX_AGE. Zero is allowed and turns the
|
||||
// Strict-Transport-Security header off. A fraction of a second is an
|
||||
// error rather than rounded, because the header carries whole seconds
|
||||
// and would otherwise say something other than what was set.
|
||||
func resolveHSTSMaxAge() (time.Duration, error) {
|
||||
maxAge, err := envDuration("HSTS_MAX_AGE", defaultHSTSMaxAge)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if maxAge < 0 || maxAge%time.Second != 0 {
|
||||
return 0, fmt.Errorf(
|
||||
"%w: HSTS_MAX_AGE must be zero or a positive whole "+
|
||||
"number of seconds, got %s",
|
||||
ErrInvalidHSTSMaxAge, maxAge,
|
||||
)
|
||||
}
|
||||
|
||||
return maxAge, nil
|
||||
}
|
||||
|
||||
// resolveListener reads the two variables that describe the HTTP
|
||||
// listener: which port it claims and which address it claims it on.
|
||||
// They are read together because neither is meaningful alone, and
|
||||
@@ -684,6 +717,11 @@ func loadFromEnv() (*Config, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hstsMaxAge, err := resolveHSTSMaxAge()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
receiverRateLimit, err := envPositiveInt(
|
||||
"RECEIVER_RATE_LIMIT",
|
||||
defaultReceiverRateLimit,
|
||||
@@ -723,6 +761,7 @@ func loadFromEnv() (*Config, error) {
|
||||
SentryDSN: sentryDSN,
|
||||
RetentionSweepInterval: retentionSweepInterval,
|
||||
SessionIdleTimeout: sessionIdleTimeout,
|
||||
HSTSMaxAge: hstsMaxAge,
|
||||
ReceiverRateLimit: receiverRateLimit,
|
||||
TrustedProxies: trustedProxies,
|
||||
AllowedEgressCIDRs: allowedEgressCIDRs,
|
||||
@@ -807,6 +846,7 @@ func New(lc fx.Lifecycle, params ConfigParams) (*Config, error) {
|
||||
// disables idle expiry entirely, and that is worth showing
|
||||
// back to the operator.
|
||||
"sessionIdleTimeout", s.SessionIdleTimeout.String(),
|
||||
"hstsMaxAge", s.HSTSMaxAge.String(),
|
||||
"receiverRateLimit", s.ReceiverRateLimit,
|
||||
"trustedProxies", len(s.TrustedProxies),
|
||||
"allowedEgressCIDRs", len(s.AllowedEgressCIDRs),
|
||||
|
||||
Reference in New Issue
Block a user