Skip to content

Commit e050c5c

Browse files
authored
Merge pull request smallstep#5 from smallstep/bootstrap-context
Add context to bootstrap methods
2 parents 299eea2 + b23e3be commit e050c5c

File tree

5 files changed

+114
-45
lines changed

5 files changed

+114
-45
lines changed

ca/bootstrap.go

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -38,17 +38,34 @@ func Bootstrap(token string) (*Client, error) {
3838
return NewClient(claims.Audience[0], WithRootSHA256(claims.SHA))
3939
}
4040

41-
// BootstrapServer is a helper function that returns an http.Server configured
42-
// with the given address and handler, and prepared to use TLS connections. The
43-
// certificate will automatically rotate if necessary.
41+
// BootstrapServer is a helper function that using the given token returns the
42+
// given http.Server configured with a TLS certificate signed by the Certificate
43+
// Authority. By default the server will kick off a routine that will renew the
44+
// certificate after 2/3rd of the certificate's lifetime has expired.
4445
//
4546
// Usage:
46-
// srv, err := ca.BootstrapServer(":443", token, handler)
47+
// // Default example with certificate rotation.
48+
// srv, err := ca.BootstrapServer(context.Background(), token, &http.Server{
49+
// Addr: ":443",
50+
// Handler: handler,
51+
// })
52+
//
53+
// // Example canceling automatic certificate rotation.
54+
// ctx, cancel := context.WithCancel(context.Background())
55+
// defer cancel()
56+
// srv, err := ca.BootstrapServer(ctx, token, &http.Server{
57+
// Addr: ":443",
58+
// Handler: handler,
59+
// })
4760
// if err != nil {
4861
// return err
4962
// }
5063
// srv.ListenAndServeTLS("", "")
51-
func BootstrapServer(addr, token string, handler http.Handler) (*http.Server, error) {
64+
func BootstrapServer(ctx context.Context, token string, base *http.Server) (*http.Server, error) {
65+
if base.TLSConfig != nil {
66+
return nil, errors.New("server TLSConfig is already set")
67+
}
68+
5269
client, err := Bootstrap(token)
5370
if err != nil {
5471
return nil, err
@@ -64,30 +81,34 @@ func BootstrapServer(addr, token string, handler http.Handler) (*http.Server, er
6481
return nil, err
6582
}
6683

67-
tlsConfig, err := client.GetServerTLSConfig(context.Background(), sign, pk)
84+
tlsConfig, err := client.GetServerTLSConfig(ctx, sign, pk)
6885
if err != nil {
6986
return nil, err
7087
}
7188

72-
return &http.Server{
73-
Addr: addr,
74-
Handler: handler,
75-
TLSConfig: tlsConfig,
76-
}, nil
89+
base.TLSConfig = tlsConfig
90+
return base, nil
7791
}
7892

7993
// BootstrapClient is a helper function that using the given bootstrap token
8094
// return an http.Client configured with a Transport prepared to do TLS
8195
// connections using the client certificate returned by the certificate
82-
// authority. The certificate will automatically rotate if necessary.
96+
// authority. By default the server will kick off a routine that will renew the
97+
// certificate after 2/3rd of the certificate's lifetime has expired.
8398
//
8499
// Usage:
85-
// client, err := ca.BootstrapClient(token)
100+
// // Default example with certificate rotation.
101+
// client, err := ca.BootstrapClient(ctx.Background(), token)
102+
//
103+
// // Example canceling automatic certificate rotation.
104+
// ctx, cancel := context.WithCancel(context.Background())
105+
// defer cancel()
106+
// client, err := ca.BootstrapClient(ctx, token)
86107
// if err != nil {
87108
// return err
88109
// }
89110
// resp, err := client.Get("https://internal.smallstep.com")
90-
func BootstrapClient(token string) (*http.Client, error) {
111+
func BootstrapClient(ctx context.Context, token string) (*http.Client, error) {
91112
client, err := Bootstrap(token)
92113
if err != nil {
93114
return nil, err
@@ -103,7 +124,7 @@ func BootstrapClient(token string) (*http.Client, error) {
103124
return nil, err
104125
}
105126

106-
transport, err := client.Transport(context.Background(), sign, pk)
127+
transport, err := client.Transport(ctx, sign, pk)
107128
if err != nil {
108129
return nil, err
109130
}

ca/bootstrap_test.go

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package ca
22

33
import (
4+
"context"
5+
"crypto/tls"
46
"net/http"
57
"net/http/httptest"
68
"reflect"
@@ -128,25 +130,23 @@ func TestBootstrapServer(t *testing.T) {
128130
token := func() string {
129131
return generateBootstrapToken(srv.URL, "subject", "ef742f95dc0d8aa82d3cca4017af6dac3fce84290344159891952d18c53eefe7")
130132
}
131-
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
132-
w.Write([]byte("ok"))
133-
})
134133
type args struct {
135-
addr string
136-
token string
137-
handler http.Handler
134+
ctx context.Context
135+
token string
136+
base *http.Server
138137
}
139138
tests := []struct {
140139
name string
141140
args args
142141
wantErr bool
143142
}{
144-
{"ok", args{":0", token(), handler}, false},
145-
{"fail", args{":0", "bad-token", handler}, true},
143+
{"ok", args{context.Background(), token(), &http.Server{}}, false},
144+
{"fail", args{context.Background(), "bad-token", &http.Server{}}, true},
145+
{"fail with TLSConfig", args{context.Background(), token(), &http.Server{TLSConfig: &tls.Config{}}}, true},
146146
}
147147
for _, tt := range tests {
148148
t.Run(tt.name, func(t *testing.T) {
149-
got, err := BootstrapServer(tt.args.addr, tt.args.token, tt.args.handler)
149+
got, err := BootstrapServer(tt.args.ctx, tt.args.token, tt.args.base)
150150
if (err != nil) != tt.wantErr {
151151
t.Errorf("BootstrapServer() error = %v, wantErr %v", err, tt.wantErr)
152152
return
@@ -156,8 +156,11 @@ func TestBootstrapServer(t *testing.T) {
156156
t.Errorf("BootstrapServer() = %v, want nil", got)
157157
}
158158
} else {
159-
if !reflect.DeepEqual(got.Addr, tt.args.addr) {
160-
t.Errorf("BootstrapServer() Addr = %v, want %v", got.Addr, tt.args.addr)
159+
expected := &http.Server{
160+
TLSConfig: got.TLSConfig,
161+
}
162+
if !reflect.DeepEqual(got, expected) {
163+
t.Errorf("BootstrapServer() = %v, want %v", got, expected)
161164
}
162165
if got.TLSConfig == nil || got.TLSConfig.ClientCAs == nil || got.TLSConfig.RootCAs == nil || got.TLSConfig.GetCertificate == nil || got.TLSConfig.GetClientCertificate == nil {
163166
t.Errorf("BootstrapServer() invalid TLSConfig = %#v", got.TLSConfig)
@@ -174,19 +177,20 @@ func TestBootstrapClient(t *testing.T) {
174177
return generateBootstrapToken(srv.URL, "subject", "ef742f95dc0d8aa82d3cca4017af6dac3fce84290344159891952d18c53eefe7")
175178
}
176179
type args struct {
180+
ctx context.Context
177181
token string
178182
}
179183
tests := []struct {
180184
name string
181185
args args
182186
wantErr bool
183187
}{
184-
{"ok", args{token()}, false},
185-
{"fail", args{"bad-token"}, true},
188+
{"ok", args{context.Background(), token()}, false},
189+
{"fail", args{context.Background(), "bad-token"}, true},
186190
}
187191
for _, tt := range tests {
188192
t.Run(tt.name, func(t *testing.T) {
189-
got, err := BootstrapClient(tt.args.token)
193+
got, err := BootstrapClient(tt.args.ctx, tt.args.token)
190194
if (err != nil) != tt.wantErr {
191195
t.Errorf("BootstrapClient() error = %v, wantErr %v", err, tt.wantErr)
192196
return

examples/README.md

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -119,13 +119,13 @@ tr, err := client.Transport(ctx, sign, pk)
119119

120120
To run the example you need to start the certificate authority:
121121

122-
```
122+
```sh
123123
certificates $ bin/step-ca examples/pki/config/ca.json
124124
2018/11/02 18:29:25 Serving HTTPS on :9000 ...
125125
```
126126

127127
And just run the client.go with a new token:
128-
```
128+
```sh
129129
certificates $ export STEPPATH=examples/pki
130130
certificates $ export STEP_CA_URL=https://localhost:9000
131131
certificates $ go run examples/basic-client/client.go $(step ca new-token client.smallstep.com)
@@ -140,15 +140,46 @@ server.
140140
The examples directory already contains a sample pki configuration with the
141141
password `password` hardcoded, but you can create your own using `step ca init`.
142142

143-
First we will start the certificate authority:
143+
These examples show the use of other helper methods, they are simple ways to
144+
create TLS configured http.Server and http.Client objects. The methods are
145+
`BootstrapServer` and `BootstrapClient` and they are used like:
146+
147+
```go
148+
// Get a cancelable context to stop the renewal goroutines and timers.
149+
ctx, cancel := context.WithCancel(context.Background())
150+
defer cancel()
151+
// Create an http.Server
152+
srv, err := ca.BootstrapServer(ctx, token, &http.Server{
153+
Addr: ":8443",
154+
Handler: handler,
155+
})
156+
if err != nil {
157+
panic(err)
158+
}
159+
srv.ListenAndServeTLS("", "")
144160
```
161+
162+
```go
163+
// Get a cancelable context to stop the renewal goroutines and timers.
164+
ctx, cancel := context.WithCancel(context.Background())
165+
defer cancel()
166+
// Create an http.Client
167+
client, err := ca.BootstrapClient(ctx, token)
168+
if err != nil {
169+
panic(err)
170+
}
171+
resp, err := client.Get("https://localhost:8443")
172+
```
173+
174+
To run the example first we will start the certificate authority:
175+
```sh
145176
certificates $ bin/step-ca examples/pki/config/ca.json
146177
2018/11/02 18:29:25 Serving HTTPS on :9000 ...
147178
```
148179

149180
We will start the server and we will type `password` when step asks for the
150181
provisioner password:
151-
```
182+
```sh
152183
certificates $ export STEPPATH=examples/pki
153184
certificates $ export STEP_CA_URL=https://localhost:9000
154185
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.
177208
```
178209

179210
But if we use the root certificate it will properly work:
180-
```
211+
```sh
181212
certificates $ curl --cacert examples/pki/secrets/root_ca.crt https://localhost:8443
182213
Hello nobody at 2018-11-03 01:49:25.66912 +0000 UTC!!!
183214
```
@@ -186,7 +217,7 @@ Notice that in the response we see `nobody`, this is because the server didn't
186217
detected a TLS client configuration.
187218

188219
But if we the client with the certificate name Mike we'll see:
189-
```
220+
```sh
190221
certificates $ export STEPPATH=examples/pki
191222
certificates $ export STEP_CA_URL=https://localhost:9000
192223
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.
206237
If we run the server, and inspect the used certificate, we can verify how it
207238
rotates after approximately two thirds of the duration has passed.
208239

209-
```
240+
```sh
210241
certificates $ export STEPPATH=examples/pki
211242
certificates $ export STEP_CA_URL=https://localhost:9000
212243
certificates $ go run examples/bootstrap-server/server.go $(step ca new-token localhost))
@@ -222,6 +253,6 @@ number between 0 and 6.
222253
We can use the following command to check the certificate expiration and to make
223254
sure the certificate changes after 74-80 seconds.
224255

225-
```
256+
```sh
226257
certificates $ step certificate inspect --insecure https://localhost:8443
227258
```

examples/bootstrap-client/client.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package main
22

33
import (
4+
"context"
45
"fmt"
56
"io/ioutil"
67
"os"
@@ -17,7 +18,11 @@ func main() {
1718

1819
token := os.Args[1]
1920

20-
client, err := ca.BootstrapClient(token)
21+
// make sure to cancel the renew goroutine
22+
ctx, cancel := context.WithCancel(context.Background())
23+
defer cancel()
24+
25+
client, err := ca.BootstrapClient(ctx, token)
2126
if err != nil {
2227
panic(err)
2328
}

examples/bootstrap-server/server.go

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package main
22

33
import (
4+
"context"
45
"fmt"
56
"net/http"
67
"os"
@@ -17,13 +18,20 @@ func main() {
1718

1819
token := os.Args[1]
1920

20-
srv, err := ca.BootstrapServer(":8443", token, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
21-
name := "nobody"
22-
if r.TLS != nil && len(r.TLS.PeerCertificates) > 0 {
23-
name = r.TLS.PeerCertificates[0].Subject.CommonName
24-
}
25-
w.Write([]byte(fmt.Sprintf("Hello %s at %s!!!", name, time.Now().UTC())))
26-
}))
21+
// make sure to cancel the renew goroutine
22+
ctx, cancel := context.WithCancel(context.Background())
23+
defer cancel()
24+
25+
srv, err := ca.BootstrapServer(ctx, token, &http.Server{
26+
Addr: ":8443",
27+
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
28+
name := "nobody"
29+
if r.TLS != nil && len(r.TLS.PeerCertificates) > 0 {
30+
name = r.TLS.PeerCertificates[0].Subject.CommonName
31+
}
32+
w.Write([]byte(fmt.Sprintf("Hello %s at %s!!!", name, time.Now().UTC())))
33+
}),
34+
})
2735
if err != nil {
2836
panic(err)
2937
}

0 commit comments

Comments
 (0)