Merge pull request #5 from smallstep/bootstrap-context

Add context to bootstrap methods
This commit is contained in:
Mariano Cano 2018-11-06 17:48:30 -08:00 committed by GitHub
commit e050c5cf0f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 114 additions and 45 deletions

View file

@ -38,17 +38,34 @@ func Bootstrap(token string) (*Client, error) {
return NewClient(claims.Audience[0], WithRootSHA256(claims.SHA))
}
// BootstrapServer is a helper function that returns an http.Server configured
// with the given address and handler, and prepared to use TLS connections. The
// certificate will automatically rotate if necessary.
// BootstrapServer is a helper function that using the given token returns the
// given http.Server configured with a TLS certificate signed by the Certificate
// Authority. By default the server will kick off a routine that will renew the
// certificate after 2/3rd of the certificate's lifetime has expired.
//
// Usage:
// srv, err := ca.BootstrapServer(":443", token, handler)
// // Default example with certificate rotation.
// srv, err := ca.BootstrapServer(context.Background(), token, &http.Server{
// Addr: ":443",
// Handler: handler,
// })
//
// // Example canceling automatic certificate rotation.
// ctx, cancel := context.WithCancel(context.Background())
// defer cancel()
// srv, err := ca.BootstrapServer(ctx, token, &http.Server{
// Addr: ":443",
// Handler: handler,
// })
// if err != nil {
// return err
// }
// srv.ListenAndServeTLS("", "")
func BootstrapServer(addr, token string, handler http.Handler) (*http.Server, error) {
func BootstrapServer(ctx context.Context, token string, base *http.Server) (*http.Server, error) {
if base.TLSConfig != nil {
return nil, errors.New("server TLSConfig is already set")
}
client, err := Bootstrap(token)
if err != nil {
return nil, err
@ -64,30 +81,34 @@ func BootstrapServer(addr, token string, handler http.Handler) (*http.Server, er
return nil, err
}
tlsConfig, err := client.GetServerTLSConfig(context.Background(), sign, pk)
tlsConfig, err := client.GetServerTLSConfig(ctx, sign, pk)
if err != nil {
return nil, err
}
return &http.Server{
Addr: addr,
Handler: handler,
TLSConfig: tlsConfig,
}, nil
base.TLSConfig = tlsConfig
return base, nil
}
// BootstrapClient is a helper function that using the given bootstrap token
// return an http.Client configured with a Transport prepared to do TLS
// connections using the client certificate returned by the certificate
// authority. The certificate will automatically rotate if necessary.
// authority. By default the server will kick off a routine that will renew the
// certificate after 2/3rd of the certificate's lifetime has expired.
//
// Usage:
// client, err := ca.BootstrapClient(token)
// // Default example with certificate rotation.
// client, err := ca.BootstrapClient(ctx.Background(), token)
//
// // Example canceling automatic certificate rotation.
// ctx, cancel := context.WithCancel(context.Background())
// defer cancel()
// client, err := ca.BootstrapClient(ctx, token)
// if err != nil {
// return err
// }
// resp, err := client.Get("https://internal.smallstep.com")
func BootstrapClient(token string) (*http.Client, error) {
func BootstrapClient(ctx context.Context, token string) (*http.Client, error) {
client, err := Bootstrap(token)
if err != nil {
return nil, err
@ -103,7 +124,7 @@ func BootstrapClient(token string) (*http.Client, error) {
return nil, err
}
transport, err := client.Transport(context.Background(), sign, pk)
transport, err := client.Transport(ctx, sign, pk)
if err != nil {
return nil, err
}

View file

@ -1,6 +1,8 @@
package ca
import (
"context"
"crypto/tls"
"net/http"
"net/http/httptest"
"reflect"
@ -128,25 +130,23 @@ func TestBootstrapServer(t *testing.T) {
token := func() string {
return generateBootstrapToken(srv.URL, "subject", "ef742f95dc0d8aa82d3cca4017af6dac3fce84290344159891952d18c53eefe7")
}
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("ok"))
})
type args struct {
addr string
token string
handler http.Handler
ctx context.Context
token string
base *http.Server
}
tests := []struct {
name string
args args
wantErr bool
}{
{"ok", args{":0", token(), handler}, false},
{"fail", args{":0", "bad-token", handler}, true},
{"ok", args{context.Background(), token(), &http.Server{}}, false},
{"fail", args{context.Background(), "bad-token", &http.Server{}}, true},
{"fail with TLSConfig", args{context.Background(), token(), &http.Server{TLSConfig: &tls.Config{}}}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := BootstrapServer(tt.args.addr, tt.args.token, tt.args.handler)
got, err := BootstrapServer(tt.args.ctx, tt.args.token, tt.args.base)
if (err != nil) != tt.wantErr {
t.Errorf("BootstrapServer() error = %v, wantErr %v", err, tt.wantErr)
return
@ -156,8 +156,11 @@ func TestBootstrapServer(t *testing.T) {
t.Errorf("BootstrapServer() = %v, want nil", got)
}
} else {
if !reflect.DeepEqual(got.Addr, tt.args.addr) {
t.Errorf("BootstrapServer() Addr = %v, want %v", got.Addr, tt.args.addr)
expected := &http.Server{
TLSConfig: got.TLSConfig,
}
if !reflect.DeepEqual(got, expected) {
t.Errorf("BootstrapServer() = %v, want %v", got, expected)
}
if got.TLSConfig == nil || got.TLSConfig.ClientCAs == nil || got.TLSConfig.RootCAs == nil || got.TLSConfig.GetCertificate == nil || got.TLSConfig.GetClientCertificate == nil {
t.Errorf("BootstrapServer() invalid TLSConfig = %#v", got.TLSConfig)
@ -174,6 +177,7 @@ func TestBootstrapClient(t *testing.T) {
return generateBootstrapToken(srv.URL, "subject", "ef742f95dc0d8aa82d3cca4017af6dac3fce84290344159891952d18c53eefe7")
}
type args struct {
ctx context.Context
token string
}
tests := []struct {
@ -181,12 +185,12 @@ func TestBootstrapClient(t *testing.T) {
args args
wantErr bool
}{
{"ok", args{token()}, false},
{"fail", args{"bad-token"}, true},
{"ok", args{context.Background(), token()}, false},
{"fail", args{context.Background(), "bad-token"}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := BootstrapClient(tt.args.token)
got, err := BootstrapClient(tt.args.ctx, tt.args.token)
if (err != nil) != tt.wantErr {
t.Errorf("BootstrapClient() error = %v, wantErr %v", err, tt.wantErr)
return

View file

@ -119,13 +119,13 @@ tr, err := client.Transport(ctx, sign, pk)
To run the example you need to start the certificate authority:
```
```sh
certificates $ bin/step-ca examples/pki/config/ca.json
2018/11/02 18:29:25 Serving HTTPS on :9000 ...
```
And just run the client.go with a new token:
```
```sh
certificates $ export STEPPATH=examples/pki
certificates $ export STEP_CA_URL=https://localhost:9000
certificates $ go run examples/basic-client/client.go $(step ca new-token client.smallstep.com)
@ -140,15 +140,46 @@ server.
The examples directory already contains a sample pki configuration with the
password `password` hardcoded, but you can create your own using `step ca init`.
First we will start the certificate authority:
These examples show the use of other helper methods, they are simple ways to
create TLS configured http.Server and http.Client objects. The methods are
`BootstrapServer` and `BootstrapClient` and they are used like:
```go
// Get a cancelable context to stop the renewal goroutines and timers.
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Create an http.Server
srv, err := ca.BootstrapServer(ctx, token, &http.Server{
Addr: ":8443",
Handler: handler,
})
if err != nil {
panic(err)
}
srv.ListenAndServeTLS("", "")
```
```go
// Get a cancelable context to stop the renewal goroutines and timers.
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Create an http.Client
client, err := ca.BootstrapClient(ctx, token)
if err != nil {
panic(err)
}
resp, err := client.Get("https://localhost:8443")
```
To run the example first we will start the certificate authority:
```sh
certificates $ bin/step-ca examples/pki/config/ca.json
2018/11/02 18:29:25 Serving HTTPS on :9000 ...
```
We will start the server and we will type `password` when step asks for the
provisioner password:
```
```sh
certificates $ export STEPPATH=examples/pki
certificates $ export STEP_CA_URL=https://localhost:9000
certificates $ go run examples/bootstrap-server/server.go $(step ca new-token localhost)
@ -177,7 +208,7 @@ HTTPS-proxy has similar options --proxy-cacert and --proxy-insecure.
```
But if we use the root certificate it will properly work:
```
```sh
certificates $ curl --cacert examples/pki/secrets/root_ca.crt https://localhost:8443
Hello nobody at 2018-11-03 01:49:25.66912 +0000 UTC!!!
```
@ -186,7 +217,7 @@ Notice that in the response we see `nobody`, this is because the server didn't
detected a TLS client configuration.
But if we the client with the certificate name Mike we'll see:
```
```sh
certificates $ export STEPPATH=examples/pki
certificates $ export STEP_CA_URL=https://localhost:9000
certificates $ go run examples/bootstrap-client/client.go $(step ca new-token Mike)
@ -206,7 +237,7 @@ this provisioner is configured with a default certificate duration of 2 minutes.
If we run the server, and inspect the used certificate, we can verify how it
rotates after approximately two thirds of the duration has passed.
```
```sh
certificates $ export STEPPATH=examples/pki
certificates $ export STEP_CA_URL=https://localhost:9000
certificates $ go run examples/bootstrap-server/server.go $(step ca new-token localhost))
@ -222,6 +253,6 @@ number between 0 and 6.
We can use the following command to check the certificate expiration and to make
sure the certificate changes after 74-80 seconds.
```
```sh
certificates $ step certificate inspect --insecure https://localhost:8443
```

View file

@ -1,6 +1,7 @@
package main
import (
"context"
"fmt"
"io/ioutil"
"os"
@ -17,7 +18,11 @@ func main() {
token := os.Args[1]
client, err := ca.BootstrapClient(token)
// make sure to cancel the renew goroutine
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
client, err := ca.BootstrapClient(ctx, token)
if err != nil {
panic(err)
}

View file

@ -1,6 +1,7 @@
package main
import (
"context"
"fmt"
"net/http"
"os"
@ -17,13 +18,20 @@ func main() {
token := os.Args[1]
srv, err := ca.BootstrapServer(":8443", token, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
name := "nobody"
if r.TLS != nil && len(r.TLS.PeerCertificates) > 0 {
name = r.TLS.PeerCertificates[0].Subject.CommonName
}
w.Write([]byte(fmt.Sprintf("Hello %s at %s!!!", name, time.Now().UTC())))
}))
// make sure to cancel the renew goroutine
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
srv, err := ca.BootstrapServer(ctx, token, &http.Server{
Addr: ":8443",
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
name := "nobody"
if r.TLS != nil && len(r.TLS.PeerCertificates) > 0 {
name = r.TLS.PeerCertificates[0].Subject.CommonName
}
w.Write([]byte(fmt.Sprintf("Hello %s at %s!!!", name, time.Now().UTC())))
}),
})
if err != nil {
panic(err)
}