forked from TrueCloudLab/certificates
Merge pull request #774 from smallstep/cm-roots
Avoid doing unauthenticated requests on the SDK
This commit is contained in:
commit
ab44fbfb3f
3 changed files with 129 additions and 35 deletions
|
@ -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
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue