Configuration keys¶
Pair is the only configuration shape the package defines. It carries
mapstructure, yaml and json tags with identical names, so the same block of
config unmarshals the same way whichever loader you use.
tls:
enabled: true
cert: /etc/certs/server.pem
key: /etc/certs/server-key.pem
client_cas:
- /etc/certs/client-ca.pem
client_auth: require-verify
The package does not read config itself — it has no loader, no environment-variable
prefix and no flags. You unmarshal into a Pair and hand it over. See
Limitations.
Every key, its type and its default¶
| Key | Type | Default | What it does |
|---|---|---|---|
enabled |
bool | false |
Advisory flag read only by Pair.Valid. No builder consults it — see below. |
cert |
string | "" |
Path to the PEM certificate file (leaf first, then any intermediates). |
key |
string | "" |
Path to the PEM private key file for cert. |
client_cas |
list of strings | empty | PEM files holding the CA certificates that client certificates must chain to. Turns on server-side mTLS. |
client_auth |
string | "" |
Client-certificate enforcement mode. One of request, verify-if-given, require-verify, or empty. |
There are no other keys. Cipher suites, protocol versions and curves are not
configurable through Pair — they come from
DefaultConfig and are fixed.
What enabled: false actually does¶
Nothing, unless you check it. Pair.Valid is the only code path that reads
Enabled:
ServerConfig, ApplyTo and Certificate ignore it. A pair with
enabled: false and valid paths still builds a working TLS config and still
loads the certificate off disk. If a false should mean "serve plain HTTP",
you have to branch on Valid() yourself before you build the config:
What each client_auth value enforces¶
| Value | crypto/tls mode |
Client certificate | client_cas required |
|---|---|---|---|
request |
RequestClientCert |
Asked for, never required, never verified | No |
verify-if-given |
VerifyClientCertIfGiven |
Optional; verified when presented | Yes |
require-verify |
RequireAndVerifyClientCert |
Required and verified | Yes |
"" with client_cas set |
RequireAndVerifyClientCert |
Required and verified | — |
"" with no client_cas |
NoClientCert |
Not asked for | — |
The three non-empty values are also exported as constants —
tls.ClientAuthRequest, tls.ClientAuthVerifyIfGiven,
tls.ClientAuthRequireVerify — so Go callers do not have to spell the strings.
request is the one mode that accepts a certificate without checking it. Your
handler receives it in r.TLS.PeerCertificates with VerifiedChains empty. Treat
anything in it as unauthenticated input.
What happens when a key is wrong¶
Every failure below happens at config-build time — when you call ServerConfig,
ApplyTo, ClientConfig or CertPool — not on the first handshake. A misconfigured
service fails at startup.
| Mistake | Result |
|---|---|
cert or key path does not exist |
Error: loading TLS certificate: open …: no such file or directory |
cert and key do not match each other |
Error from crypto/tls, wrapped as loading TLS certificate: … |
client_auth misspelled ("require_verify", "required", "REQUIRE-VERIFY") |
Error: unknown client_auth "…" (valid: "request", "verify-if-given", "require-verify"). Values are case-sensitive and hyphenated. |
client_auth: require-verify or verify-if-given with no client_cas |
Error: client_auth "…" requires client_cas to verify against |
A client_cas file is missing |
Error: reading CA file "…": open …: no such file or directory |
A client_cas file exists but holds no PEM certificate |
Error: no certificates found in "…" |
client_cas set, client_auth left empty |
Not an error — this means require-verify |
enabled: false with valid paths |
Not an error — TLS is still configured; see above |
The full list of messages, with the exact wording, is in Errors.
Sharing one certificate across several listeners¶
ResolvePair merges a shared pair with a per-transport pair, field by field, and
PairOverrides says which fields the transport section actually set:
PairOverrides field |
Overrides |
|---|---|
Enabled |
enabled |
Cert |
cert |
Key |
key |
ClientCAs |
client_cas |
ClientAuth |
client_auth |
A field is taken from transport when its override boolean is true, and from
shared otherwise. The mask exists because a transport section that omits
enabled is indistinguishable from one that sets enabled: false once both have
been unmarshalled into a struct — you decide what counts as "set" (a config key
being present, a flag being changed) and express it as the boolean.
shared := tls.Pair{Enabled: true, Cert: "/etc/certs/shared.pem", Key: "/etc/certs/shared-key.pem"}
grpc := tls.Pair{Cert: "/etc/certs/grpc.pem"}
pair := tls.ResolvePair(shared, grpc, tls.PairOverrides{Cert: true})
// pair.Enabled == true, pair.Cert == "/etc/certs/grpc.pem", pair.Key == "/etc/certs/shared-key.pem"
ResolvePair reads no files and validates nothing. It cannot fail, and a nonsense
result — say a cert from one transport and a key from another — surfaces later, as a
certificate load error.
The resolved pair shares its client_cas slice with whichever pair supplied it:
ResolvePair copies the slice header, not the backing array. Mutating
resolved.ClientCAs[0] in place also mutates the shared pair. Build a new slice
rather than editing one.