From 10b3506829e3ba91d4de5ab6ee31484e0f1649b8 Mon Sep 17 00:00:00 2001 From: Darcy Cleaver Date: Tue, 23 Jul 2024 17:31:49 -0600 Subject: [PATCH 1/3] updates to sign, inspect, and deploy signed bundles --- src/cmd/uds.go | 1 + src/cmd/viper.go | 3 + src/config/lang/lang.go | 1 + src/pkg/bundle/create.go | 25 +-------- src/pkg/bundle/inspect.go | 7 ++- src/pkg/bundler/bundler.go | 8 +-- src/pkg/bundler/localbundle.go | 72 ++++++++++++++++++------ src/pkg/bundler/remotebundle.go | 99 ++++++++++++++++++++++++++------- src/test/e2e/commands_test.go | 6 ++ src/test/e2e/ghcr_test.go | 51 +++++++++++++++++ 10 files changed, 207 insertions(+), 66 deletions(-) diff --git a/src/cmd/uds.go b/src/cmd/uds.go index 5762a05b..9aa70b23 100644 --- a/src/cmd/uds.go +++ b/src/cmd/uds.go @@ -238,6 +238,7 @@ func init() { rootCmd.AddCommand(deployCmd) deployCmd.Flags().StringToStringVar(&bundleCfg.DeployOpts.SetVariables, "set", nil, lang.CmdBundleDeployFlagSet) deployCmd.Flags().BoolVarP(&config.CommonOptions.Confirm, "confirm", "c", false, lang.CmdBundleDeployFlagConfirm) + deployCmd.Flags().StringVarP(&bundleCfg.DeployOpts.PublicKeyPath, "key", "k", v.GetString(V_BNDL_DEPLOY_KEY), lang.CmdBundleInspectFlagKey) deployCmd.Flags().StringArrayVarP(&bundleCfg.DeployOpts.Packages, "packages", "p", []string{}, lang.CmdBundleDeployFlagPackages) deployCmd.Flags().BoolVarP(&bundleCfg.DeployOpts.Resume, "resume", "r", false, lang.CmdBundleDeployFlagResume) deployCmd.Flags().IntVar(&bundleCfg.DeployOpts.Retries, "retries", 3, lang.CmdBundleDeployFlagRetries) diff --git a/src/cmd/viper.go b/src/cmd/viper.go index dd7006a9..c02a6fc3 100644 --- a/src/cmd/viper.go +++ b/src/cmd/viper.go @@ -38,6 +38,9 @@ const ( // Bundle pull config keys V_BNDL_PULL_OUTPUT = "bundle.pull.output" V_BNDL_PULL_KEY = "bundle.pull.key" + + // Bundle deploy config keys + V_BNDL_DEPLOY_KEY = "bundle.deploy.key" ) var ( diff --git a/src/config/lang/lang.go b/src/config/lang/lang.go index 3822e70a..458574f8 100644 --- a/src/config/lang/lang.go +++ b/src/config/lang/lang.go @@ -58,6 +58,7 @@ const ( CmdPackageInspectFlagExtractSBOM = "Create a folder of SBOMs contained in the bundle" CmdBundleInspectFlagFindImages = "Derive images from a uds-bundle.yaml file and list them" CmdBundleInspectFlagListVariables = "List all configurable variables in a bundle (including zarf variables)" + CmdBundleInspectSignedNoPublicKey = "The package was signed but no public key was provided, skipping signature validation" // bundle remove CmdBundleRemoveShort = "Remove a bundle that has been deployed already" diff --git a/src/pkg/bundle/create.go b/src/pkg/bundle/create.go index 56fb2f34..2962cf93 100644 --- a/src/pkg/bundle/create.go +++ b/src/pkg/bundle/create.go @@ -14,7 +14,6 @@ import ( "github.com/defenseunicorns/uds-cli/src/pkg/utils" "github.com/defenseunicorns/uds-cli/src/types" zarfConfig "github.com/defenseunicorns/zarf/src/config" - "github.com/defenseunicorns/zarf/src/pkg/interactive" "github.com/defenseunicorns/zarf/src/pkg/message" zarfUtils "github.com/defenseunicorns/zarf/src/pkg/utils" "github.com/pterm/pterm" @@ -66,28 +65,6 @@ func (b *Bundle) Create() error { validateSpinner.Successf("Bundle Validated") pterm.Print() - // sign the bundle if a signing key was provided - if b.cfg.CreateOpts.SigningKeyPath != "" { - // write the bundle to disk so we can sign it - bundlePath := filepath.Join(b.tmp, config.BundleYAML) - if err := zarfUtils.WriteYaml(bundlePath, &b.bundle, 0600); err != nil { - return err - } - - getSigCreatePassword := func(_ bool) ([]byte, error) { - if b.cfg.CreateOpts.SigningKeyPassword != "" { - return []byte(b.cfg.CreateOpts.SigningKeyPassword), nil - } - return interactive.PromptSigPassword() - } - // sign the bundle - signaturePath := filepath.Join(b.tmp, config.BundleYAMLSignature) - _, err := zarfUtils.CosignSignBlob(bundlePath, signaturePath, b.cfg.CreateOpts.SigningKeyPath, getSigCreatePassword) - if err != nil { - return err - } - } - // for dev mode update package ref for local bundles, refs for remote bundles updated on deploy if config.Dev && len(b.cfg.DevDeployOpts.Ref) != 0 { for i, pkg := range b.bundle.Packages { @@ -104,7 +81,7 @@ func (b *Bundle) Create() error { } bundlerClient := bundler.NewBundler(&opts) - return bundlerClient.Create() + return bundlerClient.Create(b.cfg.CreateOpts) } // confirmBundleCreation prompts the user to confirm bundle creation diff --git a/src/pkg/bundle/inspect.go b/src/pkg/bundle/inspect.go index ebbaff3e..49587f3d 100644 --- a/src/pkg/bundle/inspect.go +++ b/src/pkg/bundle/inspect.go @@ -11,8 +11,10 @@ import ( "path/filepath" "strings" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/pkg/oci" "github.com/defenseunicorns/uds-cli/src/config" + "github.com/defenseunicorns/uds-cli/src/config/lang" "github.com/defenseunicorns/uds-cli/src/pkg/sources" "github.com/defenseunicorns/uds-cli/src/pkg/utils" "github.com/defenseunicorns/uds-cli/src/types" @@ -61,7 +63,10 @@ func (b *Bundle) Inspect() error { } // validate the sig (if present) - if err := ValidateBundleSignature(filepaths[config.BundleYAML], filepaths[config.BundleYAMLSignature], b.cfg.InspectOpts.PublicKeyPath); err != nil { + // The package is signed, but no public key was provided + if !helpers.InvalidPath(filepaths[config.BundleYAMLSignature]) && helpers.InvalidPath(b.cfg.InspectOpts.PublicKeyPath) { + message.Warn(lang.CmdBundleInspectSignedNoPublicKey) + } else if err := ValidateBundleSignature(filepaths[config.BundleYAML], filepaths[config.BundleYAMLSignature], b.cfg.InspectOpts.PublicKeyPath); err != nil { return err } diff --git a/src/pkg/bundler/bundler.go b/src/pkg/bundler/bundler.go index 417530b3..2c2d784e 100644 --- a/src/pkg/bundler/bundler.go +++ b/src/pkg/bundler/bundler.go @@ -41,16 +41,16 @@ func NewBundler(opts *Options) *Bundler { } // Create creates a bundle -func (b *Bundler) Create() error { +func (b *Bundler) Create(createOpts types.BundleCreateOptions) error { if utils.IsRegistryURL(b.output) { - remoteBundle := NewRemoteBundle(&RemoteBundleOpts{Bundle: b.bundle, Output: b.output}) - err := remoteBundle.create(nil) + remoteBundle := NewRemoteBundle(&RemoteBundleOpts{Bundle: b.bundle, TmpDstDir: b.tmpDstDir, Output: b.output}) + err := remoteBundle.create(createOpts) if err != nil { return err } } else { localBundle := NewLocalBundle(&LocalBundleOpts{Bundle: b.bundle, TmpDstDir: b.tmpDstDir, SourceDir: b.sourceDir, OutputDir: b.output}) - err := localBundle.create(nil) + err := localBundle.create(createOpts) if err != nil { return err } diff --git a/src/pkg/bundler/localbundle.go b/src/pkg/bundler/localbundle.go index ddba5bd2..8b8593ea 100644 --- a/src/pkg/bundler/localbundle.go +++ b/src/pkg/bundler/localbundle.go @@ -19,6 +19,7 @@ import ( "github.com/defenseunicorns/uds-cli/src/pkg/utils" "github.com/defenseunicorns/uds-cli/src/pkg/utils/boci" "github.com/defenseunicorns/uds-cli/src/types" + "github.com/defenseunicorns/zarf/src/pkg/interactive" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/pkg/zoci" goyaml "github.com/goccy/go-yaml" @@ -27,6 +28,8 @@ import ( "golang.org/x/sync/errgroup" "oras.land/oras-go/v2/content" ocistore "oras.land/oras-go/v2/content/oci" + + zarfUtils "github.com/defenseunicorns/zarf/src/pkg/utils" ) // LocalBundleOpts are the options for creating a local bundle @@ -56,7 +59,7 @@ func NewLocalBundle(opts *LocalBundleOpts) *LocalBundle { } // create creates the bundle and outputs to a local tarball -func (lo *LocalBundle) create(signature []byte) error { +func (lo *LocalBundle) create(createOpts types.BundleCreateOptions) error { bundle := lo.bundle if bundle.Metadata.Architecture == "" { return fmt.Errorf("architecture is required for bundling") @@ -118,6 +121,14 @@ func (lo *LocalBundle) create(signature []byte) error { digest := bundleYAMLDesc.Digest.Encoded() artifactPathMap[filepath.Join(lo.tmpDstDir, config.BlobsDir, digest)] = filepath.Join(config.BlobsDir, digest) + // sign bundle.yaml layer + if createOpts.SigningKeyPath != "" { + rootManifest, artifactPathMap, err = lo.signBundle(createOpts, digest, store, artifactPathMap, rootManifest) + if err != nil { + return err + } + } + // create and push bundle manifest config manifestConfigDesc, err := pushManifestConfig(store, bundle.Metadata, bundle.Build) if err != nil { @@ -142,20 +153,6 @@ func (lo *LocalBundle) create(signature []byte) error { // grab oci-layout artifactPathMap[filepath.Join(lo.tmpDstDir, "oci-layout")] = "oci-layout" - // push the bundle's signature todo: need to understand functionality and add tests - if len(signature) > 0 { - signatureDesc, err := pushBundleSignature(store, signature) - if err != nil { - return err - } - rootManifest.Layers = append(rootManifest.Layers, signatureDesc) - jsonValue, err := utils.JSONValue(signatureDesc) - if err != nil { - return err - } - message.Debug("Pushed", config.BundleYAMLSignature+":", jsonValue) - } - // tag the local bundle artifact // todo: no need to tag the local artifact err = store.Tag(ctx, rootManifestDesc, bundle.Metadata.Version) @@ -297,10 +294,49 @@ jobLoop: return nil } -func pushBundleSignature(store *ocistore.Store, signature []byte) (ocispec.Descriptor, error) { +// signBundle signs the bundle layer +func (lo *LocalBundle) signBundle(createOpts types.BundleCreateOptions, digest string, store *ocistore.Store, artifactPathMap types.PathMap, rootManifest ocispec.Manifest) (ocispec.Manifest, types.PathMap, error) { + getSigCreatePassword := func(_ bool) ([]byte, error) { + if createOpts.SigningKeyPassword != "" { + return []byte(createOpts.SigningKeyPassword), nil + } + if config.CommonOptions.Confirm { + return nil, nil + } + return interactive.PromptSigPassword() + } + // sign the bundle layer + signaturePath := filepath.Join(lo.tmpDstDir, config.BundleYAMLSignature) + _, err := zarfUtils.CosignSignBlob(filepath.Join(lo.tmpDstDir, config.BlobsDir, digest), signaturePath, createOpts.SigningKeyPath, getSigCreatePassword) + if err != nil { + return ocispec.Manifest{}, nil, err + } + + // append uds-bundle.yaml.sig layer to rootManifest and grab path for archiving + signatureDesc, err := pushBundleSignature(store, lo.tmpDstDir) + if err != nil { + return ocispec.Manifest{}, nil, err + } + rootManifest.Layers = append(rootManifest.Layers, signatureDesc) + digest = signatureDesc.Digest.Encoded() + artifactPathMap[filepath.Join(lo.tmpDstDir, config.BlobsDir, digest)] = filepath.Join(config.BlobsDir, digest) + + jsonValue, err := utils.JSONValue(signatureDesc) + if err != nil { + return ocispec.Manifest{}, nil, err + } + message.Debug("Pushed", config.BundleYAMLSignature+":", jsonValue) + return rootManifest, artifactPathMap, nil +} + +func pushBundleSignature(store *ocistore.Store, tmpDstDir string) (ocispec.Descriptor, error) { ctx := context.TODO() - signatureDesc := content.NewDescriptorFromBytes(zoci.ZarfLayerMediaTypeBlob, signature) - err := store.Push(ctx, signatureDesc, bytes.NewReader(signature)) + signatureBytes, err := os.ReadFile(filepath.Join(tmpDstDir, config.BundleYAMLSignature)) + if err != nil { + return ocispec.Descriptor{}, err + } + signatureDesc := content.NewDescriptorFromBytes(zoci.ZarfLayerMediaTypeBlob, signatureBytes) + err = store.Push(ctx, signatureDesc, bytes.NewReader(signatureBytes)) if err != nil { return ocispec.Descriptor{}, err } diff --git a/src/pkg/bundler/remotebundle.go b/src/pkg/bundler/remotebundle.go index 0c9e3079..514aa241 100644 --- a/src/pkg/bundler/remotebundle.go +++ b/src/pkg/bundler/remotebundle.go @@ -7,7 +7,10 @@ package bundler import ( "context" "fmt" + "os" + "path/filepath" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/pkg/oci" "github.com/defenseunicorns/uds-cli/src/config" "github.com/defenseunicorns/uds-cli/src/pkg/bundler/pusher" @@ -18,6 +21,9 @@ import ( "github.com/defenseunicorns/zarf/src/pkg/zoci" goyaml "github.com/goccy/go-yaml" ocispec "github.com/opencontainers/image-spec/specs-go/v1" + + "github.com/defenseunicorns/zarf/src/pkg/interactive" + zarfUtils "github.com/defenseunicorns/zarf/src/pkg/utils" ) // RemoteBundleOpts are the options for creating a remote bundle @@ -44,9 +50,8 @@ func NewRemoteBundle(opts *RemoteBundleOpts) *RemoteBundle { } // create creates the bundle in a remote OCI registry publishes w/ optional signature to the remote repository. -func (r *RemoteBundle) create(signature []byte) error { +func (r *RemoteBundle) create(createOpts types.BundleCreateOptions) error { ctx := context.TODO() - // set the bundle remote's reference from metadata r.output = boci.EnsureOCIPrefix(r.output) ref, err := referenceFromMetadata(r.output, &r.bundle.Metadata) @@ -120,23 +125,6 @@ func (r *RemoteBundle) create(signature []byte) error { message.Debug("Pushed", config.BundleYAML+":", jsonValue) rootManifest.Layers = append(rootManifest.Layers, *bundleYamlDesc) - // push the bundle's signature - if len(signature) > 0 { - bundleYamlSigDesc, err := bundleRemote.PushLayer(ctx, signature, zoci.ZarfLayerMediaTypeBlob) - if err != nil { - return err - } - bundleYamlSigDesc.Annotations = map[string]string{ - ocispec.AnnotationTitle: config.BundleYAMLSignature, - } - rootManifest.Layers = append(rootManifest.Layers, *bundleYamlSigDesc) - jsonValue, err := utils.JSONValue(bundleYamlSigDesc) - if err != nil { - return err - } - message.Debug("Pushed", config.BundleYAMLSignature+":", jsonValue) - } - // push the bundle manifest config configDesc, err := pushManifestConfigFromMetadata(bundleRemote.OrasRemote, &bundle.Metadata, &bundle.Build) if err != nil { @@ -164,6 +152,14 @@ func (r *RemoteBundle) create(signature []byte) error { return err } + // Pull the bundle.yaml, sign it, and repush it to the remote along with the signature + if createOpts.SigningKeyPath != "" { + rootManifestDesc, err = r.signBundle(ctx, bundleRemote, createOpts, rootManifest) + if err != nil { + return err + } + } + // create or update, then push index.json err = boci.UpdateIndex(index, bundleRemote.OrasRemote, bundle, *rootManifestDesc) if err != nil { @@ -182,3 +178,68 @@ func (r *RemoteBundle) create(signature []byte) error { return nil } + +// signBundle signs the bundle.yaml layer and pushes the signature to the remote +func (r *RemoteBundle) signBundle(ctx context.Context, bundleRemote *zoci.Remote, createOpts types.BundleCreateOptions, rootManifest ocispec.Manifest) (*ocispec.Descriptor, error) { + // pull the bundle.yaml + if err := helpers.CreateDirectory(filepath.Join(r.tmpDstDir, config.BlobsDir), 0700); err != nil { + return nil, err + } + layers, err := bundleRemote.PullPaths(context.TODO(), filepath.Join(r.tmpDstDir, config.BlobsDir), config.BundleAlwaysPull) + if err != nil { + return nil, err + } + filepaths := make(types.PathMap) + for _, layer := range layers { + rel := layer.Annotations[ocispec.AnnotationTitle] + abs := filepath.Join(r.tmpDstDir, config.BlobsDir, rel) + absSha := filepath.Join(r.tmpDstDir, config.BlobsDir, layer.Digest.Encoded()) + if err := os.Rename(abs, absSha); err != nil { + return nil, err + } + filepaths[rel] = absSha + } + // sign the bundle.yaml layer + getSigCreatePassword := func(_ bool) ([]byte, error) { + if createOpts.SigningKeyPassword != "" { + return []byte(createOpts.SigningKeyPassword), nil + } + if config.CommonOptions.Confirm { + return nil, nil + } + return interactive.PromptSigPassword() + } + // sign the bundle layer + signaturePath := filepath.Join(r.tmpDstDir, config.BundleYAMLSignature) + _, err = zarfUtils.CosignSignBlob(filepaths[config.BundleYAML], signaturePath, createOpts.SigningKeyPath, getSigCreatePassword) + if err != nil { + return nil, err + } + // push the bundle's signature + signatureBytes, err := os.ReadFile(signaturePath) + if err != nil { + // Handle the error + fmt.Println("Error reading file:", err) + return nil, err + } + + bundleYamlSigDesc, err := bundleRemote.PushLayer(ctx, signatureBytes, zoci.ZarfLayerMediaTypeBlob) + if err != nil { + return nil, err + } + bundleYamlSigDesc.Annotations = map[string]string{ + ocispec.AnnotationTitle: config.BundleYAMLSignature, + } + rootManifest.Layers = append(rootManifest.Layers, *bundleYamlSigDesc) + jsonValue, err := utils.JSONValue(bundleYamlSigDesc) + if err != nil { + return nil, err + } + message.Debug("Pushed", config.BundleYAMLSignature+":", jsonValue) + + rootManifestDesc, err := boci.ToOCIRemote(rootManifest, ocispec.MediaTypeImageManifest, bundleRemote.OrasRemote) + if err != nil { + return nil, err + } + return rootManifestDesc, nil +} diff --git a/src/test/e2e/commands_test.go b/src/test/e2e/commands_test.go index 5e1ed0da..8fbc3ceb 100644 --- a/src/test/e2e/commands_test.go +++ b/src/test/e2e/commands_test.go @@ -140,6 +140,12 @@ func runCmd(t *testing.T, input string) (stdout string, stderr string) { return stdout, stderr } +func runCmdWithErr(input string) (stdout string, stderr string, err error) { + cmd := strings.Split(input, " ") + stdout, stderr, err = e2e.UDS(cmd...) + return stdout, stderr, err +} + func deployPackagesFlag(tarballPath string, packages string) (stdout string, stderr string) { cmd := strings.Split(fmt.Sprintf("deploy %s --confirm -l=debug --packages %s", tarballPath, packages), " ") stdout, stderr, _ = e2e.UDS(cmd...) diff --git a/src/test/e2e/ghcr_test.go b/src/test/e2e/ghcr_test.go index 6de4a123..f7f160de 100644 --- a/src/test/e2e/ghcr_test.go +++ b/src/test/e2e/ghcr_test.go @@ -7,8 +7,10 @@ package test import ( "fmt" "path/filepath" + "strings" "testing" + "github.com/defenseunicorns/uds-cli/src/config/lang" "github.com/stretchr/testify/require" "oras.land/oras-go/v2/registry" ) @@ -76,6 +78,55 @@ func TestBundleCreateRemoteAndDeployGHCR(t *testing.T) { ValidateMultiArchIndex(t, index) } +// test the create -o path +func TestBundleCreateSignedRemoteAndDeployGHCR(t *testing.T) { + deployZarfInit(t) + + bundleDir := "src/test/bundles/06-ghcr" + bundleGHCRPath := "defenseunicorns/packages/uds-cli/test/create-signed-remote" + privateKeyFlag := "--signing-key=src/test/e2e/bundle-test.prv-key" + publicKeyFlag := "--key=src/test/e2e/bundle-test.pub" + registryURL := fmt.Sprintf("ghcr.io/%s", bundleGHCRPath) + bundleRef := registry.Reference{ + Registry: registryURL, + Repository: "ghcr-test", + Reference: "0.0.1", + } + + // create arm64 bundle with private key + cmd := strings.Split(fmt.Sprintf("create %s -o %s %s --confirm -a %s", bundleDir, registryURL, privateKeyFlag, "arm64"), " ") + _, _, err := e2e.UDS(cmd...) + require.NoError(t, err) + + // create amd64 bundle with private key + cmd = strings.Split(fmt.Sprintf("create %s -o %s %s --confirm -a %s", bundleDir, registryURL, privateKeyFlag, "amd64"), " ") + _, _, err = e2e.UDS(cmd...) + require.NoError(t, err) + + // inspect signed bundle with public key + cmd = strings.Split(fmt.Sprintf("inspect %s %s", bundleRef.String(), publicKeyFlag), " ") + _, stderr, err := e2e.UDS(cmd...) + require.NoError(t, err) + require.Contains(t, stderr, "Verified OK") + + // inspect signed bundle without public key + cmd = strings.Split(fmt.Sprintf("inspect %s", bundleRef.String()), " ") + stdout, _, err := e2e.UDS(cmd...) + require.NoError(t, err) + require.Contains(t, stdout, lang.CmdBundleInspectSignedNoPublicKey) + + // Test that we get an error when trying to deploy a package without providing the public key + _, stderr, err = runCmdWithErr(fmt.Sprintf("deploy %s --confirm", bundleRef.String())) + require.Error(t, err) + require.Contains(t, stderr, "failed to validate bundle: package is signed, but no public key was provided") + + // Test that we get don't get an error when trying to deploy a package with a public key + _, stderr = runCmd(t, fmt.Sprintf("deploy %s %s --confirm", bundleRef.String(), publicKeyFlag)) + require.Contains(t, stderr, "succeeded") + + remove(t, bundleRef.String()) +} + // This test requires the following to be published (based on src/test/bundles/06-ghcr/uds-bundle.yaml): // ghcr.io/defenseunicorns/packages/uds/bundles/ghcr-test:0.0.1 // ghcr.io/defenseunicorns/packages/uds/bundles/ghcr-test:0.0.1 From 445b8b9956b8ed19b74d23c0a738e5249391b17a Mon Sep 17 00:00:00 2001 From: Darcy Cleaver Date: Tue, 23 Jul 2024 17:33:05 -0600 Subject: [PATCH 2/3] add test keys and test --- src/test/e2e/bundle-test.prv-key | 11 +++++++ src/test/e2e/bundle-test.pub | 4 +++ src/test/e2e/bundle_signing_test.go | 51 +++++++++++++++++++++++++++++ 3 files changed, 66 insertions(+) create mode 100644 src/test/e2e/bundle-test.prv-key create mode 100644 src/test/e2e/bundle-test.pub create mode 100644 src/test/e2e/bundle_signing_test.go diff --git a/src/test/e2e/bundle-test.prv-key b/src/test/e2e/bundle-test.prv-key new file mode 100644 index 00000000..0befc556 --- /dev/null +++ b/src/test/e2e/bundle-test.prv-key @@ -0,0 +1,11 @@ +-----BEGIN ENCRYPTED COSIGN PRIVATE KEY----- +eyJrZGYiOnsibmFtZSI6InNjcnlwdCIsInBhcmFtcyI6eyJOIjozMjc2OCwiciI6 +OCwicCI6MX0sInNhbHQiOiJTbDhUaGJNOERCKzZwUTNWNFprN1lSWEQ3bU9yemhK +ODMrMmJNY25lZGlzPSJ9LCJjaXBoZXIiOnsibmFtZSI6Im5hY2wvc2VjcmV0Ym94 +Iiwibm9uY2UiOiJsODR0M0JTa1JMa0F4UVU2SkpDVHRKV3FyNW1QU1QxUyJ9LCJj +aXBoZXJ0ZXh0IjoiZjhpMm5ObThhZTdySk80YUx0SHhtRTc2VzlUd0plYjhoNUw0 +Z2p4eFM2dWg0bjhsVHlTTXVQSkc0Q3hldFhPQ0J3ajViaWwvSzZwMlpTY1VIbGNX +M2FsR3JEdEYyQjNXT2NTd2pqdGJ6TWNJY2c4L1ZnWkRGRWdXS0E2aFdmWThRa2c4 +R3JLKzNyZGZYWGt6RkhYdGI3UkJtUWNKQm4zUjlHU21TUkxBYThrNGpDN0hwRDRF +ZDJFQ2lIS29HNnFkb0pYRHdKVE1FbXhnTXc9PSJ9 +-----END ENCRYPTED COSIGN PRIVATE KEY----- diff --git a/src/test/e2e/bundle-test.pub b/src/test/e2e/bundle-test.pub new file mode 100644 index 00000000..3ff78aca --- /dev/null +++ b/src/test/e2e/bundle-test.pub @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEGXxUhVYpuKyWNXFwjaRKiNHQcKyI +wjoIQCI8Th5WS/Bkbmxxbxa7v20c+w9DgyeB450qsGJoaFh+uMhdbSwlCA== +-----END PUBLIC KEY----- diff --git a/src/test/e2e/bundle_signing_test.go b/src/test/e2e/bundle_signing_test.go new file mode 100644 index 00000000..5d637b41 --- /dev/null +++ b/src/test/e2e/bundle_signing_test.go @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023-Present The UDS Authors + +// Package test provides e2e tests for UDS. +package test + +import ( + "fmt" + "os" + "path/filepath" + "testing" + + "github.com/defenseunicorns/uds-cli/src/config/lang" + "github.com/stretchr/testify/require" +) + +func TestChecksumAndSignature(t *testing.T) { + os.Setenv("UDS_CONFIG", filepath.Join("src/test/bundles/09-uds-bundle-yml", "uds-config.yml")) + defer os.Unsetenv("UDS_CONFIG") + + deployZarfInit(t) + e2e.CreateZarfPkg(t, "src/test/packages/nginx", true) + + bundleDir := "src/test/bundles/09-uds-bundle-yml" + bundlePath := filepath.Join(bundleDir, fmt.Sprintf("uds-bundle-yml-example-%s-0.0.1.tar.zst", e2e.Arch)) + privateKeyFlag := "--signing-key=src/test/e2e/bundle-test.prv-key" + publicKeyFlag := "--key=src/test/e2e/bundle-test.pub" + + // Create bundle with private key + runCmd(t, fmt.Sprintf("create %s --confirm %s", bundleDir, privateKeyFlag)) + + // Inspect signed bundle with public key + _, stderr := runCmd(t, fmt.Sprintf("inspect %s %s", bundlePath, publicKeyFlag)) + require.Contains(t, stderr, "Verified OK") + + // Inspect signed bundle without public key + stdout, _, err := runCmdWithErr(fmt.Sprintf("inspect %s", bundlePath)) + require.NoError(t, err) + require.Contains(t, stdout, lang.CmdBundleInspectSignedNoPublicKey) + + // Test that we get an error when trying to deploy a package without providing the public key + _, stderr, err = runCmdWithErr(fmt.Sprintf("deploy %s --confirm", bundlePath)) + require.Error(t, err) + require.Contains(t, stderr, "failed to validate bundle: package is signed, but no public key was provided") + + // Test that we get don't get an error when trying to deploy a package with a public key + _, stderr = runCmd(t, fmt.Sprintf("deploy %s %s --confirm", bundlePath, publicKeyFlag)) + require.Contains(t, stderr, "Loaded bundled Zarf package: nginx") + + remove(t, bundlePath) +} From d3a14968dc0277c205de725d5125eed3406e92db Mon Sep 17 00:00:00 2001 From: Darcy Cleaver Date: Tue, 23 Jul 2024 20:04:54 -0600 Subject: [PATCH 3/3] update deplpy docs with new key option --- docs/command-reference/uds_deploy.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/command-reference/uds_deploy.md b/docs/command-reference/uds_deploy.md index 1c786231..49b54982 100644 --- a/docs/command-reference/uds_deploy.md +++ b/docs/command-reference/uds_deploy.md @@ -16,6 +16,7 @@ uds deploy [BUNDLE_TARBALL|OCI_REF] [flags] ``` -c, --confirm Confirms bundle deployment without prompting. ONLY use with bundles you trust -h, --help help for deploy + -k, --key string Path to a public key file that will be used to validate a signed bundle -p, --packages stringArray Specify which zarf packages you would like to deploy from the bundle. By default all zarf packages in the bundle are deployed. -r, --resume Only deploys packages from the bundle which haven't already been deployed --retries int Specify the number of retries for package deployments (applies to all pkgs in a bundle) (default 3)