File: transport.txt

package info (click to toggle)
golang-github-cloudflare-cfssl 1.2.0%2Bgit20160825.89.7fb22c8-3
  • links: PTS, VCS
  • area: main
  • in suites: buster
  • size: 4,916 kB
  • ctags: 2,827
  • sloc: sh: 146; sql: 62; python: 11; makefile: 8
file content (406 lines) | stat: -rw-r--r-- 14,375 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
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 := ioutil.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 packge 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 `core` package; these can be changed to suit the program's needs.
(c.f https://godoc.org/github.com/cloudflare/cfssl/transport/core#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.