Documentation
¶
Overview ¶
Package tls builds the TLS configuration a server serves with, and rereads the files behind it on an interval so that a rotated certificate is served without a restart.
A consumer does not import crypto/tls. Paths are strings, and the client authentication mode, the protocol versions and the cipher suites are string enums.
The zero Config serves plaintext, which lets a consumer wire TLS unconditionally and leave the decision to configuration. Config.Enabled is never inferred from the presence of certificate material: a configuration that carries material without being enabled is reported, and serves plaintext.
Manager implements app.Component, so an app.App starts and shuts it down alongside everything else. One Manager can be handed to several servers, which then serve the same material.
Basic usage ¶
mux := http.NewServeMux()
a, err := app.New(ctx)
if err != nil { log.Fatal(err) }
manager, err := tls.NewWithConfig(&tls.Config{
Name: "api",
Enabled: true,
CertFile: "/secrets/tls/tls.crt",
KeyFile: "/secrets/tls/tls.key",
Logger: a.Logger(),
})
if err != nil { log.Fatal(err) }
srv := httpserver.NewWithConfig(&httpserver.Config{
Addr: ":8443",
Handler: mux,
TLSManager: manager,
})
a.Register(manager)
a.Register(srv)
if err := a.Start(ctx); err != nil { log.Fatal(err) }
defer a.Shutdown(ctx) //nolint:errcheck
Verifying client certificates ¶
Config.ClientAuth selects what the server asks of a client, from least to most strict: ClientAuthOff neither requests nor accepts a certificate, ClientAuthOptionalNoCA requests one and admits whatever is presented without verifying it, ClientAuthOptional verifies a certificate when one is presented and admits a client that presents none, and ClientAuthOn requires every client to present one that verifies.
An unset mode is resolved from whether Config.ClientCAFiles names an authority. With none it resolves to ClientAuthOff. With one it resolves to ClientAuthOptional rather than ClientAuthOn, so that adding an authority cannot by itself start rejecting clients; moving to ClientAuthOn is a separate, deliberate change. A mode that verifies without an authority to verify against is refused with ErrMissingClientCA, because crypto/tls would otherwise fall back to the host's trust store and admit a client certificate from any public authority.
Reloading material ¶
Manager.Start begins rereading the configured files every Config.ReloadInterval, which defaults to one minute. A reread that cannot read the key pair changes nothing: the material already in use stays in place and the server goes on serving what it last read, so a secret caught half written does not interrupt service.
The certificate authorities are treated more leniently. An unreadable file or an unusable entry is skipped, and only a set that yields no certificate between them fails the reread.
A rotated serving certificate reaches a configuration a server already holds, because the certificate is read as each handshake arrives.
The client certificate authorities do not yet follow it: they are the ones loaded when Manager.TLSConfig was called, so changing trust roots currently takes a restart.
Metrics ¶
Setting Config.Registerer publishes the expiry of the serving certificate and a count of rereads by outcome, each labelled with Config.Name:
manager, err := tls.NewWithConfig(&tls.Config{
Name: "api",
Enabled: true,
CertFile: "/secrets/tls/tls.crt",
KeyFile: "/secrets/tls/tls.key",
Registerer: a.Metrics().Registerer(),
})
Each configuration registered against one registerer needs a distinct Config.Name, since the name is what tells the series apart.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ErrAlreadyStarted = errors.New("tls manager already started")
ErrAlreadyStarted is returned by Manager.Start for a Manager that is already rereading its files. A Manager is started once and is not restarted.
var ErrIncompleteKeyPair = errors.New("incomplete tls key pair")
ErrIncompleteKeyPair is returned when a serving certificate is configured without its private key, or a private key without its certificate.
var ErrInvalidCipherSuite = errors.New("invalid tls cipher suite")
ErrInvalidCipherSuite is returned when a cipher suite is configured that this package cannot serve.
var ErrInvalidClientAuth = errors.New("invalid tls client authentication")
ErrInvalidClientAuth is returned when client authentication is configured in a way this package cannot serve.
var ErrInvalidPEM = errors.New("invalid tls pem material")
ErrInvalidPEM is returned when a file that should hold PEM-encoded TLS material cannot be parsed, holds the wrong kind of material, or holds a certificate and a private key that do not belong together.
var ErrInvalidReloadInterval = errors.New("invalid tls reload interval")
ErrInvalidReloadInterval is returned when the interval between rereads of the configured files is negative, which names no schedule.
var ErrInvalidVersion = errors.New("invalid tls version")
ErrInvalidVersion is returned when the configured TLS protocol versions cannot be served.
var ErrMissingClientCA = errors.New("no client certificate authority")
ErrMissingClientCA is returned when a client authentication mode that verifies certificates has no authority to verify against, whether because none was configured or because none of the configured files yielded one. A nil crypto/tls ClientCAs falls back to the host's trust store, which would admit any clientAuth certificate from any public authority.
var ErrNoCertificate = errors.New("no tls serving certificate configured")
ErrNoCertificate is returned when TLS is enabled but no serving certificate is configured.
var ErrNoCipherSuite = errors.New("no usable tls cipher suite configured")
ErrNoCipherSuite is returned when no configured cipher suite can serve a protocol version the server negotiates, which would fail every handshake at that version.
Functions ¶
This section is empty.
Types ¶
type CipherSuite ¶
type CipherSuite string
CipherSuite names a TLS cipher suite, spelled as crypto/tls spells it.
func (*CipherSuite) UnmarshalText ¶
func (s *CipherSuite) UnmarshalText(text []byte) error
UnmarshalText implements encoding.TextUnmarshaler, so a CipherSuite can be decoded straight out of JSON, YAML or an environment variable without the consumer writing an adapter. The receiver is left unchanged when the input does not parse.
type ClientAuthMode ¶
type ClientAuthMode string
ClientAuthMode selects how a server treats a client certificate. The values are nginx's ssl_verify_client vocabulary, so a Helm chart value and a LabKit value are the same string.
const ( // ClientAuthUnset leaves the mode to be resolved from the rest of the // configuration. It is the zero value. ClientAuthUnset ClientAuthMode = "" // ClientAuthOff neither requests nor accepts a client certificate. ClientAuthOff ClientAuthMode = "off" // ClientAuthOptionalNoCA requests a client certificate and admits the // client whatever it presents, without verifying it. The certificate is // available to the application, which takes on the whole of the // verification burden. ClientAuthOptionalNoCA ClientAuthMode = "optional_no_ca" // ClientAuthOptional verifies a client certificate against the configured // certificate authorities when one is presented, and admits a client that // presents none. ClientAuthOptional ClientAuthMode = "optional" // ClientAuthOn requires every client to present a certificate that // verifies against the configured certificate authorities. ClientAuthOn ClientAuthMode = "on" )
func (ClientAuthMode) String ¶
func (m ClientAuthMode) String() string
String implements fmt.Stringer. The zero value renders as "unset" so it is distinguishable in a log line.
func (*ClientAuthMode) UnmarshalText ¶
func (m *ClientAuthMode) UnmarshalText(text []byte) error
UnmarshalText implements encoding.TextUnmarshaler, so a ClientAuthMode can be decoded straight out of JSON, YAML or an environment variable without the consumer writing an adapter. The receiver is left unchanged when the input does not parse.
type Config ¶
type Config struct {
// Name identifies this configuration in errors and log lines. Defaults to
// "tls". Give distinct names to distinct listeners.
Name string
// Enabled turns TLS on. It defaults to false and is never inferred from
// the presence of certificate material, mirroring tls.enabled in the
// GitLab Helm charts. A disabled configuration that carries material is
// reported, and serves plaintext.
Enabled bool
// CertFile is the path to the PEM-encoded serving certificate. It may hold
// a chain, leaf first, when clients need intermediates to reach a trusted
// root. In a Kubernetes deployment it is the tls.crt entry of a
// kubernetes.io/tls secret, mounted as a file.
//
// The certificate must carry the names its clients dial as subject
// alternative names; a common name alone is not accepted.
//
// The path is trimmed, and a blank value counts as absent.
CertFile string
// KeyFile is the path to the PEM-encoded private key for CertFile. In a
// Kubernetes deployment it is the tls.key entry of the same secret.
//
// The path is trimmed, and a blank value counts as absent.
KeyFile string
// ClientCAFiles are paths to PEM-encoded certificate authorities that
// client certificates are verified against. A file may hold a bundle, and
// every certificate in every file is trusted. In a Kubernetes deployment
// this is the ca.crt entry of a trust-manager Bundle or an Opaque secret,
// mounted as a file.
//
// Paths are trimmed, and a blank entry is ignored.
//
// Configuring authorities does not by itself require a client to present a
// certificate. See ClientAuth.
ClientCAFiles []string
// ClientAuth selects how a client certificate is treated. The zero value
// resolves against ClientCAFiles: ClientAuthOptional when authorities are
// configured, ClientAuthOff when they are not.
ClientAuth ClientAuthMode
// MinVersion is the oldest protocol version the server will negotiate. The
// zero value resolves to VersionTLS12.
MinVersion Version
// MaxVersion is the newest protocol version the server will negotiate. The
// zero value imposes no maximum.
MaxVersion Version
// CipherSuites are the cipher suites the server will negotiate for TLS 1.2,
// spelled as crypto/tls spells them and matched case-insensitively. The
// zero value leaves the choice to crypto/tls. TLS 1.3 cipher suites are not
// configurable, so naming one here does not select it.
CipherSuites []CipherSuite
// ReloadInterval is how often the configured files are reread, so that a
// rotated certificate is served without a restart. The zero value resolves
// to one minute.
ReloadInterval time.Duration
// Logger receives the log lines this configuration emits as it resolves.
// When nil, nothing is logged.
Logger *slog.Logger
// Registerer publishes the Prometheus metrics describing the material
// served. When nil, no metrics are collected, as is the case for a
// configuration that is not enabled and so reads no material.
//
// Each configuration registered against the same Registerer must have a
// distinct Name, which is the constant label on every metric.
Registerer prometheus.Registerer
}
Config describes how a server serves TLS. The zero value serves plaintext, so a consumer can wire TLS unconditionally and leave the decision to configuration.
type Manager ¶ added in v2.50.0
type Manager struct {
// contains filtered or unexported fields
}
Manager holds the material a server serves TLS with. Build one with NewWithConfig.
Start and Shutdown must not be called concurrently. The expected usage is to call Start once during application startup and Shutdown once during graceful shutdown, consistent with the app.Component lifecycle.
func NewWithConfig ¶ added in v2.50.0
NewWithConfig returns a Manager for cfg with its material loaded. A nil cfg, and any configuration that is not enabled, yields a Manager that serves plaintext and reads no files.
cfg is resolved once, here, so the log lines describing it are emitted once.
func (*Manager) Name ¶ added in v2.53.0
Name returns the component name for use in logs and error messages.
func (*Manager) Shutdown ¶ added in v2.53.0
Shutdown ends the rereads and waits for the reload in flight to finish, or for ctx to expire. A Manager that was never started shuts down at once, and shutting one down twice is safe.
func (*Manager) Start ¶ added in v2.53.0
Start begins rereading the configured files on an interval, so that material replaced on disk is served without a restart. It returns immediately, and a Manager serving plaintext starts nothing.
A Manager that has been started returns ErrAlreadyStarted, whether or not it has since been shut down.
func (*Manager) TLSConfig ¶ added in v2.50.0
TLSConfig returns the crypto/tls configuration a server serves m with, or nil for a Manager that serves plaintext. Each call returns a new value.
The serving certificate is read from m as each handshake arrives, so a reload reaches a configuration a server already holds. The client certificate authorities are the ones m had loaded when this was called.
type Version ¶
type Version string
Version names a TLS protocol version.
func (Version) String ¶
String implements fmt.Stringer. The zero value renders as "unset" so it is distinguishable in a log line.
func (*Version) UnmarshalText ¶
UnmarshalText implements encoding.TextUnmarshaler, so a Version can be decoded straight out of JSON, YAML or an environment variable without the consumer writing an adapter. The receiver is left unchanged when the input does not parse.