Documentation
¶
Overview ¶
Package mtls is the TLS contract between ratd and its clients (the rat bridge, or a harness targeting ratd directly): how bundles are made, and how each side checks the other. The design decisions, and the alternatives they rejected, are described in the README of this package.
Bundles ¶
A bundle is generated at once, for one tenant (Generate): a CA, a server certificate for ratd, and one certificate per named client. The CA private key only exists in memory while generating: once written, the bundle is closed, and no certificate can be added to it. Adding a client, or replacing a compromised key, means generating a new bundle. Each side gets a directory holding what it needs, the CA certificate included, and nothing else. Certificates are valid for 10 years, and every side warns once less than a year remains (ExpiryWarning).
Roles and names ¶
Certificates carry their role (extended key usage): serverAuth for ratd, clientAuth for clients, so that neither can stand for the other. They also carry a name: the server certificate names the tenant, which ratd serves, and so does the CA, so that a client directory tells which tenant it reaches. A client certificate names its client, which is its tmux session. Both are required, and are plain names, validated as tmux names are.
Checks ¶
ratd checks client certificates: signed by the CA of the bundle, valid, clientAuth. Clients check the server certificate the same way with serverAuth, but not the host name they reach: it carries no address. Each side also checks its own directory when loading it (Load: files, key permissions, key matching the certificate, CA, role, validity, names), to fail at startup rather than at the first connection; rat-tool runs the same checks for humans. Parse runs them on credentials that are not in a directory (a secret store, an embedded asset), but for what only files have.
TLS configurations ¶
ServerConfig and ClientConfig build the TLS configuration of each side from its checked credentials, TLS 1.3 only. The client configuration is exported, for Go clients connecting to ratd directly. PeerName tells the name of the peer of a connection: its session for ratd, the tenant for a client.
Index ¶
- Constants
- Variables
- func CertificateFiles(dir string, role Role) []string
- func ClientConfig(side *Side) (*tls.Config, error)
- func ClientDir(dir, client string) string
- func Fingerprint(cert *x509.Certificate) string
- func PeerName(state tls.ConnectionState) string
- func ServerConfig(side *Side) (*tls.Config, error)
- func ServerDir(dir string) string
- type Bundle
- type Check
- type Role
- type Side
Constants ¶
const ExpiryWarning = 365 * 24 * time.Hour
ExpiryWarning is how long before a bundle expires its sides warn about it: an expired bundle stops every client at once, years after anyone remembers how it was made.
Variables ¶
var ( // ErrFiles is returned for a directory not holding the files of one side of a bundle: ca.crt, // then server.crt and server.key, or client.crt and client.key, each a single PEM block. Parse // returns it for a content that is not a single PEM block of its kind. ErrFiles = errors.New("not the directory of a side of a bundle") // ErrKeyPermissions is returned for a private key readable by others than its owner: it is a // credential (a client key grants a shell). Not checked on Windows, where modes mean nothing. ErrKeyPermissions = errors.New("private key readable by others") // ErrKeyMismatch is returned for a private key not matching the certificate. ErrKeyMismatch = errors.New("private key not matching the certificate") // ErrNotSignedByCA is returned for a certificate not signed by the CA of its directory, or a // CA certificate that is not one. ErrNotSignedByCA = errors.New("certificate not signed by the CA") // ErrRole is returned for a certificate not authenticating the side its file names, or not // only it. ErrRole = errors.New("certificate of another role") // ErrValidity is returned for a certificate, or its CA, expired or not valid yet. ErrValidity = errors.New("certificate not valid now") )
The checks of Load, in their order. An invalid name wraps names.ErrInvalid.
Functions ¶
func CertificateFiles ¶
CertificateFiles returns the paths of the certificates in dir, the directory of the side role: the CA's and the side's own, the files of the side but its key. Nothing in them is secret, but whoever replaces them chooses whom the side trusts: for the caller to check who can write them.
func ClientConfig ¶
ClientConfig returns the TLS configuration of a client of ratd, from the side of the client in the bundle: it presents the client certificate, and accepts a server certificate of the bundle (signed by its CA, valid, serverAuth) without checking the host name it reaches, which the server certificate does not carry. TLS 1.3 only. A server refused fails the handshake with an error wrapping ErrNotSignedByCA, ErrValidity or ErrRole. The error wraps ErrRole for the side of the server: the server can not stand for a client.
func ClientDir ¶
ClientDir returns the directory of client, in the bundle written to dir. Clients have a directory of their own, so that a client named "server" does not collide with the server.
func Fingerprint ¶
func Fingerprint(cert *x509.Certificate) string
Fingerprint returns the SHA-256 fingerprint of cert, as openssl prints it (colon separated uppercase hex). The fingerprint of its CA identifies a bundle.
func PeerName ¶
func PeerName(state tls.ConnectionState) string
PeerName returns the name carried by the certificate of the peer of a connection established with these configurations: for ratd, the client, which is its session; for a client, the tenant. It is empty when the peer presented no certificate, which a handshake with these configurations does not allow.
func ServerConfig ¶
ServerConfig returns the TLS configuration of ratd, from the server side of its bundle: it presents the server certificate, and requires a client certificate of the bundle (signed by its CA, valid, clientAuth), which Go checks by itself. TLS 1.3 only. It sets nothing of the protocol above TLS (ALPN), which is ratd's to choose. The error wraps ErrRole for the side of a client: a client can not run a server.
Types ¶
type Bundle ¶
type Bundle struct {
// Tenant is the tenant the bundle is for, named by its CA and its server certificate.
Tenant string
// Clients are the clients of the tenant, each named by its certificate, which is its session.
Clients []string
// CAFingerprint identifies the bundle: every directory of the bundle holds its CA.
CAFingerprint string
// NotAfter is when every certificate of the bundle expires.
NotAfter time.Time
}
Bundle describes a generated bundle.
func Generate ¶
Generate writes the bundle of tenant into dir: a CA, a server certificate naming the tenant, and a certificate per client, naming it. Names are plain names (package names), at least one client is required, and client names are unique. The CA private key is never written: no certificate can be added to the bundle afterwards.
dir must not exist, even empty: replacing a bundle silently breaks every client deployed with it, and the error then wraps fs.ErrExist. Missing parent directories are created. The bundle is written as below, keys and the directories holding them readable by their owner only:
dir/server/ ca.crt server.crt server.key dir/clients/<name>/ ca.crt client.crt client.key
On failure, dir is removed if it was created (parents created stay).
type Check ¶
type Check struct {
// Name tells what the check requires, for humans.
Name string
// Err is the sentinel the error of Load wraps when the check fails.
Err error
}
Check is a check of Load, for a caller reporting each (rat-tool). Parse runs them too, but for the permissions of the key.
type Role ¶
type Role int
Role is what a certificate authenticates, carried by its extended key usage: ratd, or one of its clients. Neither can stand for the other.
type Side ¶
type Side struct {
// Role is the side the directory is for, named by its certificate file and carried by its
// certificate.
Role Role
// Name is the name the certificate carries: the tenant for the server, the client for a
// client, which is its session.
Name string
// Tenant is the tenant of the bundle, named by its CA.
Tenant string
// CA is the certificate of the bundle CA.
CA *x509.Certificate
// Certificate is the certificate of the side, with its private key and parsed leaf.
Certificate tls.Certificate
}
Side is a side of a bundle, loaded from its directory or parsed from its contents, and checked: what ratd, or one of its clients, holds.
func Load ¶
Load reads the directory of a side of a bundle, and checks it, to fail when starting rather than at the first connection, where a misconfiguration only shows as an opaque handshake error. The side is the one whose certificate file the directory holds. The checks run in this order, and the error wraps the sentinel of the first one failing, the checks before it having passed:
- files (ErrFiles): the files of one side are present, each a single PEM block: several certificates in ca.crt would all be trusted;
- key permissions (ErrKeyPermissions): the key is readable by its owner only (not on Windows);
- key (ErrKeyMismatch): the key matches the certificate;
- CA (ErrNotSignedByCA): ca.crt is a CA, which signed the certificate;
- role (ErrRole): the certificate authenticates the side its file names, and only it;
- validity (ErrValidity): the certificate and the CA are valid now;
- names (names.ErrInvalid): the certificate and the CA carry plain names.
func Parse ¶
Parse returns the side role of a bundle from the contents of its PEM files: the CA certificate, the certificate of the side and its key. It is Load for credentials that are not in a directory (a secret store, an embedded asset): the same checks, in the same order, apart from what only files have. The first, ErrFiles, checks each content is a single PEM block of its kind; the permissions of a key (ErrKeyPermissions) are its holder's concern. The side is the one role names, which a directory tells by the names of its files.