Merge pull request #5 from smallstep/bootstrap-context
Add context to bootstrap methods
This commit is contained in:
commit
e050c5cf0f
5 changed files with 114 additions and 45 deletions
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
```
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue