Add context to bootstrap methods.

This commit is contained in:
Mariano Cano 2018-11-06 17:16:33 -08:00
parent 299eea233b
commit ba88c8c5cb
5 changed files with 99 additions and 41 deletions

View file

@ -43,12 +43,22 @@ func Bootstrap(token string) (*Client, error) {
// certificate will automatically rotate if necessary. // certificate will automatically rotate if necessary.
// //
// Usage: // Usage:
// srv, err := ca.BootstrapServer(":443", token, handler) // // make sure to cancel the rotation goroutine
// ctx, cancel := context.WithCancel(context.Background())
// defer cancel()
// srv, err := ca.BootstrapServer(ctx, token, &http.Server{
// Addr: ":443",
// Handler: handler,
// })
// if err != nil { // if err != nil {
// return err // return err
// } // }
// srv.ListenAndServeTLS("", "") // 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) client, err := Bootstrap(token)
if err != nil { if err != nil {
return nil, err return nil, err
@ -64,16 +74,13 @@ func BootstrapServer(addr, token string, handler http.Handler) (*http.Server, er
return nil, err return nil, err
} }
tlsConfig, err := client.GetServerTLSConfig(context.Background(), sign, pk) tlsConfig, err := client.GetServerTLSConfig(ctx, sign, pk)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &http.Server{ base.TLSConfig = tlsConfig
Addr: addr, return base, nil
Handler: handler,
TLSConfig: tlsConfig,
}, nil
} }
// BootstrapClient is a helper function that using the given bootstrap token // BootstrapClient is a helper function that using the given bootstrap token
@ -82,12 +89,15 @@ func BootstrapServer(addr, token string, handler http.Handler) (*http.Server, er
// authority. The certificate will automatically rotate if necessary. // authority. The certificate will automatically rotate if necessary.
// //
// Usage: // Usage:
// client, err := ca.BootstrapClient(token) // // make sure to cancel the rotation goroutine
// ctx, cancel := context.WithCancel(context.Background())
// defer cancel()
// client, err := ca.BootstrapClient(ctx, token)
// if err != nil { // if err != nil {
// return err // return err
// } // }
// resp, err := client.Get("https://internal.smallstep.com") // 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) client, err := Bootstrap(token)
if err != nil { if err != nil {
return nil, err return nil, err
@ -103,7 +113,7 @@ func BootstrapClient(token string) (*http.Client, error) {
return nil, err return nil, err
} }
transport, err := client.Transport(context.Background(), sign, pk) transport, err := client.Transport(ctx, sign, pk)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -1,6 +1,8 @@
package ca package ca
import ( import (
"context"
"crypto/tls"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"reflect" "reflect"
@ -128,25 +130,23 @@ func TestBootstrapServer(t *testing.T) {
token := func() string { token := func() string {
return generateBootstrapToken(srv.URL, "subject", "ef742f95dc0d8aa82d3cca4017af6dac3fce84290344159891952d18c53eefe7") return generateBootstrapToken(srv.URL, "subject", "ef742f95dc0d8aa82d3cca4017af6dac3fce84290344159891952d18c53eefe7")
} }
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("ok"))
})
type args struct { type args struct {
addr string ctx context.Context
token string token string
handler http.Handler base *http.Server
} }
tests := []struct { tests := []struct {
name string name string
args args args args
wantErr bool wantErr bool
}{ }{
{"ok", args{":0", token(), handler}, false}, {"ok", args{context.Background(), token(), &http.Server{}}, false},
{"fail", args{":0", "bad-token", handler}, true}, {"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 { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { 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 { if (err != nil) != tt.wantErr {
t.Errorf("BootstrapServer() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("BootstrapServer() error = %v, wantErr %v", err, tt.wantErr)
return return
@ -156,8 +156,11 @@ func TestBootstrapServer(t *testing.T) {
t.Errorf("BootstrapServer() = %v, want nil", got) t.Errorf("BootstrapServer() = %v, want nil", got)
} }
} else { } else {
if !reflect.DeepEqual(got.Addr, tt.args.addr) { expected := &http.Server{
t.Errorf("BootstrapServer() Addr = %v, want %v", got.Addr, tt.args.addr) 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 { 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) t.Errorf("BootstrapServer() invalid TLSConfig = %#v", got.TLSConfig)
@ -174,6 +177,7 @@ func TestBootstrapClient(t *testing.T) {
return generateBootstrapToken(srv.URL, "subject", "ef742f95dc0d8aa82d3cca4017af6dac3fce84290344159891952d18c53eefe7") return generateBootstrapToken(srv.URL, "subject", "ef742f95dc0d8aa82d3cca4017af6dac3fce84290344159891952d18c53eefe7")
} }
type args struct { type args struct {
ctx context.Context
token string token string
} }
tests := []struct { tests := []struct {
@ -181,12 +185,12 @@ func TestBootstrapClient(t *testing.T) {
args args args args
wantErr bool wantErr bool
}{ }{
{"ok", args{token()}, false}, {"ok", args{context.Background(), token()}, false},
{"fail", args{"bad-token"}, true}, {"fail", args{context.Background(), "bad-token"}, true},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { 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 { if (err != nil) != tt.wantErr {
t.Errorf("BootstrapClient() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("BootstrapClient() error = %v, wantErr %v", err, tt.wantErr)
return 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: To run the example you need to start the certificate authority:
``` ```sh
certificates $ bin/step-ca examples/pki/config/ca.json certificates $ bin/step-ca examples/pki/config/ca.json
2018/11/02 18:29:25 Serving HTTPS on :9000 ... 2018/11/02 18:29:25 Serving HTTPS on :9000 ...
``` ```
And just run the client.go with a new token: And just run the client.go with a new token:
``` ```sh
certificates $ export STEPPATH=examples/pki certificates $ export STEPPATH=examples/pki
certificates $ export STEP_CA_URL=https://localhost:9000 certificates $ export STEP_CA_URL=https://localhost:9000
certificates $ go run examples/basic-client/client.go $(step ca new-token client.smallstep.com) 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 The examples directory already contains a sample pki configuration with the
password `password` hardcoded, but you can create your own using `step ca init`. 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 certificates $ bin/step-ca examples/pki/config/ca.json
2018/11/02 18:29:25 Serving HTTPS on :9000 ... 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 We will start the server and we will type `password` when step asks for the
provisioner password: provisioner password:
``` ```sh
certificates $ export STEPPATH=examples/pki certificates $ export STEPPATH=examples/pki
certificates $ export STEP_CA_URL=https://localhost:9000 certificates $ export STEP_CA_URL=https://localhost:9000
certificates $ go run examples/bootstrap-server/server.go $(step ca new-token localhost) 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: But if we use the root certificate it will properly work:
``` ```sh
certificates $ curl --cacert examples/pki/secrets/root_ca.crt https://localhost:8443 certificates $ curl --cacert examples/pki/secrets/root_ca.crt https://localhost:8443
Hello nobody at 2018-11-03 01:49:25.66912 +0000 UTC!!! 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. detected a TLS client configuration.
But if we the client with the certificate name Mike we'll see: But if we the client with the certificate name Mike we'll see:
``` ```sh
certificates $ export STEPPATH=examples/pki certificates $ export STEPPATH=examples/pki
certificates $ export STEP_CA_URL=https://localhost:9000 certificates $ export STEP_CA_URL=https://localhost:9000
certificates $ go run examples/bootstrap-client/client.go $(step ca new-token Mike) 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 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. rotates after approximately two thirds of the duration has passed.
``` ```sh
certificates $ export STEPPATH=examples/pki certificates $ export STEPPATH=examples/pki
certificates $ export STEP_CA_URL=https://localhost:9000 certificates $ export STEP_CA_URL=https://localhost:9000
certificates $ go run examples/bootstrap-server/server.go $(step ca new-token localhost)) 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 We can use the following command to check the certificate expiration and to make
sure the certificate changes after 74-80 seconds. sure the certificate changes after 74-80 seconds.
``` ```sh
certificates $ step certificate inspect --insecure https://localhost:8443 certificates $ step certificate inspect --insecure https://localhost:8443
``` ```

View file

@ -1,6 +1,7 @@
package main package main
import ( import (
"context"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
@ -17,7 +18,11 @@ func main() {
token := os.Args[1] 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 { if err != nil {
panic(err) panic(err)
} }

View file

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