Skip to content

Commit

Permalink
Enhancing Testability with Swappable Interfaces in SPIFFE
Browse files Browse the repository at this point in the history
Why:

This PR introduces swappable interfaces to enhance testability in the SPIFFE package. Previously lacking such interfaces, this change simplifies unit testing and mocking, making our code more reliable.

What is Covered:

Swappable Interfaces: The SPIFFE package now includes interfaces for better unit testing and mocking, facilitating isolated component testing.
Advantages:

Improved Testing: Swappable interfaces empower more comprehensive and reliable testing, speeding up development cycles.

Increased Reliability: Enhanced testing reduces the chances of unexpected issues, ensuring a smoother user experience.

Enhanced Maintainability: Easier issue identification and resolution simplify codebase maintenance.

The goal is to bolster SPIFFE's testability with swappable interfaces, resulting in a more reliable, maintainable, and efficient codebase.

Signed-off-by: naveensrinivasan <[email protected]>
  • Loading branch information
naveensrinivasan committed Oct 22, 2023
1 parent 5e567f0 commit eac999c
Show file tree
Hide file tree
Showing 5 changed files with 380 additions and 17 deletions.
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ require (
github.com/stretchr/testify v1.8.1
github.com/testifysec/archivista-api v0.0.0-20230220215059-632b84b82b76
go.step.sm/crypto v0.25.0
go.uber.org/mock v0.3.0
golang.org/x/sys v0.13.0
google.golang.org/grpc v1.53.0
gopkg.in/square/go-jose.v2 v2.6.0
Expand Down Expand Up @@ -50,7 +51,7 @@ require (
github.com/tchap/go-patricia/v2 v2.3.1 // indirect
github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect
github.com/zclconf/go-cty v1.12.1 // indirect
golang.org/x/mod v0.8.0 // indirect
golang.org/x/mod v0.11.0 // indirect
golang.org/x/oauth2 v0.5.0 // indirect
golang.org/x/tools v0.6.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
Expand Down
6 changes: 4 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,8 @@ github.com/zeebo/errs v1.3.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtC
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.step.sm/crypto v0.25.0 h1:a+7sKyozZH9B30s0dHluygxreUxI1NtCBEmuNXx7a4k=
go.step.sm/crypto v0.25.0/go.mod h1:kr1rzO6SzeQnLm6Zu6lNtksHZLiFe9k8LolSJNhoc94=
go.uber.org/mock v0.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo=
go.uber.org/mock v0.3.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
Expand All @@ -247,8 +249,8 @@ golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU=
golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
Expand Down
101 changes: 87 additions & 14 deletions signer/spiffe/spiffe.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,55 +12,106 @@
// See the License for the specific language governing permissions and
// limitations under the License.

// Package spiffe provides a SPIFFE implementation of the SignerProvider interface.
// It uses the SPIFFE Workload API to fetch SVIDs and uses them to create signers.
package spiffe

import (
"context"
"crypto"
"fmt"

"github.com/spiffe/go-spiffe/v2/svid/x509svid"
"github.com/spiffe/go-spiffe/v2/workloadapi"
"github.com/testifysec/go-witness/cryptoutil"
"github.com/testifysec/go-witness/registry"
"github.com/testifysec/go-witness/signer"
)

// X509SVID interface represents the X509-SVID. It provides a method to get the default SVID.
type X509SVID interface {
// DefaultSVID returns the default SVID.
DefaultSVID() *x509svid.SVID
}

// WorkloadAPI interface represents the Workload API. It provides a method to fetch the X509 context.
type WorkloadAPI interface {
// FetchX509Context fetches the X509 context.
FetchX509Context(ctx context.Context, opts ...workloadapi.ClientOption) (X509SVID, error)
}

// SignerCreator interface represents the Signer Creator. It provides a method to create a new signer.
type SignerCreator interface {
// NewSigner creates a new signer with the provided private key and options.
NewSigner(privateKey crypto.PrivateKey, opts ...cryptoutil.SignerOption) (cryptoutil.Signer, error)
}

// SpiffeSignerProvider struct represents the Spiffe Signer Provider. It contains the socket path, workload API and signer creator.
type SpiffeSignerProvider struct {
SocketPath string
workload WorkloadAPI
signer SignerCreator
}

// ErrInvalidSVID represents an error for invalid SVID. It implements the error interface.
type ErrInvalidSVID string

// Option represents a function that modifies SpiffeSignerProvider. It is used to set the socket path, workload API and signer creator.
type Option func(*SpiffeSignerProvider)

// spiffe struct represents the spiffe and impl. It contains the X509 context.
type spiffe struct {
x509Context *workloadapi.X509Context
}

// init registers the spiffe signer provider.
func init() {
signer.Register("spiffe", func() signer.SignerProvider { return New() },
registry.StringConfigOption(
"socket-path",
"Path to the SPIFFE Workload API Socket",
"",
func(sp signer.SignerProvider, socketPath string) (signer.SignerProvider, error) {
ssp, ok := sp.(SpiffeSignerProvider)
ssp, ok := sp.(*SpiffeSignerProvider)
if !ok {
return sp, fmt.Errorf("provided signer provider is not a spiffe signer provider")
return nil, fmt.Errorf("provided signer provider is not a spiffe signer provider")
}

WithSocketPath(socketPath)(&ssp)
WithSocketPath(socketPath)(ssp)
s := &spiffe{}
ssp.workload, ssp.signer = s, s
return ssp, nil
},
),
)
}

type SpiffeSignerProvider struct {
SocketPath string
}

type ErrInvalidSVID string

// Error method for ErrInvalidSVID. It returns a formatted error message.
func (e ErrInvalidSVID) Error() string {
return fmt.Sprintf("invalid svid: %v", string(e))
}

type Option func(*SpiffeSignerProvider)

// WithSocketPath returns an Option that sets the SocketPath in the SpiffeSignerProvider.
func WithSocketPath(socketPath string) Option {
return func(ssp *SpiffeSignerProvider) {
ssp.SocketPath = socketPath
}
}

// WithWorkloadAPI returns an Option that sets the WorkloadAPI in the SpiffeSignerProvider.
func WithWorkloadAPI(workloadAPI WorkloadAPI) Option {
return func(ssp *SpiffeSignerProvider) {
ssp.workload = workloadAPI
}
}

// WithSignerCreator returns an Option that sets the SignerCreator in the SpiffeSignerProvider.
func WithSignerCreator(signerCreator SignerCreator) Option {
return func(ssp *SpiffeSignerProvider) {
ssp.signer = signerCreator
}
}

// New returns a new SpiffeSignerProvider. It applies the provided options to the SpiffeSignerProvider.
func New(opts ...Option) SpiffeSignerProvider {
ssp := SpiffeSignerProvider{}
for _, opt := range opts {
Expand All @@ -70,12 +121,33 @@ func New(opts ...Option) SpiffeSignerProvider {
return ssp
}

// FetchX509Context fetches the X509 context from the workload API.
func (s *spiffe) FetchX509Context(ctx context.Context, opts ...workloadapi.ClientOption) (X509SVID, error) {
var err error
s.x509Context, err = workloadapi.FetchX509Context(ctx, opts...)
if err != nil {
return nil, err
}
return s.x509Context, nil
}

// DefaultSVID returns the default SVID from the X509 context.
func (s *spiffe) DefaultSVID() *x509svid.SVID {
return s.x509Context.DefaultSVID()
}

// NewSigner creates a new signer with the provided private key and options.
func (c *spiffe) NewSigner(privateKey crypto.PrivateKey, opts ...cryptoutil.SignerOption) (cryptoutil.Signer, error) {
return cryptoutil.NewSigner(privateKey, opts...)
}

// Signer returns a cryptoutil.Signer. It fetches the X509 context from the workload API and creates a new signer with the SVID's private key.
func (ssp SpiffeSignerProvider) Signer(ctx context.Context) (cryptoutil.Signer, error) {
if len(ssp.SocketPath) == 0 {
return nil, fmt.Errorf("socker path cannot be empty")
}

svidCtx, err := workloadapi.FetchX509Context(ctx, workloadapi.WithAddr(ssp.SocketPath))
svidCtx, err := ssp.workload.FetchX509Context(ctx, workloadapi.WithAddr(ssp.SocketPath))
if err != nil {
return nil, err
}
Expand All @@ -89,5 +161,6 @@ func (ssp SpiffeSignerProvider) Signer(ctx context.Context) (cryptoutil.Signer,
return nil, ErrInvalidSVID("no private key")
}

return cryptoutil.NewSigner(svid.PrivateKey, cryptoutil.SignWithIntermediates(svid.Certificates[1:]), cryptoutil.SignWithCertificate(svid.Certificates[0]))
return ssp.signer.NewSigner(svid.PrivateKey,
cryptoutil.SignWithIntermediates(svid.Certificates[1:]), cryptoutil.SignWithCertificate(svid.Certificates[0]))
}
143 changes: 143 additions & 0 deletions signer/spiffe/spiffe_mock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit eac999c

Please sign in to comment.