mirror of
https://github.com/smallstep/certificates.git
synced 2024-11-15 18:12:59 +00:00
229 lines
6.7 KiB
Go
229 lines
6.7 KiB
Go
package ca
|
|
|
|
import (
|
|
"context"
|
|
"crypto"
|
|
"crypto/tls"
|
|
"net"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/pkg/errors"
|
|
"github.com/smallstep/certificates/api"
|
|
"go.step.sm/crypto/jose"
|
|
)
|
|
|
|
type tokenClaims struct {
|
|
SHA string `json:"sha"`
|
|
jose.Claims
|
|
}
|
|
|
|
// Bootstrap is a helper function that initializes a client with the
|
|
// configuration in the bootstrap token.
|
|
func Bootstrap(token string) (*Client, error) {
|
|
tok, err := jose.ParseSigned(token)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "error parsing token")
|
|
}
|
|
var claims tokenClaims
|
|
if err := tok.UnsafeClaimsWithoutVerification(&claims); err != nil {
|
|
return nil, errors.Wrap(err, "error parsing token")
|
|
}
|
|
|
|
// Validate bootstrap token
|
|
switch {
|
|
case claims.SHA == "":
|
|
return nil, errors.New("invalid bootstrap token: sha claim is not present")
|
|
case !strings.HasPrefix(strings.ToLower(claims.Audience[0]), "http"):
|
|
return nil, errors.New("invalid bootstrap token: aud claim is not a url")
|
|
}
|
|
|
|
return NewClient(claims.Audience[0], WithRootSHA256(claims.SHA))
|
|
}
|
|
|
|
// 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. 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:
|
|
//
|
|
// // 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(ctx context.Context, token string, options ...TLSOption) (*http.Client, error) {
|
|
b, err := createBootstrap(token) //nolint:contextcheck // deeply nested context; temporary
|
|
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, AddRootsToRootCAs())
|
|
}
|
|
|
|
transport, err := b.Client.Transport(ctx, b.SignResponse, b.PrivateKey, options...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &http.Client{
|
|
Transport: transport,
|
|
}, nil
|
|
}
|
|
|
|
// 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.
|
|
//
|
|
// Without any extra option the server will be configured for mTLS, it will
|
|
// require and verify clients certificates, but options can be used to drop this
|
|
// requirement, the most common will be only verify the certs if given with
|
|
// ca.VerifyClientCertIfGiven(), or add extra CAs with
|
|
// ca.AddClientCA(*x509.Certificate).
|
|
//
|
|
// Usage:
|
|
//
|
|
// // 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(ctx context.Context, token string, base *http.Server, options ...TLSOption) (*http.Server, error) {
|
|
if base.TLSConfig != nil {
|
|
return nil, errors.New("server TLSConfig is already set")
|
|
}
|
|
|
|
b, err := createBootstrap(token) //nolint:contextcheck // deeply nested context; temporary
|
|
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
|
|
}
|
|
|
|
base.TLSConfig = tlsConfig
|
|
return base, nil
|
|
}
|
|
|
|
// BootstrapListener is a helper function that using the given token returns a
|
|
// TLS listener which accepts connections from an inner listener and wraps each
|
|
// connection with Server.
|
|
//
|
|
// Without any extra option the server will be configured for mTLS, it will
|
|
// require and verify clients certificates, but options can be used to drop this
|
|
// requirement, the most common will be only verify the certs if given with
|
|
// ca.VerifyClientCertIfGiven(), or add extra CAs with
|
|
// ca.AddClientCA(*x509.Certificate).
|
|
//
|
|
// Usage:
|
|
//
|
|
// inner, err := net.Listen("tcp", ":443")
|
|
// if err != nil {
|
|
// return nil
|
|
// }
|
|
// ctx, cancel := context.WithCancel(context.Background())
|
|
// defer cancel()
|
|
// lis, err := ca.BootstrapListener(ctx, token, inner)
|
|
// if err != nil {
|
|
// return err
|
|
// }
|
|
// srv := grpc.NewServer()
|
|
// ... // register services
|
|
// srv.Serve(lis)
|
|
func BootstrapListener(ctx context.Context, token string, inner net.Listener, options ...TLSOption) (net.Listener, error) {
|
|
b, err := createBootstrap(token) //nolint:contextcheck // deeply nested context; temporary
|
|
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)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
version, err := client.Version()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
req, pk, err := CreateSignRequest(token)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
sign, err := client.Sign(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &bootstrap{
|
|
Client: client,
|
|
RequireClientAuth: version.RequireClientAuthentication,
|
|
SignResponse: sign,
|
|
PrivateKey: pk,
|
|
}, nil
|
|
}
|