Skip to content

Commit

Permalink
Merge pull request #3082 from kichristensen/signing
Browse files Browse the repository at this point in the history
Signing of Porter bundles
  • Loading branch information
kichristensen authored May 13, 2024
2 parents 7600978 + 02dec04 commit 826511a
Show file tree
Hide file tree
Showing 37 changed files with 1,676 additions and 6 deletions.
1 change: 1 addition & 0 deletions cmd/porter/bundle.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ Note: if overrides for registry/tag/reference are provided, this command only re
"viper-key": {"force-overwrite"},
}
f.BoolVar(&opts.AutoBuildDisabled, "autobuild-disabled", false, "Do not automatically build the bundle from source when the last build is out-of-date.")
f.BoolVar(&opts.SignBundle, "sign-bundle", false, "Sign the bundle using the configured signing plugin")

return &cmd
}
Expand Down
1 change: 1 addition & 0 deletions cmd/porter/installations.go
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ The docker driver runs the bundle container using the local Docker host. To use
"Create the installation in the specified namespace. Defaults to the global namespace.")
f.StringSliceVarP(&opts.Labels, "label", "l", nil,
"Associate the specified labels with the installation. May be specified multiple times.")
f.BoolVar(&opts.VerifyBundleBeforeExecution, "verify-bundle", false, "Verify the bundle signature before executing")
addBundleActionFlags(f, opts)

// Allow configuring the --driver flag with runtime-driver, to avoid conflicts with other commands
Expand Down
51 changes: 50 additions & 1 deletion magefile.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ import (
"get.porter.sh/porter/tests/tester"
mageci "github.com/carolynvs/magex/ci"
"github.com/carolynvs/magex/mgx"
magepkg "github.com/carolynvs/magex/pkg"
"github.com/carolynvs/magex/pkg/archive"
"github.com/carolynvs/magex/pkg/downloads"
"github.com/carolynvs/magex/shx"
"github.com/carolynvs/magex/xplat"
"github.com/magefile/mage/mg"
Expand Down Expand Up @@ -567,7 +570,7 @@ func chmodRecursive(name string, mode os.FileMode) error {

// Run integration tests (slow).
func TestIntegration() {
mg.Deps(tests.EnsureTestCluster, copySchema, TryRegisterLocalHostAlias, BuildTestMixin, BuildTestPlugin)
mg.Deps(tests.EnsureTestCluster, copySchema, TryRegisterLocalHostAlias, BuildTestMixin, BuildTestPlugin, EnsureCosign, EnsureNotation)

var run string
runTest := os.Getenv("PORTER_RUN_TEST")
Expand Down Expand Up @@ -723,3 +726,49 @@ func getPorterHome() string {
func SetupDCO() error {
return git.SetupDCO()
}

func EnsureCosign() {
if ok, _ := magepkg.IsCommandAvailable("cosign", "version", "v2.2.2"); ok {
return
}

opts := downloads.DownloadOptions{
UrlTemplate: "https://github.com/sigstore/cosign/releases/download/v{{.VERSION}}/cosign-{{.GOOS}}-{{.GOARCH}}{{.EXT}}",
Name: "cosign",
Version: "2.2.2",
}

if runtime.GOOS == "windows" {
opts.Ext = ".exe"
}

err := downloads.DownloadToGopathBin(opts)
mgx.Must(err)
}

func EnsureNotation() {
if ok, _ := magepkg.IsCommandAvailable("notation", "version", "1.1.0"); ok {
return
}

target := "notation{{.EXT}}"
if runtime.GOOS == "windows" {
target = "notation.exe"
}

opts := archive.DownloadArchiveOptions{
DownloadOptions: downloads.DownloadOptions{
UrlTemplate: "https://github.com/notaryproject/notation/releases/download/v{{.VERSION}}/notation_{{.VERSION}}_{{.GOOS}}_{{.GOARCH}}{{.EXT}}",
Name: "notation",
Version: "1.1.0",
},
ArchiveExtensions: map[string]string{
"linux": ".tar.gz",
"darwin": ".tar.gz",
"windows": ".zip",
},
TargetFileTemplate: target,
}
err := archive.DownloadToGopathBin(opts)
mgx.Must(err)
}
12 changes: 12 additions & 0 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,18 @@ func (c *Config) GetSecretsPlugin(name string) (SecretsPlugin, error) {
return SecretsPlugin{}, errors.New("secrets %q not defined")
}

func (c *Config) GetSigningPlugin(name string) (SigningPlugin, error) {
if c != nil {
for _, cs := range c.Data.SigningPlugin {
if cs.Name == name {
return cs, nil
}
}
}

return SigningPlugin{}, errors.New("signing %q not defined")
}

// GetHomeDir determines the absolute path to the porter home directory.
// Hierarchy of checks:
// - PORTER_HOME
Expand Down
15 changes: 15 additions & 0 deletions pkg/config/datastore.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,21 @@ type Data struct {
// DefaultSecrets to use when one is not specified by a flag.
DefaultSecrets string `mapstructure:"default-secrets"`

// DefaultSigningPlugin is the plugin to use when no plugin is specified.
DefaultSigningPlugin string `mapstructure:"default-signing-plugin"`

// DefaultSigning to use when one is not specified by a flag.
DefaultSigning string `mapstructure:"default-signer"`

// Namespace is the default namespace for commands that do not override it with a flag.
Namespace string `mapstructure:"namespace"`

// SecretsPlugin defined in the configuration file.
SecretsPlugin []SecretsPlugin `mapstructure:"secrets"`

// SigningPlugin defined in the configuration file.
SigningPlugin []SigningPlugin `mapstructure:"signers"`

// Logs are settings related to Porter's log files.
Logs LogConfig `mapstructure:"logs"`

Expand All @@ -94,11 +103,17 @@ func DefaultDataStore() Data {
RuntimeDriver: RuntimeDriverDocker,
DefaultStoragePlugin: "mongodb-docker",
DefaultSecretsPlugin: "host",
DefaultSigningPlugin: "",
Logs: LogConfig{Level: "info"},
Verbosity: DefaultVerbosity,
}
}

// SigningPlugin is the plugin stanza for signing.
type SigningPlugin struct {
PluginConfig `mapstructure:",squash"`
}

// SecretsPlugin is the plugin stanza for secrets.
type SecretsPlugin struct {
PluginConfig `mapstructure:",squash"`
Expand Down
5 changes: 4 additions & 1 deletion pkg/grpc/portergrpc/portergrpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"get.porter.sh/porter/pkg/porter"
"get.porter.sh/porter/pkg/secrets"
secretsplugin "get.porter.sh/porter/pkg/secrets/pluginstore"
"get.porter.sh/porter/pkg/signing"
signingplugin "get.porter.sh/porter/pkg/signing/pluginstore"
"get.porter.sh/porter/pkg/storage"
storageplugin "get.porter.sh/porter/pkg/storage/pluginstore"
"google.golang.org/grpc"
Expand All @@ -30,7 +32,8 @@ func NewPorterServer(cfg *config.Config) (*PorterServer, error) {
func (s *PorterServer) NewConnectionInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
storage := storage.NewPluginAdapter(storageplugin.NewStore(s.PorterConfig))
secretStorage := secrets.NewPluginAdapter(secretsplugin.NewStore(s.PorterConfig))
p := porter.NewFor(s.PorterConfig, storage, secretStorage)
signer := signing.NewPluginAdapter(signingplugin.NewSigner(s.PorterConfig))
p := porter.NewFor(s.PorterConfig, storage, secretStorage, signer)
if _, err := p.Connect(ctx); err != nil {
return nil, err
}
Expand Down
6 changes: 4 additions & 2 deletions pkg/porter/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"get.porter.sh/porter/pkg/mixin"
"get.porter.sh/porter/pkg/plugins"
"get.porter.sh/porter/pkg/secrets"
"get.porter.sh/porter/pkg/signing"
"get.porter.sh/porter/pkg/storage"
"get.porter.sh/porter/pkg/tracing"
"get.porter.sh/porter/pkg/yaml"
Expand Down Expand Up @@ -63,13 +64,14 @@ func NewTestPorter(t *testing.T) *TestPorter {
tc := config.NewTestConfig(t)
testStore := storage.NewTestStore(tc)
testSecrets := secrets.NewTestSecretsProvider()
testSigner := signing.NewTestSigningProvider()
testCredentials := storage.NewTestCredentialProviderFor(t, testStore, testSecrets)
testParameters := storage.NewTestParameterProviderFor(t, testStore, testSecrets)
testCache := cache.NewTestCache(cache.New(tc.Config))
testInstallations := storage.NewTestInstallationProviderFor(t, testStore)
testRegistry := cnabtooci.NewTestRegistry()

p := NewFor(tc.Config, testStore, testSecrets)
p := NewFor(tc.Config, testStore, testSecrets, testSigner)
p.Config = tc.Config
p.Mixins = mixin.NewTestMixinProvider()
p.Plugins = plugins.NewTestPluginProvider()
Expand Down Expand Up @@ -113,7 +115,7 @@ func (p *TestPorter) SetupIntegrationTest() context.Context {
t := p.TestConfig.TestContext.T

// Undo changes above to make a unit test friendly Porter, so we hit the host
p.Porter = NewFor(p.Config, p.TestStore, p.TestSecrets)
p.Porter = NewFor(p.Config, p.TestStore, p.TestSecrets, p.Signer)

// Run the test in a temp directory
ctx, testDir, _ := p.TestConfig.SetupIntegrationTest()
Expand Down
29 changes: 29 additions & 0 deletions pkg/porter/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,35 @@ func (p *Porter) InstallBundle(ctx context.Context, opts InstallOptions) error {
return fmt.Errorf("error saving installation record: %w", err)
}

if opts.VerifyBundleBeforeExecution {
ref, ok, err := i.Bundle.GetBundleReference()
if err != nil {
return err
}
log.Debugf("verifying bundle signature for %s", ref.String())
if !ok {
return log.Errorf("unable to get reference for bundle %s: %w", ref.String(), err)
}
err = p.Signer.Verify(ctx, ref.String())
if err != nil {
return log.Errorf("unable to verify signature: %w", err)
}
log.Debugf("bundle signature verified for %s", ref.String())

bun, err := opts.GetOptions().GetBundleReference(ctx, p)
if err != nil {
return log.Errorf("unable to get bundle reference")
}

invocationImage := bun.Definition.InvocationImages[0].Image
log.Debugf("verifying invocation image signature for %s", invocationImage)
err = p.Signer.Verify(ctx, invocationImage)
if err != nil {
return log.Errorf("unable to verify signature: %w", err)
}
log.Debugf("invocation image signature verified for %s", invocationImage)
}

// Run install using the updated installation record
return p.ExecuteAction(ctx, i, opts)
}
Expand Down
17 changes: 17 additions & 0 deletions pkg/porter/internal_plugins.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ import (
secretsplugins "get.porter.sh/porter/pkg/secrets/plugins"
"get.porter.sh/porter/pkg/secrets/plugins/filesystem"
"get.porter.sh/porter/pkg/secrets/plugins/host"
signingplugins "get.porter.sh/porter/pkg/signing/plugins"
"get.porter.sh/porter/pkg/signing/plugins/cosign"
"get.porter.sh/porter/pkg/signing/plugins/notation"
storageplugins "get.porter.sh/porter/pkg/storage/plugins"
"get.porter.sh/porter/pkg/storage/plugins/mongodb"
"get.porter.sh/porter/pkg/storage/plugins/mongodb_docker"
Expand Down Expand Up @@ -143,5 +146,19 @@ func getInternalPlugins() map[string]InternalPlugin {
return mongodb_docker.NewPlugin(c.Context, pluginCfg)
},
},
notation.PluginKey: {
Interface: signingplugins.PluginInterface,
ProtocolVersion: signingplugins.PluginProtocolVersion,
Create: func(c *config.Config, pluginCfg interface{}) (plugin.Plugin, error) {
return notation.NewPlugin(c.Context, pluginCfg)
},
},
cosign.PluginKey: {
Interface: signingplugins.PluginInterface,
ProtocolVersion: signingplugins.PluginProtocolVersion,
Create: func(c *config.Config, pluginCfg interface{}) (plugin.Plugin, error) {
return cosign.NewPlugin(c.Context, pluginCfg)
},
},
}
}
2 changes: 2 additions & 0 deletions pkg/porter/lifecycle.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ type BundleExecutionOptions struct {
// A cache of the final resolved set of parameters that are passed to the bundle
// Do not use directly, use GetParameters instead.
finalParams map[string]interface{}

VerifyBundleBeforeExecution bool
}

func NewBundleExecutionOptions() *BundleExecutionOptions {
Expand Down
15 changes: 13 additions & 2 deletions pkg/porter/porter.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import (
"get.porter.sh/porter/pkg/plugins"
"get.porter.sh/porter/pkg/secrets"
secretsplugin "get.porter.sh/porter/pkg/secrets/pluginstore"
"get.porter.sh/porter/pkg/signing"
signingplugin "get.porter.sh/porter/pkg/signing/pluginstore"
"get.porter.sh/porter/pkg/storage"
"get.porter.sh/porter/pkg/storage/migrations"
storageplugin "get.porter.sh/porter/pkg/storage/pluginstore"
Expand Down Expand Up @@ -46,24 +48,27 @@ type Porter struct {
CNAB cnabprovider.CNABProvider
Secrets secrets.Store
Storage storage.Provider
Signer signing.Signer
}

// New porter client, initialized with useful defaults.
func New() *Porter {
c := config.New()
storage := storage.NewPluginAdapter(storageplugin.NewStore(c))
secretStorage := secrets.NewPluginAdapter(secretsplugin.NewStore(c))
return NewFor(c, storage, secretStorage)
signer := signing.NewPluginAdapter(signingplugin.NewSigner(c))
return NewFor(c, storage, secretStorage, signer)
}

func NewFor(c *config.Config, store storage.Store, secretStorage secrets.Store) *Porter {
func NewFor(c *config.Config, store storage.Store, secretStorage secrets.Store, signer signing.Signer) *Porter {
cache := cache.New(c)

storageManager := migrations.NewManager(c, store)
installationStorage := storage.NewInstallationStore(storageManager)
credStorage := storage.NewCredentialStore(storageManager, secretStorage)
paramStorage := storage.NewParameterStore(storageManager, secretStorage)
sanitizerService := storage.NewSanitizer(paramStorage, secretStorage)

storageManager.Initialize(sanitizerService) // we have a bit of a dependency problem here that it would be great to figure out eventually

return &Porter{
Expand All @@ -80,6 +85,7 @@ func NewFor(c *config.Config, store storage.Store, secretStorage secrets.Store)
Plugins: plugins.NewPackageManager(c),
CNAB: cnabprovider.NewRuntime(c, installationStorage, credStorage, paramStorage, secretStorage, sanitizerService),
Sanitizer: sanitizerService,
Signer: signer,
}
}

Expand Down Expand Up @@ -133,6 +139,11 @@ func (p *Porter) Close() error {
bigErr = multierror.Append(bigErr, err)
}

err = p.Signer.Close()
if err != nil {
bigErr = multierror.Append(bigErr, err)
}

return bigErr.ErrorOrNil()
}

Expand Down
19 changes: 19 additions & 0 deletions pkg/porter/publish.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ type PublishOptions struct {
Tag string
Registry string
ArchiveFile string
SignBundle bool
}

// Validate performs validation on the publish options
Expand Down Expand Up @@ -215,6 +216,24 @@ func (p *Porter) publishFromFile(ctx context.Context, opts PublishOptions) error
return err
}

if opts.SignBundle {
log.Debugf("signing bundle %s", bundleRef.String())
inImage, err := cnab.CalculateTemporaryImageTag(bundleRef.Reference)
if err != nil {
return log.Errorf("error calculation temporary image tag: %w", err)
}
log.Debugf("Signing invocation image %s.", inImage.String())
err = p.Signer.Sign(context.Background(), inImage.String())
if err != nil {
return log.Errorf("error signing invocation image: %w", err)
}
log.Debugf("Signing bundle artifact %s.", bundleRef.Reference.String())
err = p.Signer.Sign(context.Background(), bundleRef.Reference.String())
if err != nil {
return log.Errorf("error signing bundle artifact: %w", err)
}
}

// Perhaps we have a cached version of a bundle with the same reference, previously pulled
// If so, replace it, as it is most likely out-of-date per this publish
err = p.refreshCachedBundle(bundleRef)
Expand Down
19 changes: 19 additions & 0 deletions pkg/signing/helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package signing

import "get.porter.sh/porter/pkg/signing/plugins/mock"

var _ Signer = &TestSigningProvider{}

type TestSigningProvider struct {
PluginAdapter

signer *mock.Signer
}

func NewTestSigningProvider() TestSigningProvider {
signer := mock.NewSigner()
return TestSigningProvider{
PluginAdapter: NewPluginAdapter(signer),
signer: signer,
}
}
Loading

0 comments on commit 826511a

Please sign in to comment.