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

Enhancing Testability with Swappable Interfaces in SPIFFE #56

Closed
Closed
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
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ require (
github.com/spiffe/go-spiffe/v2 v2.1.7
github.com/stretchr/testify v1.8.4
go.step.sm/crypto v0.25.2
go.uber.org/mock v0.3.0
golang.org/x/sys v0.15.0
google.golang.org/grpc v1.60.1
gopkg.in/square/go-jose.v2 v2.6.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,8 @@ github.com/zclconf/go-cty v1.12.1/go.mod h1:s9IfD1LK5ccNMSWCVFCE2rJfHiZgi7JijgeW
github.com/zeebo/errs v1.3.0 h1:hmiaKqgYZzcVgRL1Vkc1Mn2914BbzB0IBxs+ebeutGs=
github.com/zeebo/errs v1.3.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.uber.org/mock v0.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo=
go.uber.org/mock v0.3.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
go.step.sm/crypto v0.25.2 h1:NgoI3bcNF0iLI+Rwq00brlJyFfMqseLOa8L8No3Daog=
go.step.sm/crypto v0.25.2/go.mod h1:4pUEuZ+4OAf2f70RgW5oRv/rJudibcAAWQg5prC3DT8=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
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/in-toto/go-witness/cryptoutil"
"github.com/in-toto/go-witness/registry"
"github.com/in-toto/go-witness/signer"
"github.com/spiffe/go-spiffe/v2/workloadapi"
)

// 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 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All of this work is great, and is a good start to the process of making our codebase testable. The only real question I have about what is going on here is whether it is necessary to create an interface to wrap cryptoutil.Signer? Could we not manage to implement these changes without this?

// 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]))
}
160 changes: 160 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
Loading