Skip to content

Commit bacbf85

Browse files
committed
Add new bootstrap method that creates a listener.
1 parent 984bf8d commit bacbf85

File tree

2 files changed

+122
-1
lines changed

2 files changed

+122
-1
lines changed

ca/bootstrap.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package ca
22

33
import (
44
"context"
5+
"crypto/tls"
6+
"net"
57
"net/http"
68
"strings"
79

@@ -145,3 +147,54 @@ func BootstrapClient(ctx context.Context, token string, options ...TLSOption) (*
145147
Transport: transport,
146148
}, nil
147149
}
150+
151+
// BootstrapListener is a helper function that using the given token returns a
152+
// TLS listener which accepts connections from an inner listener and wraps each
153+
// connection with Server.
154+
//
155+
// Without any extra option the server will be configured for mTLS, it will
156+
// require and verify clients certificates, but options can be used to drop this
157+
// requirement, the most common will be only verify the certs if given with
158+
// ca.VerifyClientCertIfGiven(), or add extra CAs with
159+
// ca.AddClientCA(*x509.Certificate).
160+
//
161+
// Usage:
162+
// inner, err := net.Listen("tcp", ":443")
163+
// if err != nil {
164+
// return nil
165+
// }
166+
// ctx, cancel := context.WithCancel(context.Background())
167+
// defer cancel()
168+
// lis, err := ca.BootstrapListener(ctx, token, inner)
169+
// if err != nil {
170+
// return err
171+
// }
172+
// srv := grpc.NewServer()
173+
// ... // register services
174+
// srv.Serve(lis)
175+
func BootstrapListener(ctx context.Context, token string, inner net.Listener, options ...TLSOption) (net.Listener, error) {
176+
client, err := Bootstrap(token)
177+
if err != nil {
178+
return nil, err
179+
}
180+
181+
req, pk, err := CreateSignRequest(token)
182+
if err != nil {
183+
return nil, err
184+
}
185+
186+
sign, err := client.Sign(req)
187+
if err != nil {
188+
return nil, err
189+
}
190+
191+
// Make sure the tlsConfig have all supported roots on ClientCAs and RootCAs
192+
options = append(options, AddRootsToCAs())
193+
194+
tlsConfig, err := client.GetServerTLSConfig(ctx, sign, pk, options...)
195+
if err != nil {
196+
return nil, err
197+
}
198+
199+
return tls.NewListener(inner, tlsConfig), nil
200+
}

ca/bootstrap_test.go

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@ import (
88
"net/http"
99
"net/http/httptest"
1010
"reflect"
11+
"sync"
1112
"testing"
1213
"time"
1314

1415
"github.com/pkg/errors"
1516
"github.com/smallstep/certificates/api"
1617
"github.com/smallstep/certificates/authority"
17-
1818
"github.com/smallstep/cli/crypto/randutil"
1919
stepJOSE "github.com/smallstep/cli/jose"
2020
jose "gopkg.in/square/go-jose.v2"
@@ -530,3 +530,71 @@ func doReload(ca *CA) error {
530530
newCA.srv.Addr = ca.srv.Addr
531531
return ca.srv.Reload(newCA.srv)
532532
}
533+
534+
func TestBootstrapListener(t *testing.T) {
535+
srv := startCABootstrapServer()
536+
defer srv.Close()
537+
token := func() string {
538+
return generateBootstrapToken(srv.URL, "127.0.0.1", "ef742f95dc0d8aa82d3cca4017af6dac3fce84290344159891952d18c53eefe7")
539+
}
540+
type args struct {
541+
token string
542+
}
543+
tests := []struct {
544+
name string
545+
args args
546+
wantErr bool
547+
}{
548+
{"ok", args{token()}, false},
549+
{"fail", args{"bad-token"}, true},
550+
}
551+
for _, tt := range tests {
552+
t.Run(tt.name, func(t *testing.T) {
553+
inner := newLocalListener()
554+
defer inner.Close()
555+
lis, err := BootstrapListener(context.Background(), tt.args.token, inner)
556+
if (err != nil) != tt.wantErr {
557+
t.Errorf("BootstrapListener() error = %v, wantErr %v", err, tt.wantErr)
558+
return
559+
}
560+
if tt.wantErr {
561+
if lis != nil {
562+
t.Errorf("BootstrapListener() = %v, want nil", lis)
563+
}
564+
return
565+
}
566+
wg := new(sync.WaitGroup)
567+
go func() {
568+
wg.Add(1)
569+
http.Serve(lis, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
570+
w.Write([]byte("ok"))
571+
}))
572+
wg.Done()
573+
}()
574+
defer wg.Wait()
575+
defer lis.Close()
576+
577+
client, err := BootstrapClient(context.Background(), token())
578+
if err != nil {
579+
t.Errorf("BootstrapClient() error = %v", err)
580+
return
581+
}
582+
println("https://" + lis.Addr().String())
583+
resp, err := client.Get("https://" + lis.Addr().String())
584+
if err != nil {
585+
t.Errorf("client.Get() error = %v", err)
586+
return
587+
}
588+
defer resp.Body.Close()
589+
b, err := ioutil.ReadAll(resp.Body)
590+
if err != nil {
591+
t.Errorf("ioutil.ReadAll() error = %v", err)
592+
return
593+
}
594+
if string(b) != "ok" {
595+
t.Errorf("client.Get() = %s, want ok", string(b))
596+
return
597+
}
598+
})
599+
}
600+
}

0 commit comments

Comments
 (0)