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

Signing of Porter bundles #3082

Merged
merged 15 commits into from
May 13, 2024
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
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
Loading