Skip to content

Commit

Permalink
tlsconfig trace implementation
Browse files Browse the repository at this point in the history
Signed-off-by: Joe Williams <[email protected]>

add time to function call

typo

forgotten imports

aybabtme's suggestions

function name change clean up

use two structs

Signed-off-by: Antoine Grondin <[email protected]>

introduce func. opts. pattern and convert GetCertificate tracing to it

Signed-off-by: Antoine Grondin <[email protected]>

adjust trickle down occurences of tlsconfig.Trace

Signed-off-by: Antoine Grondin <[email protected]>

collapse all option types into 1 for whole tlsconfig package

Signed-off-by: Antoine Grondin <[email protected]>

rework option type and trace mechanism

Signed-off-by: Antoine Grondin <[email protected]>

remove weird go-sum addition

Signed-off-by: Antoine Grondin <[email protected]>

test clean up

Signed-off-by: Antoine Grondin <[email protected]>

Update v2 to beta

Signed-off-by: Andrew Harding <[email protected]>
Signed-off-by: Antoine Grondin <[email protected]>

Print out the expected peer and domains when encountering mismatches

Signed-off-by: Kyle Anderson <[email protected]>
Signed-off-by: Antoine Grondin <[email protected]>

assert that GetCertificate gets called as expected

Signed-off-by: Antoine Grondin <[email protected]>

pass config.Option to every func

Signed-off-by: Antoine Grondin <[email protected]>

pluralize options funcs

Signed-off-by: Antoine Grondin <[email protected]>
  • Loading branch information
Joe Williams committed Sep 15, 2020
1 parent dfb504d commit 0ca6ea6
Show file tree
Hide file tree
Showing 10 changed files with 193 additions and 60 deletions.
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)
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)
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 {
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

0 comments on commit 0ca6ea6

Please sign in to comment.