Skip to content

Commit

Permalink
adds flags for creating bundle SBOMs
Browse files Browse the repository at this point in the history
  • Loading branch information
UncleGedd committed Sep 5, 2023
1 parent a05cdc2 commit 4346f93
Show file tree
Hide file tree
Showing 12 changed files with 289 additions and 24 deletions.
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
*.vbox
*.zst
*.zim
assets/
build
data/
dist/
Expand All @@ -26,4 +25,5 @@ zarf-sbom/
*.part*
test-*.txt
__debug_bin
docs-website/static/zarf.schema.json
sboms
bundle-sboms
16 changes: 14 additions & 2 deletions src/cmd/bundle.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,12 @@ var bundleInspectCmd = &cobra.Command{
Aliases: []string{"i"},
Short: lang.CmdBundleInspectShort,
Args: cobra.MaximumNArgs(1),
PreRun: firstArgIsEitherOCIorTarball,
PreRun: func(cmd *cobra.Command, args []string) {
firstArgIsEitherOCIorTarball(nil, args)
if cmd.Flag("extract").Value.String() == "true" && cmd.Flag("sbom").Value.String() == "false" {
message.Fatal(nil, "cannot use 'extract' flag without 'sbom' flag")
}
},
Run: func(cmd *cobra.Command, args []string) {
bundleCfg.InspectOpts.Source = choosePackage(args)
configureZarf()
Expand Down Expand Up @@ -144,7 +149,7 @@ var bundlePullCmd = &cobra.Command{
Args: cobra.ExactArgs(1),
PreRun: func(cmd *cobra.Command, args []string) {
if err := oci.ValidateReference(args[0]); err != nil {
message.Fatalf(err, "First agument (%q) must be a valid OCI URL: %s", args[0], err.Error())
message.Fatalf(err, "First argument (%q) must be a valid OCI URL: %s", args[0], err.Error())
}
},
Run: func(cmd *cobra.Command, args []string) {
Expand Down Expand Up @@ -186,25 +191,32 @@ func init() {
v.SetDefault(V_BNDL_OCI_CONCURRENCY, 3)
bundleCmd.PersistentFlags().IntVar(&config.CommonOptions.OCIConcurrency, "oci-concurrency", v.GetInt(V_BNDL_OCI_CONCURRENCY), lang.CmdBundleFlagConcurrency)

// create cmd flags
bundleCmd.AddCommand(bundleCreateCmd)
bundleCreateCmd.Flags().BoolVarP(&config.CommonOptions.Confirm, "confirm", "c", false, lang.CmdBundleRemoveFlagConfirm)
bundleCreateCmd.Flags().StringVarP(&bundleCfg.CreateOpts.Output, "output", "o", v.GetString(V_BNDL_CREATE_OUTPUT), lang.CmdBundleCreateFlagOutput)
bundleCreateCmd.Flags().StringVarP(&bundleCfg.CreateOpts.SigningKeyPath, "signing-key", "k", v.GetString(V_BNDL_CREATE_SIGNING_KEY), lang.CmdBundleCreateFlagSigningKey)
bundleCreateCmd.Flags().StringVarP(&bundleCfg.CreateOpts.SigningKeyPassword, "signing-key-password", "p", v.GetString(V_BNDL_CREATE_SIGNING_KEY_PASSWORD), lang.CmdBundleCreateFlagSigningKeyPassword)
bundleCreateCmd.Flags().StringToStringVarP(&bundleCfg.CreateOpts.SetVariables, "set", "s", v.GetStringMapString(V_BNDL_CREATE_SET), lang.CmdBundleCreateFlagSet)

// deploy cmd flags
bundleCmd.AddCommand(bundleDeployCmd)
// todo: add "set" flag on deploy for high-level bundle configs?
bundleDeployCmd.Flags().BoolVarP(&config.CommonOptions.Confirm, "confirm", "c", false, lang.CmdBundleDeployFlagConfirm)

// inspect cmd flags
bundleCmd.AddCommand(bundleInspectCmd)
bundleInspectCmd.Flags().BoolVarP(&bundleCfg.InspectOpts.IncludeSBOM, "sbom", "s", false, lang.CmdPackageInspectFlagSBOM)
bundleInspectCmd.Flags().BoolVarP(&bundleCfg.InspectOpts.ExtractSBOM, "extract", "e", false, lang.CmdPackageInspectFlagExtractSBOM)
bundleInspectCmd.Flags().StringVarP(&bundleCfg.InspectOpts.PublicKeyPath, "key", "k", v.GetString(V_BNDL_INSPECT_KEY), lang.CmdBundleInspectFlagKey)

// remove cmd flags
bundleCmd.AddCommand(bundleRemoveCmd)
// confirm does not use the Viper config
bundleRemoveCmd.Flags().BoolVarP(&config.CommonOptions.Confirm, "confirm", "c", false, lang.CmdBundleRemoveFlagConfirm)
_ = bundleRemoveCmd.MarkFlagRequired("confirm")

// pull cmd flags
bundleCmd.AddCommand(bundlePullCmd)
bundlePullCmd.Flags().StringVarP(&bundleCfg.PullOpts.OutputDirectory, "output", "o", v.GetString(V_BNDL_PULL_OUTPUT), lang.CmdBundlePullFlagOutput)
bundlePullCmd.Flags().StringVarP(&bundleCfg.PullOpts.PublicKeyPath, "key", "k", v.GetString(V_BNDL_PULL_KEY), lang.CmdBundlePullFlagKey)
Expand Down
9 changes: 9 additions & 0 deletions src/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,15 @@ const (

// BundlePrefix is the prefix for compiled uds bundles
BundlePrefix = "uds-bundle-"

// SBOMsTar is the sboms.tar file in a Zarf pkg
SBOMsTar = "sboms.tar"

// BundleSBOMTar is the name of the tarball containing the bundle's SBOM
BundleSBOMTar = "bundle-sboms.tar"

// BundleSBOM is the name of the untarred folder containing the bundle's SBOM
BundleSBOM = "bundle-sboms"
)

var (
Expand Down
11 changes: 9 additions & 2 deletions src/config/lang/lang.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,23 +19,30 @@ const (
CmdBundleShort = "Commands for creating, deploying, removing, pulling, and inspecting bundles"
CmdBundleFlagConcurrency = "Number of concurrent layer operations to perform when interacting with a remote bundle."

// bundle create
CmdBundleCreateShort = "Create a bundle from a given directory or the current directory"
//CmdBundleCreateFlagConfirm = "Confirm bundle creation without prompting"
CmdBundleCreateFlagOutput = "Specify the output (an oci:// URL) for the created bundle"
CmdBundleCreateFlagSigningKey = "Path to private key file for signing bundles"
CmdBundleCreateFlagSigningKeyPassword = "Password to the private key file used for signing bundles"
CmdBundleCreateFlagSet = "Specify bundle template variables to set on the command line (KEY=value)"

// bundle deploy
CmdBundleDeployShort = "Deploy a bundle from a local tarball or oci:// URL"
//CmdBundleDeployFlagSet = "Specify deployment variables to set on the command line (KEY=value)"
CmdBundleDeployFlagConfirm = "Confirms bundle deployment without prompting. ONLY use with bundles you trust. Skips prompts to review SBOM, configure variables, select optional components and review potential breaking changes."

CmdBundleInspectShort = "Display the metadata of a bundle"
CmdBundleInspectFlagKey = "Path to a public key file that will be used to validate a signed bundle"
// bundle inspect
CmdBundleInspectShort = "Display the metadata of a bundle"
CmdBundleInspectFlagKey = "Path to a public key file that will be used to validate a signed bundle"
CmdPackageInspectFlagSBOM = "Create a tarball of SBOMs contained in the bundle"
CmdPackageInspectFlagExtractSBOM = "Create a folder of SBOMs contained in the bundle"

// bundle remove
CmdBundleRemoveShort = "Remove a bundle that has been deployed already"
CmdBundleRemoveFlagConfirm = "REQUIRED. Confirm the removal action to prevent accidental deletions"

// bundle pull
CmdBundlePullShort = "Pull a bundle from a remote registry and save to the local file system"
CmdBundlePullFlagOutput = "Specify the output directory for the pulled bundle"
CmdBundlePullFlagKey = "Path to a public key file that will be used to validate a signed bundle"
Expand Down
11 changes: 9 additions & 2 deletions src/pkg/bundle/inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ func (b *Bundler) Inspect() error {
return err
}

// pull the bundle's metadata + sig
// pull the bundle's metadata + sig + sboms (optional)
loaded, err := provider.LoadBundleMetadata()
if err != nil {
return err
Expand All @@ -30,6 +30,13 @@ func (b *Bundler) Inspect() error {
return err
}

// pull sbom
if b.cfg.InspectOpts.IncludeSBOM {
err := provider.CreateBundleSBOM(b.cfg.InspectOpts.ExtractSBOM)
if err != nil {
return err
}
}
// read the bundle's metadata into memory
if err := utils.ReadYaml(loaded[config.BundleYAML], &b.bundle); err != nil {
return err
Expand All @@ -38,7 +45,7 @@ func (b *Bundler) Inspect() error {
// show the bundle's metadata
utils.ColorPrintYAML(b.bundle, nil, false)

// TODO: showing SBOMs?
// todo: validate checksums in package (zarf checksum.go)
// TODO: showing package metadata?
// TODO: could be cool to have an interactive mode that lets you select a package and show its metadata
return nil
Expand Down
3 changes: 3 additions & 0 deletions src/pkg/bundle/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ type Provider interface {
// (currently only the remote provider utilizes the concurrency parameter)
LoadBundle(concurrency int) (PathMap, error)

// CreateBundleSBOM creates a bundle-level SBOM from the underlying Zarf packages, if the Zarf package contains an SBOM
CreateBundleSBOM(extractSBOM bool) error

getBundleManifest() error
}

Expand Down
71 changes: 68 additions & 3 deletions src/pkg/bundle/remote.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,20 @@
package bundle

import (
"bytes"
"context"
"fmt"
"github.com/defenseunicorns/uds-cli/src/config"
"github.com/defenseunicorns/uds-cli/src/pkg/utils"
"github.com/mholt/archiver/v4"
"os"
"path/filepath"
"strings"

"github.com/defenseunicorns/uds-cli/src/types"
"github.com/defenseunicorns/zarf/src/pkg/message"
"github.com/defenseunicorns/zarf/src/pkg/oci"
"github.com/defenseunicorns/zarf/src/pkg/utils"
zarfUtils "github.com/defenseunicorns/zarf/src/pkg/utils"
goyaml "github.com/goccy/go-yaml"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"oras.land/oras-go/v2/content/file"
Expand Down Expand Up @@ -103,13 +106,14 @@ func (op *ociProvider) LoadPackage(sha, destinationDir string, concurrency int)

// LoadBundleMetadata loads a remote bundle's metadata
func (op *ociProvider) LoadBundleMetadata() (PathMap, error) {
if err := utils.CreateDirectory(filepath.Join(op.dst, config.BlobsDir), 0700); err != nil {
if err := zarfUtils.CreateDirectory(filepath.Join(op.dst, config.BlobsDir), 0700); err != nil {
return nil, err
}
layers, err := op.PullPackagePaths(BundleAlwaysPull, filepath.Join(op.dst, config.BlobsDir))
if err != nil {
return nil, err
}

loaded := make(PathMap)
for _, layer := range layers {
rel := layer.Annotations[ocispec.AnnotationTitle]
Expand All @@ -123,9 +127,70 @@ func (op *ociProvider) LoadBundleMetadata() (PathMap, error) {
return loaded, nil
}

// CreateBundleSBOM creates a bundle-level SBOM from the underlying Zarf packages, if the Zarf package contains an SBOM
func (op *ociProvider) CreateBundleSBOM(extractSBOM bool) error {
SBOMArtifactPathMap := make(PathMap)
root, err := op.FetchRoot()
if err != nil {
return err
}
// make tmp dir for pkg SBOM extraction
err = os.Mkdir(filepath.Join(op.dst, config.BundleSBOM), 0700)
if err != nil {
return err
}
// iterate through Zarf image manifests and find the Zarf pkg's sboms.tar
for _, layer := range root.Layers {
zarfManifest, err := op.OrasRemote.FetchManifest(layer)
if err != nil {
continue
}
sbomDesc := zarfManifest.Locate(config.SBOMsTar)
zarfYAML, err := op.OrasRemote.FetchZarfYAML(zarfManifest)
if err != nil {
return err
}
if sbomDesc.Annotations == nil {
message.Warnf("%s not found in Zarf pkg: %s", config.SBOMsTar, zarfYAML.Metadata.Name)
}
if err != nil {
return err
}
sbomBytes, err := op.OrasRemote.FetchLayer(sbomDesc)
if err != nil {
return err
}
// extract sboms.tar to tmp dir
format := archiver.Tar{}
extractor := utils.SBOMExtractor(op.dst, SBOMArtifactPathMap)
err = format.Extract(context.TODO(), bytes.NewReader(sbomBytes), nil, extractor)
if err != nil {
return err
}
}
if extractSBOM {
tmpSBOMPath := filepath.Join(op.dst, config.BundleSBOM)
currentDir, err := os.Getwd()
if err != nil {
return nil
}
sbomDir := filepath.Join(currentDir, config.BundleSBOM)
err = os.Rename(tmpSBOMPath, sbomDir)
if err != nil {
return nil
}
} else {
err = utils.CreateSBOMArtifact(err, SBOMArtifactPathMap)
if err != nil {
return err
}
}
return nil
}

// LoadBundle loads a bundle from a remote source
func (op *ociProvider) LoadBundle(concurrency int) (PathMap, error) {
layersToPull := []ocispec.Descriptor{}
var layersToPull []ocispec.Descriptor

if err := op.getBundleManifest(); err != nil {
return nil, err
Expand Down
Loading

0 comments on commit 4346f93

Please sign in to comment.