mirror of
https://github.com/anchore/syft.git
synced 2025-11-17 16:33:21 +01:00
Add source.NewFromRegistry function so that the syft attest command can always explicitly ask for an OCIRegistry provider rather than rely on local daemon detection for image sources. Attestation can not be used where local images loaded in a daemon are the source. Digest values for the layer identification step in attestation can sometimes vary across workstations. This fix makes it so that attest is generating an SBOM for, and attesting to, a source that exists in an OCI registry. It should never load a source from a local user docker/podman daemon. Signed-off-by: Christopher Phillips <christopher.phillips@anchore.com> Co-authored-by: Alex Goodman <alex.goodman@anchore.com>
176 lines
4.4 KiB
Go
176 lines
4.4 KiB
Go
package cli
|
|
|
|
import (
|
|
"bufio"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func runAndShow(t *testing.T, cmd *exec.Cmd) {
|
|
t.Helper()
|
|
|
|
stderr, err := cmd.StderrPipe()
|
|
require.NoErrorf(t, err, "could not get stderr: +v", err)
|
|
|
|
stdout, err := cmd.StdoutPipe()
|
|
require.NoErrorf(t, err, "could not get stdout: +v", err)
|
|
|
|
err = cmd.Start()
|
|
require.NoErrorf(t, err, "failed to start cmd: %+v", err)
|
|
|
|
show := func(label string, reader io.ReadCloser) {
|
|
scanner := bufio.NewScanner(reader)
|
|
scanner.Split(bufio.ScanLines)
|
|
for scanner.Scan() {
|
|
t.Logf("%s: %s", label, scanner.Text())
|
|
}
|
|
}
|
|
|
|
show("out", stdout)
|
|
show("err", stderr)
|
|
}
|
|
|
|
func TestCosignWorkflow(t *testing.T) {
|
|
// found under test-fixtures/registry/Makefile
|
|
img := "localhost:5000/attest:latest"
|
|
attestationFile := "attestation.json"
|
|
tests := []struct {
|
|
name string
|
|
syftArgs []string
|
|
cosignAttachArgs []string
|
|
cosignVerifyArgs []string
|
|
env map[string]string
|
|
assertions []traitAssertion
|
|
setup func(*testing.T)
|
|
cleanup func()
|
|
}{
|
|
{
|
|
name: "cosign verify syft attest",
|
|
syftArgs: []string{
|
|
"attest",
|
|
"-o",
|
|
"json",
|
|
img,
|
|
},
|
|
// cosign attach attestation --attestation image_latest_sbom_attestation.json caphill4/attest:latest
|
|
cosignAttachArgs: []string{
|
|
"attach",
|
|
"attestation",
|
|
"--attestation",
|
|
attestationFile,
|
|
img,
|
|
},
|
|
// cosign verify-attestation -key cosign.pub caphill4/attest:latest
|
|
cosignVerifyArgs: []string{
|
|
"verify-attestation",
|
|
"-key",
|
|
"cosign.pub",
|
|
img,
|
|
},
|
|
assertions: []traitAssertion{
|
|
assertSuccessfulReturnCode,
|
|
},
|
|
setup: func(t *testing.T) {
|
|
cwd, err := os.Getwd()
|
|
require.NoErrorf(t, err, "unable to get cwd: %+v", err)
|
|
|
|
// get working directory for local registry
|
|
fixturesPath := filepath.Join(cwd, "test-fixtures", "registry")
|
|
makeTask := filepath.Join(fixturesPath, "Makefile")
|
|
t.Logf("Generating Fixture from 'make %s'", makeTask)
|
|
|
|
cmd := exec.Command("make")
|
|
cmd.Dir = fixturesPath
|
|
runAndShow(t, cmd)
|
|
|
|
var done = make(chan struct{})
|
|
defer close(done)
|
|
for interval := range testRetryIntervals(done) {
|
|
resp, err := http.Get("http://127.0.0.1:5000/v2/")
|
|
if err != nil {
|
|
t.Logf("waiting for registry err=%+v", err)
|
|
} else {
|
|
if resp.StatusCode == http.StatusOK {
|
|
break
|
|
}
|
|
t.Logf("waiting for registry code=%+v", resp.StatusCode)
|
|
}
|
|
|
|
time.Sleep(interval)
|
|
}
|
|
|
|
cmd = exec.Command("make", "push")
|
|
cmd.Dir = fixturesPath
|
|
runAndShow(t, cmd)
|
|
|
|
},
|
|
cleanup: func() {
|
|
cwd, err := os.Getwd()
|
|
assert.NoErrorf(t, err, "unable to get cwd: %+v", err)
|
|
|
|
fixturesPath := filepath.Join(cwd, "test-fixtures", "registry")
|
|
makeTask := filepath.Join(fixturesPath, "Makefile")
|
|
t.Logf("Generating Fixture from 'make %s'", makeTask)
|
|
|
|
// delete attestation file
|
|
os.Remove(attestationFile)
|
|
|
|
cmd := exec.Command("make", "stop")
|
|
cmd.Dir = fixturesPath
|
|
|
|
runAndShow(t, cmd)
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Cleanup(tt.cleanup)
|
|
tt.setup(t)
|
|
pkiCleanup := setupPKI(t, "") // blank password
|
|
defer pkiCleanup()
|
|
|
|
// attest
|
|
cmd, stdout, stderr := runSyft(t, tt.env, tt.syftArgs...)
|
|
for _, traitFn := range tt.assertions {
|
|
traitFn(t, stdout, stderr, cmd.ProcessState.ExitCode())
|
|
}
|
|
checkCmdFailure(t, stdout, stderr, cmd)
|
|
require.NoError(t, os.WriteFile(attestationFile, []byte(stdout), 0666))
|
|
|
|
// attach
|
|
cmd, stdout, stderr = runCosign(t, tt.env, tt.cosignAttachArgs...)
|
|
for _, traitFn := range tt.assertions {
|
|
traitFn(t, stdout, stderr, cmd.ProcessState.ExitCode())
|
|
}
|
|
checkCmdFailure(t, stdout, stderr, cmd)
|
|
|
|
// attest
|
|
cmd, stdout, stderr = runCosign(t, tt.env, tt.cosignAttachArgs...)
|
|
for _, traitFn := range tt.assertions {
|
|
traitFn(t, stdout, stderr, cmd.ProcessState.ExitCode())
|
|
}
|
|
checkCmdFailure(t, stdout, stderr, cmd)
|
|
|
|
})
|
|
}
|
|
}
|
|
|
|
func checkCmdFailure(t testing.TB, stdout, stderr string, cmd *exec.Cmd) {
|
|
require.Falsef(t, t.Failed(), "%s %s trait assertion failed", cmd.Path, strings.Join(cmd.Args, " "))
|
|
if t.Failed() {
|
|
t.Log("STDOUT:\n", stdout)
|
|
t.Log("STDERR:\n", stderr)
|
|
t.Log("COMMAND:", strings.Join(cmd.Args, " "))
|
|
}
|
|
}
|