From ba88c8c5cbf8dfd524f5fb37dff5c6d4e18e5741 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Tue, 6 Nov 2018 17:16:33 -0800 Subject: [PATCH] Add context to bootstrap methods. --- ca/bootstrap.go | 32 +++++++++++++------- ca/bootstrap_test.go | 32 +++++++++++--------- examples/README.md | 47 ++++++++++++++++++++++++----- examples/bootstrap-client/client.go | 7 ++++- examples/bootstrap-server/server.go | 22 +++++++++----- 5 files changed, 99 insertions(+), 41 deletions(-) diff --git a/ca/bootstrap.go b/ca/bootstrap.go index 880d4652..b8e51941 100644 --- a/ca/bootstrap.go +++ b/ca/bootstrap.go @@ -43,12 +43,22 @@ func Bootstrap(token string) (*Client, error) { // certificate will automatically rotate if necessary. // // 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 { // 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,16 +74,13 @@ 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 @@ -82,12 +89,15 @@ func BootstrapServer(addr, token string, handler http.Handler) (*http.Server, er // authority. The certificate will automatically rotate if necessary. // // 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 { // 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 +113,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 } diff --git a/ca/bootstrap_test.go b/ca/bootstrap_test.go index 821b82a0..3d1863eb 100644 --- a/ca/bootstrap_test.go +++ b/ca/bootstrap_test.go @@ -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 diff --git a/examples/README.md b/examples/README.md index 54c28acd..2c0f0e3a 100644 --- a/examples/README.md +++ b/examples/README.md @@ -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 ``` diff --git a/examples/bootstrap-client/client.go b/examples/bootstrap-client/client.go index 869484bd..109336be 100644 --- a/examples/bootstrap-client/client.go +++ b/examples/bootstrap-client/client.go @@ -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) } diff --git a/examples/bootstrap-server/server.go b/examples/bootstrap-server/server.go index c48ca776..1f9c0901 100644 --- a/examples/bootstrap-server/server.go +++ b/examples/bootstrap-server/server.go @@ -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) }