Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tlsconfig trace implementation #150

Merged
merged 1 commit into from
Sep 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
# go-spiffe (v1) library [![GoDoc](https://godoc.org/github.com/spiffe/go-spiffe?status.svg)](https://godoc.org/github.com/spiffe/go-spiffe)

# Deprecation Warning

__NOTE:__ This version of the library will be deprecated soon.

The new [v2](./v2) module is currently in alpha release and published under
The [v2](./v2) module is in **beta** and published under
`github.com/spiffe/go-spiffe/v2`, following go module guidelines.

New code should consider using the `v2` module.
**New code should strongly consider using the `v2` module.**

See the [v2 README](./v2) for more details.

# go-spiffe (v1) library [![GoDoc](https://godoc.org/github.com/spiffe/go-spiffe?status.svg)](https://godoc.org/github.com/spiffe/go-spiffe)

## Overview

The go-spiffe project provides two components:
Expand Down
6 changes: 3 additions & 3 deletions spiffe/expect.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ func ExpectAnyPeer() ExpectPeerFunc {
func ExpectPeer(expectedID string) ExpectPeerFunc {
return func(peerID string, _ [][]*x509.Certificate) error {
if peerID != expectedID {
return fmt.Errorf("unexpected peer ID %q", peerID)
return fmt.Errorf("unexpected peer ID %q: expected %q", peerID, expectedID)
}
return nil
}
Expand All @@ -36,7 +36,7 @@ func ExpectPeers(expectedIDs ...string) ExpectPeerFunc {
}
return func(peerID string, _ [][]*x509.Certificate) error {
if _, ok := m[peerID]; !ok {
return fmt.Errorf("unexpected peer ID %q", peerID)
return fmt.Errorf("unexpected peer ID %q: expected one of %q", peerID, expectedIDs)
}
return nil
}
Expand All @@ -47,7 +47,7 @@ func ExpectPeers(expectedIDs ...string) ExpectPeerFunc {
func ExpectPeerInDomain(expectedDomain string) ExpectPeerFunc {
return func(peerID string, _ [][]*x509.Certificate) error {
if domain := getPeerTrustDomain(peerID); domain != expectedDomain {
return fmt.Errorf("unexpected peer trust domain %q", domain)
return fmt.Errorf("unexpected trust domain %q for peer ID %q: expected trust domain %q", domain, peerID, expectedDomain)
}
return nil
}
Expand Down
6 changes: 3 additions & 3 deletions spiffe/expect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,20 @@ func TestExpectPeer(t *testing.T) {
expect := ExpectPeer("spiffe://domain.test/workload1")
assert.NoError(t, expect("spiffe://domain.test/workload1", nil))
assert.EqualError(t, expect("spiffe://domain.test/workload2", nil),
`unexpected peer ID "spiffe://domain.test/workload2"`)
`unexpected peer ID "spiffe://domain.test/workload2": expected "spiffe://domain.test/workload1"`)
}

func TestExpectPeers(t *testing.T) {
expect := ExpectPeers("spiffe://domain.test/workload1", "spiffe://domain.test/workload2")
assert.NoError(t, expect("spiffe://domain.test/workload1", nil))
assert.NoError(t, expect("spiffe://domain.test/workload2", nil))
assert.EqualError(t, expect("spiffe://domain.test/workload3", nil),
`unexpected peer ID "spiffe://domain.test/workload3"`)
`unexpected peer ID "spiffe://domain.test/workload3": expected one of ["spiffe://domain.test/workload1" "spiffe://domain.test/workload2"]`)
}

func TestExpectPeerInDomain(t *testing.T) {
expect := ExpectPeerInDomain("domain1.test")
assert.NoError(t, expect("spiffe://domain1.test/workload", nil))
assert.EqualError(t, expect("spiffe://domain2.test/workload", nil),
`unexpected peer trust domain "domain2.test"`)
`unexpected trust domain "domain2.test" for peer ID "spiffe://domain2.test/workload": expected trust domain "domain1.test"`)
}
2 changes: 1 addition & 1 deletion spiffe/tls_verify_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ func TestVerifyPeerCertificate(t *testing.T) {
chain: peer1,
roots: roots1,
expect: ExpectPeer("spiffe://domain2.test/workload"),
err: `unexpected peer ID "spiffe://domain1.test/workload"`,
err: `unexpected peer ID "spiffe://domain1.test/workload": expected "spiffe://domain2.test/workload"`,
},
{
name: "bad peer id",
Expand Down
4 changes: 2 additions & 2 deletions v2/spiffetls/dial.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,9 @@ func DialWithMode(ctx context.Context, network, addr string, mode DialMode, opti
case tlsClientMode:
tlsconfig.HookTLSClientConfig(tlsConfig, m.bundle, m.authorizer)
joewilliams marked this conversation as resolved.
Show resolved Hide resolved
case mtlsClientMode:
tlsconfig.HookMTLSClientConfig(tlsConfig, m.svid, m.bundle, m.authorizer)
tlsconfig.HookMTLSClientConfig(tlsConfig, m.svid, m.bundle, m.authorizer, opt.tlsoptions...)
case mtlsWebClientMode:
tlsconfig.HookMTLSWebClientConfig(tlsConfig, m.svid, m.roots)
tlsconfig.HookMTLSWebClientConfig(tlsConfig, m.svid, m.roots, opt.tlsoptions...)
default:
return nil, spiffetlsErr.New("unknown client mode: %v", m.mode)
}
Expand Down
4 changes: 2 additions & 2 deletions v2/spiffetls/listen.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,9 @@ func NewListenerWithMode(ctx context.Context, inner net.Listener, mode ListenMod

switch m.mode {
case tlsServerMode:
tlsconfig.HookTLSServerConfig(tlsConfig, m.svid)
tlsconfig.HookTLSServerConfig(tlsConfig, m.svid, opt.tlsoptions...)
case mtlsServerMode:
tlsconfig.HookMTLSServerConfig(tlsConfig, m.svid, m.bundle, m.authorizer)
tlsconfig.HookMTLSServerConfig(tlsConfig, m.svid, m.bundle, m.authorizer, opt.tlsoptions...)
case mtlsWebServerMode:
tlsconfig.HookMTLSWebServerConfig(tlsConfig, m.cert, m.bundle, m.authorizer)
joewilliams marked this conversation as resolved.
Show resolved Hide resolved
default:
Expand Down
17 changes: 17 additions & 0 deletions v2/spiffetls/option.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"crypto/tls"
"net"

"github.com/spiffe/go-spiffe/v2/spiffetls/tlsconfig"
"github.com/zeebo/errs"
)

Expand All @@ -23,12 +24,14 @@ func (fn dialOption) apply(c *dialConfig) {
type dialConfig struct {
baseTLSConf *tls.Config
dialer *net.Dialer
tlsoptions []tlsconfig.Option
}

type listenOption func(*listenConfig)

type listenConfig struct {
baseTLSConf *tls.Config
tlsoptions []tlsconfig.Option
}

func (fn listenOption) apply(c *listenConfig) {
Expand All @@ -44,6 +47,13 @@ func WithDialTLSConfigBase(base *tls.Config) DialOption {
})
}

// WithDialTLSOptions provides options to use for the TLS config.
func WithDialTLSOptions(opts ...tlsconfig.Option) DialOption {
return dialOption(func(c *dialConfig) {
c.tlsoptions = opts
})
}

// WithDialer provides a net dialer to use. If unset, the standard net dialer
// will be used.
func WithDialer(dialer *net.Dialer) DialOption {
Expand All @@ -65,3 +75,10 @@ func WithListenTLSConfigBase(base *tls.Config) ListenOption {
c.baseTLSConf = base
})
}

// WithListenTLSOptions provides options to use when doing Server mTLS.
func WithListenTLSOptions(opts ...tlsconfig.Option) ListenOption {
return listenOption(func(c *listenConfig) {
c.tlsoptions = opts
})
}
113 changes: 78 additions & 35 deletions v2/spiffetls/tlsconfig/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,79 +10,108 @@ import (

// TLSClientConfig returns a TLS configuration which verifies and authorizes
// the server X509-SVID.
func TLSClientConfig(bundle x509bundle.Source, authorizer Authorizer) *tls.Config {
func TLSClientConfig(bundle x509bundle.Source, authorizer Authorizer, opts ...Option) *tls.Config {
config := new(tls.Config)
HookTLSClientConfig(config, bundle, authorizer)
HookTLSClientConfig(config, bundle, authorizer, opts...)
return config
}

// HookTLSClientConfig sets up the TLS configuration to verify and authorize
// the server X509-SVID. If there is an existing callback set for
// VerifyPeerCertificate it will be wrapped by by this package and invoked
// after SPIFFE authentication has completed.
func HookTLSClientConfig(config *tls.Config, bundle x509bundle.Source, authorizer Authorizer) {
func HookTLSClientConfig(config *tls.Config, bundle x509bundle.Source, authorizer Authorizer, opts ...Option) {
resetAuthFields(config)
config.InsecureSkipVerify = true
config.VerifyPeerCertificate = WrapVerifyPeerCertificate(config.VerifyPeerCertificate, bundle, authorizer)
config.VerifyPeerCertificate = WrapVerifyPeerCertificate(config.VerifyPeerCertificate, bundle, authorizer, opts...)
}

// A Option changes the defaults used to by mTLS ClientConfig functions.
type Option interface {
joewilliams marked this conversation as resolved.
Show resolved Hide resolved
apply(*options)
}

type option func(*options)

func (fn option) apply(o *options) { fn(o) }

type options struct {
trace Trace
}

func newOptions(opts []Option) *options {
out := &options{}
for _, opt := range opts {
opt.apply(out)
}
return out
}

// WithTrace will use the provided tracing callbacks
// when various TLS config functions gets invoked.
func WithTrace(trace Trace) Option {
return option(func(opts *options) {
opts.trace = trace
})
}

// MTLSClientConfig returns a TLS configuration which presents an X509-SVID
// to the server and verifies and authorizes the server X509-SVID.
func MTLSClientConfig(svid x509svid.Source, bundle x509bundle.Source, authorizer Authorizer) *tls.Config {
func MTLSClientConfig(svid x509svid.Source, bundle x509bundle.Source, authorizer Authorizer, opts ...Option) *tls.Config {
config := new(tls.Config)
HookMTLSClientConfig(config, svid, bundle, authorizer)
HookMTLSClientConfig(config, svid, bundle, authorizer, opts...)
return config
}

// HookMTLSClientConfig sets up the TLS configuration to present an X509-SVID
// to the server and verify and authorize the server X509-SVID. If there is an
// existing callback set for VerifyPeerCertificate it will be wrapped by by
// this package and invoked after SPIFFE authentication has completed.
func HookMTLSClientConfig(config *tls.Config, svid x509svid.Source, bundle x509bundle.Source, authorizer Authorizer) {
func HookMTLSClientConfig(config *tls.Config, svid x509svid.Source, bundle x509bundle.Source, authorizer Authorizer, opts ...Option) {
resetAuthFields(config)
config.GetClientCertificate = GetClientCertificate(svid)
config.GetClientCertificate = GetClientCertificate(svid, opts...)
config.InsecureSkipVerify = true
config.VerifyPeerCertificate = WrapVerifyPeerCertificate(config.VerifyPeerCertificate, bundle, authorizer)
config.VerifyPeerCertificate = WrapVerifyPeerCertificate(config.VerifyPeerCertificate, bundle, authorizer, opts...)
}

// MTLSWebClientConfig returns a TLS configuration which presents an X509-SVID
// to the server and verifies the server certificate using provided roots (or
// the system roots if nil).
func MTLSWebClientConfig(svid x509svid.Source, roots *x509.CertPool) *tls.Config {
func MTLSWebClientConfig(svid x509svid.Source, roots *x509.CertPool, opts ...Option) *tls.Config {
config := new(tls.Config)
HookMTLSWebClientConfig(config, svid, roots)
HookMTLSWebClientConfig(config, svid, roots, opts...)
return config
}

// HookMTLSWebClientConfig sets up the TLS configuration to present an
// X509-SVID to the server and verifies the server certificate using the
// provided roots (or the system roots if nil).
func HookMTLSWebClientConfig(config *tls.Config, svid x509svid.Source, roots *x509.CertPool) {
func HookMTLSWebClientConfig(config *tls.Config, svid x509svid.Source, roots *x509.CertPool, opts ...Option) {
resetAuthFields(config)
config.GetClientCertificate = GetClientCertificate(svid)
config.GetClientCertificate = GetClientCertificate(svid, opts...)
config.RootCAs = roots
}

// TLSServerConfig returns a TLS configuration which presents an X509-SVID
// to the client and does not require or verify client certificates.
func TLSServerConfig(svid x509svid.Source) *tls.Config {
func TLSServerConfig(svid x509svid.Source, opts ...Option) *tls.Config {
config := new(tls.Config)
HookTLSServerConfig(config, svid)
HookTLSServerConfig(config, svid, opts...)
return config
}

// HookTLSServerConfig sets up the TLS configuration to present an X509-SVID
// to the client and to not require or verify client certificates.
func HookTLSServerConfig(config *tls.Config, svid x509svid.Source) {
func HookTLSServerConfig(config *tls.Config, svid x509svid.Source, opts ...Option) {
resetAuthFields(config)
config.GetCertificate = GetCertificate(svid)
config.GetCertificate = GetCertificate(svid, opts...)
}

// MTLSServerConfig returns a TLS configuration which presents an X509-SVID
// to the client and requires, verifies, and authorizes client X509-SVIDs.
func MTLSServerConfig(svid x509svid.Source, bundle x509bundle.Source, authorizer Authorizer) *tls.Config {
func MTLSServerConfig(svid x509svid.Source, bundle x509bundle.Source, authorizer Authorizer, opts ...Option) *tls.Config {
config := new(tls.Config)
HookMTLSServerConfig(config, svid, bundle, authorizer)
HookMTLSServerConfig(config, svid, bundle, authorizer, opts...)
return config
}

Expand All @@ -91,19 +120,19 @@ func MTLSServerConfig(svid x509svid.Source, bundle x509bundle.Source, authorizer
// there is an existing callback set for VerifyPeerCertificate it will be
// wrapped by by this package and invoked after SPIFFE authentication has
// completed.
func HookMTLSServerConfig(config *tls.Config, svid x509svid.Source, bundle x509bundle.Source, authorizer Authorizer) {
func HookMTLSServerConfig(config *tls.Config, svid x509svid.Source, bundle x509bundle.Source, authorizer Authorizer, opts ...Option) {
resetAuthFields(config)
config.ClientAuth = tls.RequireAnyClientCert
config.GetCertificate = GetCertificate(svid)
config.VerifyPeerCertificate = WrapVerifyPeerCertificate(config.VerifyPeerCertificate, bundle, authorizer)
config.GetCertificate = GetCertificate(svid, opts...)
config.VerifyPeerCertificate = WrapVerifyPeerCertificate(config.VerifyPeerCertificate, bundle, authorizer, opts...)
}

// MTLSWebServerConfig returns a TLS configuration which presents a web
// server certificate to the client and requires, verifies, and authorizes
// client X509-SVIDs.
func MTLSWebServerConfig(cert *tls.Certificate, bundle x509bundle.Source, authorizer Authorizer) *tls.Config {
func MTLSWebServerConfig(cert *tls.Certificate, bundle x509bundle.Source, authorizer Authorizer, opts ...Option) *tls.Config {
config := new(tls.Config)
HookMTLSWebServerConfig(config, cert, bundle, authorizer)
HookMTLSWebServerConfig(config, cert, bundle, authorizer, opts...)
return config
}

Expand All @@ -112,34 +141,36 @@ func MTLSWebServerConfig(cert *tls.Certificate, bundle x509bundle.Source, author
// X509-SVIDs. If there is an existing callback set for VerifyPeerCertificate
// it will be wrapped by by this package and invoked after SPIFFE
// authentication has completed.
func HookMTLSWebServerConfig(config *tls.Config, cert *tls.Certificate, bundle x509bundle.Source, authorizer Authorizer) {
func HookMTLSWebServerConfig(config *tls.Config, cert *tls.Certificate, bundle x509bundle.Source, authorizer Authorizer, opts ...Option) {
resetAuthFields(config)
config.ClientAuth = tls.RequireAnyClientCert
config.Certificates = []tls.Certificate{*cert}
config.VerifyPeerCertificate = WrapVerifyPeerCertificate(config.VerifyPeerCertificate, bundle, authorizer)
config.VerifyPeerCertificate = WrapVerifyPeerCertificate(config.VerifyPeerCertificate, bundle, authorizer, opts...)
}

// GetCertificate returns a GetCertificate callback for tls.Config. It uses the
// given X509-SVID getter to obtain a server X509-SVID for the TLS handshake.
func GetCertificate(svid x509svid.Source) func(*tls.ClientHelloInfo) (*tls.Certificate, error) {
func GetCertificate(svid x509svid.Source, opts ...Option) func(*tls.ClientHelloInfo) (*tls.Certificate, error) {
opt := newOptions(opts)
return func(*tls.ClientHelloInfo) (*tls.Certificate, error) {
return getTLSCertificate(svid)
return getTLSCertificate(svid, opt.trace)
}
}

// GetClientCertificate returns a GetClientCertificate callback for tls.Config.
// It uses the given X509-SVID getter to obtain a client X509-SVID for the TLS
// handshake.
func GetClientCertificate(svid x509svid.Source) func(*tls.CertificateRequestInfo) (*tls.Certificate, error) {
func GetClientCertificate(svid x509svid.Source, opts ...Option) func(*tls.CertificateRequestInfo) (*tls.Certificate, error) {
opt := newOptions(opts)
return func(*tls.CertificateRequestInfo) (*tls.Certificate, error) {
return getTLSCertificate(svid)
return getTLSCertificate(svid, opt.trace)
}
}

// VerifyPeerCertificate returns a VerifyPeerCertificate callback for
// tls.Config. It uses the given bundle source and authorizer to verify and
// authorize X509-SVIDs provided by peers during the TLS handshake.
func VerifyPeerCertificate(bundle x509bundle.Source, authorizer Authorizer) func([][]byte, [][]*x509.Certificate) error {
func VerifyPeerCertificate(bundle x509bundle.Source, authorizer Authorizer, opts ...Option) func([][]byte, [][]*x509.Certificate) error {
return func(raw [][]byte, _ [][]*x509.Certificate) error {
id, certs, err := x509svid.ParseAndVerify(raw, bundle)
if err != nil {
Expand All @@ -154,9 +185,9 @@ func VerifyPeerCertificate(bundle x509bundle.Source, authorizer Authorizer) func
// SPIFFE authentication against the peer certificates using the given bundle and
// authorizer. The wrapped callback will be passed the verified chains.
// Note: TLS clients must set `InsecureSkipVerify` when doing SPIFFE authentication to disable hostname verification.
func WrapVerifyPeerCertificate(wrapped func([][]byte, [][]*x509.Certificate) error, bundle x509bundle.Source, authorizer Authorizer) func([][]byte, [][]*x509.Certificate) error {
func WrapVerifyPeerCertificate(wrapped func([][]byte, [][]*x509.Certificate) error, bundle x509bundle.Source, authorizer Authorizer, opts ...Option) func([][]byte, [][]*x509.Certificate) error {
if wrapped == nil {
return VerifyPeerCertificate(bundle, authorizer)
return VerifyPeerCertificate(bundle, authorizer, opts...)
}

return func(raw [][]byte, _ [][]*x509.Certificate) error {
Expand All @@ -173,10 +204,18 @@ func WrapVerifyPeerCertificate(wrapped func([][]byte, [][]*x509.Certificate) err
}
}

func getTLSCertificate(svid x509svid.Source) (*tls.Certificate, error) {
func getTLSCertificate(svid x509svid.Source, trace Trace) (*tls.Certificate, error) {
var traceVal interface{}
if trace.GetCertificate != nil {
traceVal = trace.GetCertificate()
}

s, err := svid.GetX509SVID()
if err != nil {
return nil, err
if trace.GotCertificate != nil {
trace.GotCertificate(traceVal, GotCertificateInfo{Err: err})
return nil, err
}
}

cert := &tls.Certificate{
Expand All @@ -188,6 +227,10 @@ func getTLSCertificate(svid x509svid.Source) (*tls.Certificate, error) {
cert.Certificate = append(cert.Certificate, svidCert.Raw)
}

if trace.GotCertificate != nil {
trace.GotCertificate(traceVal, GotCertificateInfo{Cert: cert})
}

return cert, nil
}

Expand Down
Loading