123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407 |
- Using the transport package
- ===========================
- The transport package is designed to provide automated mutually-
- authenticated and server-only TLS security with proper security
- settings for Go programs. Mutually-authenticated means that clients
- will strictly validate the server's certificate, and servers will
- require that clients present a valid client authentication
- certificate.
- Adding the transport package to a project consists of a few steps:
- 1. Planning the right communications model.
- 2. Determining the configuration.
- 3. Adding the transport package to the project.
- Each of these steps will be covered in sequence, with the examples
- under transport/example/ used as illustrations. Some terminology:
- certificate provider: an interface to a CA that will sign CSRs and
- return certificates. The only available certificate provider at
- this time is CFSSL.
- key provider: mechanism for providing keys and signing certificate
- signing requests (CSRs). The only available key provider at this
- time is a disk-backed key set.
- root: the public certificate for a certificate authority (CA). This
- certificate is used to verify the certificate of a remote system: a
- client authentication root specifies the CA that a server uses to
- verify clients. The unqualified term root usually refers to a CA
- certificate that a client uses to verify a server's certificate.
- Transport package communication models
- --------------------------------------
- There are three models for communications:
- 1. A general TLS listener, such as a public HTTPS server. In this
- model, the server does not expect clients to authenticate
- themselves to the server using client authentication
- certificates. This listener doesn't need to configure any roots.
- 2. A mutually-authenticated TCP server. This can be an HTTPS server
- that requires client authentication, or any other TCP server that
- wants to set up mutually-authenticated communications. A server
- will construct a `Listener` and call the `Listen` method on that
- structure (this will be useful to remember later).
- 3. A mutually-authenticated TCP client. This can be an HTTPS client
- that supplies a client authentication certificate, or any other TCP
- client setting up a mutually-authenticated connection. A client
- will call `Dial` from a `Transport` structure.
- Once the model is determined, the configuration can be built.
- Transport package configuration
- -------------------------------
- In general, the transport package is build around the `core.Identity`
- type. This contains several top-level fields:
- + `Request` contains a `CertificateRequest` from the CFSSL csr package
- (e.g. https://godoc.org/github.com/cloudflare/cfssl/csr#CertificateRequest).
- The JSON tag for this field is "request".
- + `Profiles` contains profiles for certificate and key providers. The
- JSON tag for this field is "profiles".
- + `Roots` specifies roots that are used by clients to verify server
- certificates. The JSON tag for this field is "roots".
- + `ClientRoots` specifies roots that are used by servers to verify
- client certificates. The JSON tag for this field is "client_roots".
- The `Identity` structure is set up so that it could be integrated into
- a current configuration set up, or it can be present as a standalone
- configuration. The example programs use the code
- // conf is a string contain the path to a JSON configuration
- // file.
- var id = new(core.Identity)
- data, err := os.ReadFile(conf)
- if err != nil {
- exlib.Err(1, err, "reading config file")
- }
- err = json.Unmarshal(data, id)
- if err != nil {
- exlib.Err(1, err, "parsing config file")
- }
- to load a standalone transport configuration file in JSON.
- The `Profiles` field configures both key providers and certificate
- authorities.
- Key providers use the key request in the `Request` field to determine
- what sort of key to generate, and the rest of the field to determine
- the certificate signing request to generate.
- The only key-provider supported right now is the "path" provider,
- which would be configured something like
- // id is a core.Identity value.
- id.Profiles["path"] = map[string]string{
- "private_key": "/path/to/key.pem",
- "certificate": "/path/to/cert.pem",
- }
- The path provider determines whether a private key exists at the path
- provided; if it does not, a key is generated. If a certificate exists
- at the configured path, it is loaded --- if it's valid, it's
- kept. Otherwise, when the transport setup occurs, a new certificate
- will be requested. If the path fields are empty, then the keys will
- never be stored on disk; the "path" key must still be present though.
- // A path configuration for keys that are never stored on
- // disk.
- id["profiles"]["path"] = map[string]string{}
- When the key provider determines that its certificate is out of date
- (or, in the case of auto-updating, at some interval before the
- certificate expires), it will generate a CSR and pass it to a
- certificate provider.
- A CFSSL certificate provider points to a CFSSL server. It supports the
- following keys:
- + "remote" provides the hostname/IP and port for the CFSSL server.
- + "label" identifies which signer in a multiroot CFSSL should be
- used. An empty or missing label assumes the remote's default
- label will be used.
- + "profile" identifies the signing profile. An empty or missing
- profile assumes the remote's default profile will be used.
- + "auth-type" should be present if the remote CFSSL needs
- authentication. It tells the transport package what type of
- authentication to use. The authentication system in CFSSL
- is documented in "doc/authentication.txt"; for now, the
- only available authentication type is "standard".
- + "auth-key" specifies the authentication key in the case where the
- remote CFSSL requires authentication. Details are in
- "doc/authentication.txt", particularly the section covering key
- specification. As of now, The key may be specified in one of three
- ways:
- * hex-encoded string (e.g. "000102030405060708")
- * an environment variable prefixed with "env:"
- (e.g. "env:AUTH_KEY") that contains a hex-encoded string.
- * a path to a file containing the hex-encoded key, prefixed with
- "file:" (e.g. "file:/path/to/auth.key")
- A configuration that talks to the CFSSL instance running on
- ca.example.org might look like
- id["profiles"]["cfssl"] = map[string]string{
- "remote": "ca.example.org:8888",
- "profile": "short-lived",
- "auth-type": "standard",
- "auth-key": "env:TRANSPORT_CA_AUTH_KEY",
- }
- where the auth key would be set up as
- $ TRANSPORT_CA_AUTH_KEY="000102030405060708" ./some-program
- The `Roots` and `ClientRoots` fields are set up the same way; they
- differ only in how they are used. The are an array of root
- structures. There are three supported types of roots, each specified
- with the "type" key:
- + system roots use the operating system's default set of roots
- + file load PEM-encoded certificates from a file
- + cfssl retrieves the CA certificate from a remote CFSSL instance
- The file and cfssl types should contain a "metadata" key that contains
- a `map[string]string` with further information. The file type looks
- for the "source" metadata key, which should contain a path to the file
- to be loaded. The cfssl type accepts the same arguments as the CFSSL
- certificate provider; note that CFSSL servers don't authenticate the
- info endpoint. If the metadata contains authentication information
- (e.g. because it was copied from the certificate provider
- specification), the authentication keys will be ignored.
- The following example loads the system roots, a set of root
- certificates stored in a "custom.pem" file, and the same CFSSL
- instance used above; they are used for server authentication in this
- example.
- id["roots"] = []*core.Root{
- {
- Type: "system",
- },
- {
- Type: "file",
- Metadata: map[string]string{
- "source": "/etc/ssl/custom.pem",
- },
- },
- {
- Type: "cfssl",
- Metadata: map[string]string{
- "remote": "ca.example.org:8888",
- "profile": "short-lived",
- },
- },
- }
- If the above configuration was placed into a JSON file, it would look
- like:
- {
- "request": {
- "CN": "Example Service Client",
- "hosts": [
- "svc-client.example.org"
- ]
- },
- "profiles": {
- "path": {
- "private_key": "/path/to/key.pem",
- "certificate": "/path/to/cert.pem"
- },
- "cfssl": {
- "remote": "ca.example.org:8888",
- "profile": "short-lived",
- "auth-type": "standard",
- "auth-key": "env:TRANSPORT_CA_AUTH_KEY"
- }
- },
- "roots": [
- {
- "type": "system"
- },
- {
- "type": "file",
- "metadata": {
- "source": "/etc/ssl/custom.pem"
- }
- },
- {
- "type": "cfssl",
- "metadata": {
- "remote": "ca.example.org:8888",
- "profile": "short-lived"
- }
- }
- ]
- }
- This configuration would be used for a system using the third
- communications model discussed above. It could also be integrated into
- an existing configuration; an example of such a configuration would be
- type Configuration struct {
- Remote string // Server to connect to.
- Port int // Server's port.
- // Additional configuration fields follow
- Transport *core.Identity
- }
- Now that the service has a configuration, the transport package can be
- integrated into the code.
- Adding the transport package to a project
- -----------------------------------------
- Somehow, the program needs to load the `Identity` described in the
- previous section. For the sake of this discussion, it's assumed to
- be in the `id` variable:
- var id core.Identity
- The next step is to build a `Transport` from this. A `Transport` is
- set up with a "before" time (how long before the certificate expires
- should the service attempt to update the certificate) and a
- `*Identity`.
- // The default is to get a new certificate one day prior to
- // its expiration.
- tr, err := transport.New(core.DefaultBefore, &id)
- if err != nil {
- log.Fatalf("failed to configure a new TLS transport: %s", err)
- }
- The auto-updater must be configured explicitly. It takes two
- arguments: the update channel and an error channel. If the update
- channel is non-nil, it will receive `time.Time` values indicating when
- the certificate was renewed. If the error channel is non-nil, it will
- receive `error` values from the auto updater.
- If an error in updating occurs, the updater will use a backoff to keep
- retrying. If the backoff isn't configured, a default backoff (using
- an interval of 5 minutes and a max delay of six hours) will be
- used. The values for the default interval and maximum duration are
- found in the `DefaultInterval` and `DefaultMaxDuration` variables in
- the `backoff` package; these can be changed to suit the program's needs.
- (c.f https://godoc.org/github.com/cloudflare/backoff#Backoff).
- Clients will call `AutoUpdate` on the `Transport` itself; servers
- should call `AutoUpdate` on the listener (discussed below).
- The following example logs update timestamps and errors for a client:
- updates := make(chan time.Time, 0)
- go func(updatesc <-chan time.Time) {
- for {
- t, ok := <-updatesc
- if !ok {
- return
- }
- log.Printf("certificate auto-updated at %s",
- t.Format(time.RFC3339))
- }
- }
- errs := make(chan error, 0)
- go func(errsc <-chan error) {
- for {
- err, ok := <-errsc
- if !ok {
- return
- }
- log.Printf("certificate auto-update error: %s", err)
- }
- }
- go tr.AutoUpdate(updates, errs)
- A client may not want to start the auto-updater if the connection is
- expected to be short-lived. The package will check the certificate
- before the connection occurs, making sure it's still valid.
- At this point, the client can call the `Dial` function:
- conn, err := transport.Dial(address, tr)
- if err != nil {
- log.Fatalf("failed to dial remote host: %s", err)
- }
- The returned `conn` is a `*tls.Conn`, which is an implementation of
- `net.Conn`.
- Servers need one extra step before they are ready:
- l, err := transport.Listen(address, tr)
- if err != nil {
- loglFatalf("error setting up listener: %s", err)
- }
- // The same update channels
- go l.AutoUpdate(nil, nil)
- defer l.Close()
- for {
- conn, err := l.Accept()
- if err != nil {
- log.Printf("connection failed: %s", err)
- continue
- }
- log.Printf("connection from %s", conn.RemoteAddr())
- go serveClient(conn)
- }
- Extending the transport package
- ===============================
- The package is set up to deliver a useful set of defaults, but these
- defaults won't be appropriate for everyone. There are several places
- where the behaviour can be altered.
- Additional root providers may be set up by adding the relevant entries
- to `roots.Providers`. This is a map of string names (e.g. the `Type`
- field) to a function that accepts the `Metadata` field (a
- `map[string]string`), and which returns a list of `*x509.Certificate`:
- var Providers map[string]func(map[string]string) ([]*x509.Certificate, error)
- The `NewKeyProvider` and `NewCA` functions provide a mechanism for
- choosing a key provider and CA from an identity. The default is to
- attempt to load the standard ("path") key provider and a CFSSL CA:
- var (
- // NewKeyProvider is the function used to build key providers
- // from some identity.
- NewKeyProvider = func(id *core.Identity) (kp.KeyProvider, error) {
- return kp.NewStandardProvider(id)
- }
- // NewCA is used to load a configuration for a certificate
- // authority.
- NewCA = func(id *core.Identity) (ca.CertificateAuthority, error) {
- return ca.NewCFSSLProvider(id, nil)
- }
- )
- By default, `AutoUpdate` checks the expiry on the certificate every
- thirty seconds. This behaviour may be changed by changing
- `transport.PollInterval`. If set to 0, the updater will just wait for
- the lifespan of the certificate.
|