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.
|