Merge pull request #774 from smallstep/cm-roots

Avoid doing unauthenticated requests on the SDK
This commit is contained in:
Mariano Cano 2021-12-15 12:22:36 -08:00 committed by GitHub
commit ab44fbfb3f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 129 additions and 35 deletions

View file

@ -2,12 +2,14 @@ package ca
import ( import (
"context" "context"
"crypto"
"crypto/tls" "crypto/tls"
"net" "net"
"net/http" "net/http"
"strings" "strings"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/certificates/api"
"go.step.sm/crypto/jose" "go.step.sm/crypto/jose"
) )
@ -58,25 +60,21 @@ func Bootstrap(token string) (*Client, error) {
// } // }
// resp, err := client.Get("https://internal.smallstep.com") // resp, err := client.Get("https://internal.smallstep.com")
func BootstrapClient(ctx context.Context, token string, options ...TLSOption) (*http.Client, error) { func BootstrapClient(ctx context.Context, token string, options ...TLSOption) (*http.Client, error) {
client, err := Bootstrap(token) b, err := createBootstrap(token)
if err != nil { if err != nil {
return nil, err return nil, err
} }
req, pk, err := CreateSignRequest(token) // Make sure the tlsConfig has all supported roots on RootCAs.
if err != nil { //
return nil, err // The roots request is only supported if identity certificates are not
// required. In all cases the current root is also added after applying all
// options too.
if !b.RequireClientAuth {
options = append(options, AddRootsToRootCAs())
} }
sign, err := client.Sign(req) transport, err := b.Client.Transport(ctx, b.SignResponse, b.PrivateKey, options...)
if err != nil {
return nil, err
}
// Make sure the tlsConfig have all supported roots on RootCAs
options = append(options, AddRootsToRootCAs())
transport, err := client.Transport(ctx, sign, pk, options...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -120,25 +118,21 @@ func BootstrapServer(ctx context.Context, token string, base *http.Server, optio
return nil, errors.New("server TLSConfig is already set") return nil, errors.New("server TLSConfig is already set")
} }
client, err := Bootstrap(token) b, err := createBootstrap(token)
if err != nil { if err != nil {
return nil, err return nil, err
} }
req, pk, err := CreateSignRequest(token) // Make sure the tlsConfig has all supported roots on RootCAs.
if err != nil { //
return nil, err // The roots request is only supported if identity certificates are not
// required. In all cases the current root is also added after applying all
// options too.
if !b.RequireClientAuth {
options = append(options, AddRootsToCAs())
} }
sign, err := client.Sign(req) tlsConfig, err := b.Client.GetServerTLSConfig(ctx, b.SignResponse, b.PrivateKey, options...)
if err != nil {
return nil, err
}
// Make sure the tlsConfig have all supported roots on ClientCAs and RootCAs
options = append(options, AddRootsToCAs())
tlsConfig, err := client.GetServerTLSConfig(ctx, sign, pk, options...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -172,11 +166,46 @@ func BootstrapServer(ctx context.Context, token string, base *http.Server, optio
// ... // register services // ... // register services
// srv.Serve(lis) // srv.Serve(lis)
func BootstrapListener(ctx context.Context, token string, inner net.Listener, options ...TLSOption) (net.Listener, error) { func BootstrapListener(ctx context.Context, token string, inner net.Listener, options ...TLSOption) (net.Listener, error) {
b, err := createBootstrap(token)
if err != nil {
return nil, err
}
// Make sure the tlsConfig has all supported roots on RootCAs.
//
// The roots request is only supported if identity certificates are not
// required. In all cases the current root is also added after applying all
// options too.
if !b.RequireClientAuth {
options = append(options, AddRootsToCAs())
}
tlsConfig, err := b.Client.GetServerTLSConfig(ctx, b.SignResponse, b.PrivateKey, options...)
if err != nil {
return nil, err
}
return tls.NewListener(inner, tlsConfig), nil
}
type bootstrap struct {
Client *Client
RequireClientAuth bool
SignResponse *api.SignResponse
PrivateKey crypto.PrivateKey
}
func createBootstrap(token string) (*bootstrap, error) {
client, err := Bootstrap(token) client, err := Bootstrap(token)
if err != nil { if err != nil {
return nil, err return nil, err
} }
version, err := client.Version()
if err != nil {
return nil, err
}
req, pk, err := CreateSignRequest(token) req, pk, err := CreateSignRequest(token)
if err != nil { if err != nil {
return nil, err return nil, err
@ -187,13 +216,10 @@ func BootstrapListener(ctx context.Context, token string, inner net.Listener, op
return nil, err return nil, err
} }
// Make sure the tlsConfig have all supported roots on ClientCAs and RootCAs return &bootstrap{
options = append(options, AddRootsToCAs()) Client: client,
RequireClientAuth: version.RequireClientAuthentication,
tlsConfig, err := client.GetServerTLSConfig(ctx, sign, pk, options...) SignResponse: sign,
if err != nil { PrivateKey: pk,
return nil, err }, nil
}
return tls.NewListener(inner, tlsConfig), nil
} }

View file

@ -8,6 +8,7 @@ import (
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"reflect" "reflect"
"strings"
"sync" "sync"
"testing" "testing"
"time" "time"
@ -15,6 +16,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/certificates/api" "github.com/smallstep/certificates/api"
"github.com/smallstep/certificates/authority" "github.com/smallstep/certificates/authority"
"github.com/smallstep/certificates/errs"
"go.step.sm/crypto/jose" "go.step.sm/crypto/jose"
"go.step.sm/crypto/randutil" "go.step.sm/crypto/randutil"
) )
@ -74,6 +76,30 @@ func startCAServer(configFile string) (*CA, string, error) {
return ca, caURL, nil return ca, caURL, nil
} }
func mTLSMiddleware(next http.Handler, nonAuthenticatedPaths ...string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/version" {
api.JSON(w, api.VersionResponse{
Version: "test",
RequireClientAuthentication: true,
})
return
}
for _, s := range nonAuthenticatedPaths {
if strings.HasPrefix(r.URL.Path, s) || strings.HasPrefix(r.URL.Path, "/1.0"+s) {
next.ServeHTTP(w, r)
}
}
isMTLS := r.TLS != nil && len(r.TLS.PeerCertificates) > 0
if !isMTLS {
api.WriteError(w, errs.Unauthorized("missing peer certificate"))
} else {
next.ServeHTTP(w, r)
}
})
}
func generateBootstrapToken(ca, subject, sha string) string { func generateBootstrapToken(ca, subject, sha string) string {
now := time.Now() now := time.Now()
jwk, err := jose.ReadKey("testdata/secrets/ott_mariano_priv.jwk", jose.WithPassword([]byte("password"))) jwk, err := jose.ReadKey("testdata/secrets/ott_mariano_priv.jwk", jose.WithPassword([]byte("password")))
@ -171,6 +197,15 @@ func TestBootstrapServerWithoutMTLS(t *testing.T) {
token := func() string { token := func() string {
return generateBootstrapToken(srv.URL, "subject", "ef742f95dc0d8aa82d3cca4017af6dac3fce84290344159891952d18c53eefe7") return generateBootstrapToken(srv.URL, "subject", "ef742f95dc0d8aa82d3cca4017af6dac3fce84290344159891952d18c53eefe7")
} }
mtlsServer := startCABootstrapServer()
next := mtlsServer.Config.Handler
mtlsServer.Config.Handler = mTLSMiddleware(next, "/root/", "/sign")
defer mtlsServer.Close()
mtlsToken := func() string {
return generateBootstrapToken(mtlsServer.URL, "subject", "ef742f95dc0d8aa82d3cca4017af6dac3fce84290344159891952d18c53eefe7")
}
type args struct { type args struct {
ctx context.Context ctx context.Context
token string token string
@ -182,6 +217,7 @@ func TestBootstrapServerWithoutMTLS(t *testing.T) {
wantErr bool wantErr bool
}{ }{
{"ok", args{context.Background(), token(), &http.Server{}}, false}, {"ok", args{context.Background(), token(), &http.Server{}}, false},
{"ok mtls", args{context.Background(), mtlsToken(), &http.Server{}}, false},
{"fail", args{context.Background(), "bad-token", &http.Server{}}, true}, {"fail", args{context.Background(), "bad-token", &http.Server{}}, true},
{"fail with TLSConfig", args{context.Background(), token(), &http.Server{TLSConfig: &tls.Config{}}}, true}, {"fail with TLSConfig", args{context.Background(), token(), &http.Server{TLSConfig: &tls.Config{}}}, true},
} }
@ -217,6 +253,15 @@ func TestBootstrapServerWithMTLS(t *testing.T) {
token := func() string { token := func() string {
return generateBootstrapToken(srv.URL, "subject", "ef742f95dc0d8aa82d3cca4017af6dac3fce84290344159891952d18c53eefe7") return generateBootstrapToken(srv.URL, "subject", "ef742f95dc0d8aa82d3cca4017af6dac3fce84290344159891952d18c53eefe7")
} }
mtlsServer := startCABootstrapServer()
next := mtlsServer.Config.Handler
mtlsServer.Config.Handler = mTLSMiddleware(next, "/root/", "/sign")
defer mtlsServer.Close()
mtlsToken := func() string {
return generateBootstrapToken(mtlsServer.URL, "subject", "ef742f95dc0d8aa82d3cca4017af6dac3fce84290344159891952d18c53eefe7")
}
type args struct { type args struct {
ctx context.Context ctx context.Context
token string token string
@ -228,6 +273,7 @@ func TestBootstrapServerWithMTLS(t *testing.T) {
wantErr bool wantErr bool
}{ }{
{"ok", args{context.Background(), token(), &http.Server{}}, false}, {"ok", args{context.Background(), token(), &http.Server{}}, false},
{"ok mtls", args{context.Background(), mtlsToken(), &http.Server{}}, false},
{"fail", args{context.Background(), "bad-token", &http.Server{}}, true}, {"fail", args{context.Background(), "bad-token", &http.Server{}}, true},
{"fail with TLSConfig", args{context.Background(), token(), &http.Server{TLSConfig: &tls.Config{}}}, true}, {"fail with TLSConfig", args{context.Background(), token(), &http.Server{TLSConfig: &tls.Config{}}}, true},
} }
@ -263,6 +309,15 @@ func TestBootstrapClient(t *testing.T) {
token := func() string { token := func() string {
return generateBootstrapToken(srv.URL, "subject", "ef742f95dc0d8aa82d3cca4017af6dac3fce84290344159891952d18c53eefe7") return generateBootstrapToken(srv.URL, "subject", "ef742f95dc0d8aa82d3cca4017af6dac3fce84290344159891952d18c53eefe7")
} }
mtlsServer := startCABootstrapServer()
next := mtlsServer.Config.Handler
mtlsServer.Config.Handler = mTLSMiddleware(next, "/root/", "/sign")
defer mtlsServer.Close()
mtlsToken := func() string {
return generateBootstrapToken(mtlsServer.URL, "subject", "ef742f95dc0d8aa82d3cca4017af6dac3fce84290344159891952d18c53eefe7")
}
type args struct { type args struct {
ctx context.Context ctx context.Context
token string token string
@ -273,6 +328,7 @@ func TestBootstrapClient(t *testing.T) {
wantErr bool wantErr bool
}{ }{
{"ok", args{context.Background(), token()}, false}, {"ok", args{context.Background(), token()}, false},
{"ok mtls", args{context.Background(), mtlsToken()}, false},
{"fail", args{context.Background(), "bad-token"}, true}, {"fail", args{context.Background(), "bad-token"}, true},
} }
for _, tt := range tests { for _, tt := range tests {
@ -541,6 +597,15 @@ func TestBootstrapListener(t *testing.T) {
token := func() string { token := func() string {
return generateBootstrapToken(srv.URL, "127.0.0.1", "ef742f95dc0d8aa82d3cca4017af6dac3fce84290344159891952d18c53eefe7") return generateBootstrapToken(srv.URL, "127.0.0.1", "ef742f95dc0d8aa82d3cca4017af6dac3fce84290344159891952d18c53eefe7")
} }
mtlsServer := startCABootstrapServer()
next := mtlsServer.Config.Handler
mtlsServer.Config.Handler = mTLSMiddleware(next, "/root/", "/sign")
defer mtlsServer.Close()
mtlsToken := func() string {
return generateBootstrapToken(mtlsServer.URL, "127.0.0.1", "ef742f95dc0d8aa82d3cca4017af6dac3fce84290344159891952d18c53eefe7")
}
type args struct { type args struct {
token string token string
} }
@ -550,6 +615,7 @@ func TestBootstrapListener(t *testing.T) {
wantErr bool wantErr bool
}{ }{
{"ok", args{token()}, false}, {"ok", args{token()}, false},
{"ok mtls", args{mtlsToken()}, false},
{"fail", args{"bad-token"}, true}, {"fail", args{"bad-token"}, true},
} }
for _, tt := range tests { for _, tt := range tests {

View file

@ -115,6 +115,7 @@ func AddRootCA(cert *x509.Certificate) TLSOption {
if ctx.Config.RootCAs == nil { if ctx.Config.RootCAs == nil {
ctx.Config.RootCAs = x509.NewCertPool() ctx.Config.RootCAs = x509.NewCertPool()
} }
ctx.hasRootCA = true
ctx.Config.RootCAs.AddCert(cert) ctx.Config.RootCAs.AddCert(cert)
ctx.mutableConfig.AddImmutableRootCACert(cert) ctx.mutableConfig.AddImmutableRootCACert(cert)
return nil return nil
@ -129,6 +130,7 @@ func AddClientCA(cert *x509.Certificate) TLSOption {
if ctx.Config.ClientCAs == nil { if ctx.Config.ClientCAs == nil {
ctx.Config.ClientCAs = x509.NewCertPool() ctx.Config.ClientCAs = x509.NewCertPool()
} }
ctx.hasClientCA = true
ctx.Config.ClientCAs.AddCert(cert) ctx.Config.ClientCAs.AddCert(cert)
ctx.mutableConfig.AddImmutableClientCACert(cert) ctx.mutableConfig.AddImmutableClientCACert(cert)
return nil return nil