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

feat: support cosign signatures / attestations and discover in zarf prepare find-images #2027

Merged
merged 32 commits into from
Oct 13, 2023
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
968e7f5
add signatures
rjferguson21 Sep 21, 2023
9c40306
Merge branch 'main' into signatures
mjnagel Sep 21, 2023
65eb086
fix: cache, sbom, skipping
mjnagel Sep 22, 2023
fd77f55
chore: move example to examples
mjnagel Sep 25, 2023
ada9357
fix: comment
mjnagel Sep 25, 2023
af0929a
Merge branch 'main' into signatures
Racer159 Sep 26, 2023
d376d39
Merge branch 'main' into signatures
mjnagel Sep 27, 2023
2eee4ce
fix: pr comments
mjnagel Sep 27, 2023
1328b17
docs: additions from autogen
mjnagel Sep 27, 2023
cda70d8
fix: skip cosign for diff test
mjnagel Sep 27, 2023
ac792e9
fix: s
mjnagel Sep 27, 2023
6520970
fix: doh
mjnagel Sep 27, 2023
0696c8a
Merge branch 'main' into signatures
Racer159 Oct 3, 2023
e15af7f
Merge branch 'main' into signatures
mjnagel Oct 4, 2023
87eaf8b
Merge branch 'main' into signatures
mjnagel Oct 5, 2023
44173fc
chore: cleanup things
mjnagel Oct 5, 2023
642f1b6
fix: test
mjnagel Oct 5, 2023
5276585
ci: skip cosign for sbom test
mjnagel Oct 5, 2023
2ae7cbd
fix: ci/cleanup things
mjnagel Oct 6, 2023
3f0b90b
fix: add comment
mjnagel Oct 6, 2023
caed87e
chore: refactor to helpers
mjnagel Oct 6, 2023
95d4d74
ci: test fix length check
mjnagel Oct 6, 2023
dfe133a
chore: rename to cosign util
mjnagel Oct 6, 2023
5bae063
chore: cleanup
mjnagel Oct 6, 2023
f202d1e
ci: fix length checks
mjnagel Oct 6, 2023
219db79
Merge branch 'main' into signatures
mjnagel Oct 12, 2023
b35379b
chore: move to prepare find-images
mjnagel Oct 12, 2023
71b429c
change pull return
mjnagel Oct 12, 2023
2e69b17
cleanups
mjnagel Oct 12, 2023
3f2d56d
updates from pr feedback
mjnagel Oct 12, 2023
062809d
add comment
mjnagel Oct 12, 2023
f61288f
Update src/pkg/utils/cosign.go
mjnagel Oct 12, 2023
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
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ zarf package create [ DIRECTORY ] [flags]
-s, --sbom View SBOM contents after creating the package
--sbom-out string Specify an output directory for the SBOMs from the created Zarf package
--set stringToString Specify package variables to set on the command line (KEY=value) (default [])
--skip-cosign-lookup Skip looking up and pulling cosign artifacts (signatures, attestations, etc) for images in this package
--skip-sbom Skip generating SBOM for this package
```

Expand Down
1 change: 1 addition & 0 deletions src/cmd/common/viper.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ const (
VPkgCreateSbom = "package.create.sbom"
VPkgCreateSbomOutput = "package.create.sbom_output"
VPkgCreateSkipSbom = "package.create.skip_sbom"
VPkgCreateSkipCosignLookup = "package.create.skip_cosign_lookup"
VPkgCreateMaxPackageSize = "package.create.max_package_size"
VPkgCreateSigningKey = "package.create.signing_key"
VPkgCreateSigningKeyPassword = "package.create.signing_key_password"
Expand Down
1 change: 1 addition & 0 deletions src/cmd/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,7 @@ func bindCreateFlags(v *viper.Viper) {
createFlags.StringToStringVar(&pkgConfig.CreateOpts.SetVariables, "set", v.GetStringMapString(common.VPkgCreateSet), lang.CmdPackageCreateFlagSet)
createFlags.BoolVarP(&pkgConfig.CreateOpts.ViewSBOM, "sbom", "s", v.GetBool(common.VPkgCreateSbom), lang.CmdPackageCreateFlagSbom)
createFlags.StringVar(&pkgConfig.CreateOpts.SBOMOutputDir, "sbom-out", v.GetString(common.VPkgCreateSbomOutput), lang.CmdPackageCreateFlagSbomOut)
createFlags.BoolVar(&pkgConfig.CreateOpts.SkipCosignLookup, "skip-cosign-lookup", v.GetBool(common.VPkgCreateSkipCosignLookup), lang.CmdPackageCreateFlagSkipCosignLookup)
createFlags.BoolVar(&pkgConfig.CreateOpts.SkipSBOM, "skip-sbom", v.GetBool(common.VPkgCreateSkipSbom), lang.CmdPackageCreateFlagSkipSbom)
createFlags.IntVarP(&pkgConfig.CreateOpts.MaxPackageSizeMB, "max-package-size", "m", v.GetInt(common.VPkgCreateMaxPackageSize), lang.CmdPackageCreateFlagMaxPackageSize)
createFlags.StringVarP(&pkgConfig.CreateOpts.SigningKeyPath, "key", "k", v.GetString(common.VPkgCreateSigningKey), lang.CmdPackageCreateFlagSigningKey)
Expand Down
1 change: 1 addition & 0 deletions src/config/lang/english.go
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ const (
CmdPackageCreateFlagSbom = "View SBOM contents after creating the package"
CmdPackageCreateFlagSbomOut = "Specify an output directory for the SBOMs from the created Zarf package"
CmdPackageCreateFlagSkipSbom = "Skip generating SBOM for this package"
CmdPackageCreateFlagSkipCosignLookup = "Skip looking up and pulling cosign artifacts (signatures, attestations, etc) for images in this package"
CmdPackageCreateFlagMaxPackageSize = "Specify the maximum size of the package in megabytes, packages larger than this will be split into multiple parts. Use 0 to disable splitting."
CmdPackageCreateFlagSigningKey = "Path to private key file for signing packages"
CmdPackageCreateFlagSigningKeyPassword = "Password to the private key file used for signing packages"
Expand Down
12 changes: 10 additions & 2 deletions src/internal/packager/images/pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -486,8 +486,16 @@ func (i *ImageConfig) PullImage(src string, spinner *message.Spinner) (img v1.Im
}

spinner.Updatef("Preparing image %s", src)
imageCachePath := filepath.Join(config.GetAbsCachePath(), layout.ImagesDir)
img = cache.Image(img, cache.NewFilesystemCache(imageCachePath))

isImage, err := utils.HasImageLayers(img)
if err != nil {
return nil, fmt.Errorf("failed to check image %s layer mediatype: %w", src, err)
}

if isImage {
imageCachePath := filepath.Join(config.GetAbsCachePath(), layout.ImagesDir)
img = cache.Image(img, cache.NewFilesystemCache(imageCachePath))
}

return img, nil
}
34 changes: 32 additions & 2 deletions src/pkg/packager/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,11 @@ func (p *Packager) Create() (err error) {
return err
}

// Add extra artifacts (cosign signatures, etc) to the package config
if err := p.addExtraArtifacts(); err != nil {
return err
}

// After we have a full zarf.yaml remove unnecessary repos and images if we are building a differential package
if p.cfg.CreateOpts.DifferentialData.DifferentialPackagePath != "" {
// Verify the package version of the package we're using as a 'reference' for the differential build is different than the package we're building
Expand Down Expand Up @@ -156,6 +161,7 @@ func (p *Packager) Create() (err error) {
}

imageList := helpers.Unique(combinedImageList)
var sbomImageList []transform.Image

// Images are handled separately from other component assets.
if len(imageList) > 0 {
Expand All @@ -182,10 +188,17 @@ func (p *Packager) Create() (err error) {
return fmt.Errorf("unable to pull images after 3 attempts: %w", err)
}

for _, img := range pulled {
for transform, img := range pulled {
if err := p.layout.Images.AddV1Image(img); err != nil {
return err
}
isImage, err := utils.HasImageLayers(img)
mjnagel marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return fmt.Errorf("failed to check image %s layer mediatype: %w", transform.Name, err)
}
if isImage {
sbomImageList = append(sbomImageList, transform)
}
}
}

Expand All @@ -194,7 +207,7 @@ func (p *Packager) Create() (err error) {
message.Debug("Skipping image SBOM processing per --skip-sbom flag")
} else {
p.layout = p.layout.AddSBOMs()
if err := sbom.Catalog(componentSBOMs, imageList, p.layout); err != nil {
if err := sbom.Catalog(componentSBOMs, sbomImageList, p.layout); err != nil {
return fmt.Errorf("unable to create an SBOM catalog for the package: %w", err)
}
}
Expand Down Expand Up @@ -776,3 +789,20 @@ func (p *Packager) removeCopiesFromDifferentialPackage() error {

return nil
}

func (p *Packager) addExtraArtifacts() (err error) {
for i, c := range p.cfg.Pkg.Components {
var extraArtifactList []string
for _, image := range c.Images {
if !p.cfg.CreateOpts.SkipCosignLookup {
mjnagel marked this conversation as resolved.
Show resolved Hide resolved
cosignArtifacts, err := utils.GetCosignArtifacts(image)
if err != nil {
return err
}
extraArtifactList = append(extraArtifactList, cosignArtifacts...)
}
}
p.cfg.Pkg.Components[i].Images = append(p.cfg.Pkg.Components[i].Images, extraArtifactList...)
}
return nil
}
35 changes: 35 additions & 0 deletions src/pkg/utils/sget.go → src/pkg/utils/cosign.go
Original file line number Diff line number Diff line change
Expand Up @@ -217,3 +217,38 @@

return sig, err
}

func GetCosignArtifacts(image string) (cosignList []string, err error) {

Check warning on line 221 in src/pkg/utils/cosign.go

View workflow job for this annotation

GitHub Actions / validate

exported function GetCosignArtifacts should have comment or be unexported
mjnagel marked this conversation as resolved.
Show resolved Hide resolved
var cosignArtifactList []string
var nameOpts []name.Option
ref, err := name.ParseReference(image, nameOpts...)

if err != nil {
return cosignArtifactList, err
}

var remoteOpts []ociremote.Option
simg, _ := ociremote.SignedEntity(ref, remoteOpts...)
if simg == nil {
return cosignArtifactList, nil
}
sigRef, _ := ociremote.SignatureTag(ref, remoteOpts...)
attRef, _ := ociremote.AttestationTag(ref, remoteOpts...)
mjnagel marked this conversation as resolved.
Show resolved Hide resolved

sigs, err := simg.Signatures()
if err == nil {
layers, _ := sigs.Layers()
if len(layers) > 0 {
cosignArtifactList = append(cosignArtifactList, sigRef.String())
}
}

atts, err := simg.Attestations()
if err == nil {
layers, _ := atts.Layers()
if len(layers) > 0 {
cosignArtifactList = append(cosignArtifactList, attRef.String())
}
}
mjnagel marked this conversation as resolved.
Show resolved Hide resolved
return cosignArtifactList, nil
}
19 changes: 19 additions & 0 deletions src/pkg/utils/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,22 @@ func AddImageNameAnnotation(ociPath string, referenceToDigest map[string]string)
}
return os.WriteFile(indexPath, indexJSONBytes, 0600)
}

// HasImageLayers checks if any layers in the v1.Image are known image layers.
func HasImageLayers(img v1.Image) (bool, error) {
layers, err := img.Layers()
if err != nil {
return false, err
}
for _, layer := range layers {
mediatype, err := layer.MediaType()
if err != nil {
return false, err
}
// Check if mediatype is a known image layer
if mediatype.IsLayer() {
return true, nil
}
}
return false, nil
}
3 changes: 2 additions & 1 deletion src/test/e2e/08_create_differential_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,9 @@ func TestCreateDifferential(t *testing.T) {
expectedImages := []string{
"ghcr.io/stefanprodan/podinfo:latest",
"ghcr.io/defenseunicorns/zarf/agent:v0.26.0",
"ghcr.io/defenseunicorns/zarf/agent:sha256-0da3c9b61dd764a39a433f8c3f967fe534d4f3c517dfb92c43923f51fe16d179.sig",
}
require.Len(t, actualImages, 2, "zarf.yaml from the differential package does not contain the correct number of images")
require.Len(t, actualImages, 3, "zarf.yaml from the differential package does not contain the correct number of images")
for _, expectedImage := range expectedImages {
require.Contains(t, actualImages, expectedImage, fmt.Sprintf("unable to find expected image %s", expectedImage))
}
Expand Down
60 changes: 60 additions & 0 deletions src/test/e2e/10_create_cosign_lookup_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2021-Present The Zarf Authors

// Package test provides e2e tests for Zarf.
package test

import (
"fmt"
"path/filepath"
"testing"

"github.com/defenseunicorns/zarf/src/pkg/layout"
"github.com/defenseunicorns/zarf/src/pkg/utils"
"github.com/defenseunicorns/zarf/src/types"
"github.com/mholt/archiver/v3"
"github.com/stretchr/testify/require"
)

func TestCosignLookup(t *testing.T) {
t.Log("E2E: Cosign lookup")
tmpdir := t.TempDir()

var (
createPath = "src/test/packages/10-cosign-lookup"
packageName = fmt.Sprintf("zarf-package-cosign-lookup-%s.tar.zst", e2e.Arch)
)

e2e.CleanFiles(packageName)

// Create the package
stdOut, stdErr, err := e2e.Zarf("package", "create", createPath, "--confirm")
require.NoError(t, err, stdOut, stdErr)

// Extract the yaml of the differential package
err = archiver.Extract(packageName, layout.ZarfYAML, tmpdir)
require.NoError(t, err, "unable to extract zarf.yaml from the package")

// Load the extracted zarf.yaml specification
var zarfConfig types.ZarfPackage
err = utils.ReadYaml(filepath.Join(tmpdir, layout.ZarfYAML), &zarfConfig)
require.NoError(t, err, "unable to read zarf.yaml from the package")

// Get a list of all images and repos that are inside of the differential package
actualImages := []string{}
for _, component := range zarfConfig.Components {
actualImages = append(actualImages, component.Images...)
}

expectedImages := []string{
"ghcr.io/defenseunicorns/zarf/agent:v0.30.0",
"ghcr.io/defenseunicorns/zarf/agent:sha256-90863c246da361499e8f59dfae728c34b50ee2057e61e06dacdcfad983425c32.sig",
}

require.Len(t, actualImages, 2, "zarf.yaml from the package does not contain the expected number of images")
for _, expectedImage := range expectedImages {
require.Contains(t, actualImages, expectedImage, fmt.Sprintf("unable to find expected image %s", expectedImage))
}

e2e.CleanFiles(packageName)
}
6 changes: 3 additions & 3 deletions src/test/e2e/52_oci_compose_differential_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,9 @@ func (suite *OCIDifferentialSuite) Test_0_Create_Differential_OCI() {
suite.Equal(normalZarfConfig.Components[0].Name, "demo-helm-oci-chart")
suite.Equal(normalZarfConfig.Components[0].Charts[0].URL, "oci://ghcr.io/stefanprodan/charts/podinfo")
suite.Equal(normalZarfConfig.Components[0].Images[0], "ghcr.io/stefanprodan/podinfo:6.4.0")
suite.Len(normalZarfConfig.Components[1].Images, 2)
suite.Len(normalZarfConfig.Components[1].Images, 3)
suite.Len(normalZarfConfig.Components[1].Repos, 2)
suite.Len(normalZarfConfig.Components[2].Images, 1)
suite.Len(normalZarfConfig.Components[2].Images, 2)
suite.Len(normalZarfConfig.Components[2].Repos, 3)

/* Perform a bunch of asserts around the differential package */
Expand All @@ -116,7 +116,7 @@ func (suite *OCIDifferentialSuite) Test_0_Create_Differential_OCI() {
// Check the component data for the differential package
suite.Len(differentialZarfConfig.Components, 2)
suite.Equal(differentialZarfConfig.Components[0].Name, "versioned-assets")
suite.Len(differentialZarfConfig.Components[0].Images, 1)
suite.Len(differentialZarfConfig.Components[0].Images, 2)
suite.Equal(differentialZarfConfig.Components[0].Images[0], "ghcr.io/defenseunicorns/zarf/agent:v0.25.0")
suite.Len(differentialZarfConfig.Components[0].Repos, 1)
suite.Equal(differentialZarfConfig.Components[0].Repos[0], "https://github.com/defenseunicorns/zarf.git@refs/tags/v0.25.0")
Expand Down
9 changes: 9 additions & 0 deletions src/test/packages/10-cosign-lookup/zarf.yaml
mjnagel marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
kind: ZarfPackageConfig
metadata:
name: cosign-lookup
description: Test package for cosign image artifact lookups

components:
- name: test
images:
- ghcr.io/defenseunicorns/zarf/agent:v0.30.0
1 change: 1 addition & 0 deletions src/types/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ type ZarfInitOptions struct {
// ZarfCreateOptions tracks the user-defined options used to create the package.
type ZarfCreateOptions struct {
SkipSBOM bool `json:"skipSBOM" jsonschema:"description=Disable the generation of SBOM materials during package creation"`
SkipCosignLookup bool `json:"skipCosignLookup" jsonschema:"description=Disable the lookup of cosign image artifacts during package creation"`
BaseDir string `json:"baseDir" jsonschema:"description=Location where the Zarf package will be created from"`
Output string `json:"output" jsonschema:"description=Location where the finalized Zarf package will be placed"`
ViewSBOM bool `json:"sbom" jsonschema:"description=Whether to pause to allow for viewing the SBOM post-creation"`
Expand Down
5 changes: 5 additions & 0 deletions src/ui/lib/api-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1231,6 +1231,10 @@ export interface ZarfCreateOptions {
* Location where the private key component of a cosign key-pair can be found
*/
signingKeyPath: string;
/**
* Disable the lookup of cosign image artifacts during package creation
*/
skipCosignLookup: boolean;
/**
* Disable the generation of SBOM materials during package creation
*/
Expand Down Expand Up @@ -1822,6 +1826,7 @@ const typeMap: any = {
{ json: "setVariables", js: "setVariables", typ: m("") },
{ json: "signingKeyPassword", js: "signingKeyPassword", typ: "" },
{ json: "signingKeyPath", js: "signingKeyPath", typ: "" },
{ json: "skipCosignLookup", js: "skipCosignLookup", typ: true },
{ json: "skipSBOM", js: "skipSBOM", typ: true },
], false),
"DifferentialData": o([
Expand Down
Loading