mirror of
https://github.com/anchore/syft.git
synced 2025-11-18 08:53:15 +01:00
fix: update attestation code to remove library dependencies and shellout for keyless flow (#1442)
Co-authored-by: Alex Goodman <alex.goodman@anchore.com>
This commit is contained in:
parent
ac8f72fdd1
commit
44e8ae2577
@ -367,6 +367,8 @@ syft convert sbom.syft.json -o cyclonedx-json=sbom.cdx.json # convert it to Cyc
|
|||||||
### Keyless support
|
### Keyless support
|
||||||
Syft supports generating attestations using cosign's [keyless](https://github.com/sigstore/cosign/blob/main/KEYLESS.md) signatures.
|
Syft supports generating attestations using cosign's [keyless](https://github.com/sigstore/cosign/blob/main/KEYLESS.md) signatures.
|
||||||
|
|
||||||
|
Note: users need to have >= v1.12.0 of cosign installed for this command to function
|
||||||
|
|
||||||
To use this feature with a format like CycloneDX json simply run:
|
To use this feature with a format like CycloneDX json simply run:
|
||||||
```
|
```
|
||||||
syft attest --output cyclonedx-json <IMAGE WITH OCI WRITE ACCESS>
|
syft attest --output cyclonedx-json <IMAGE WITH OCI WRITE ACCESS>
|
||||||
|
|||||||
@ -4,7 +4,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
sigopts "github.com/sigstore/cosign/cmd/cosign/cli/options"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
|
||||||
@ -15,33 +14,27 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
attestExample = ` {{.appName}} {{.command}} --output [FORMAT] --key [KEY] alpine:latest
|
attestExample = ` {{.appName}} {{.command}} --output [FORMAT] alpine:latest defaults to using images from a Docker daemon. If Docker is not present, the image is pulled directly from the registry
|
||||||
Supports the following image sources:
|
|
||||||
{{.appName}} {{.command}} --key [KEY] yourrepo/yourimage:tag defaults to using images from a Docker daemon. If Docker is not present, the image is pulled directly from the registry.
|
|
||||||
{{.appName}} {{.command}} --key [KEY] path/to/a/file/or/dir only for OCI tar or OCI directory
|
|
||||||
`
|
`
|
||||||
attestSchemeHelp = "\n" + indent + schemeHelpHeader + "\n" + imageSchemeHelp
|
attestSchemeHelp = "\n" + indent + schemeHelpHeader + "\n" + imageSchemeHelp
|
||||||
|
attestHelp = attestExample + attestSchemeHelp
|
||||||
attestHelp = attestExample + attestSchemeHelp
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func Attest(v *viper.Viper, app *config.Application, ro *options.RootOptions) *cobra.Command {
|
//nolint:dupl
|
||||||
ao := options.AttestOptions{}
|
func Attest(v *viper.Viper, app *config.Application, ro *options.RootOptions, po *options.PackagesOptions) *cobra.Command {
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "attest --output [FORMAT] --key [KEY] [SOURCE]",
|
Use: "attest --output [FORMAT] <IMAGE>",
|
||||||
Short: "Generate a package SBOM as an attestation for the given [SOURCE] container image",
|
Short: "Generate an SBOM as an attestation for the given [SOURCE] container image",
|
||||||
Long: "Generate a packaged-based Software Bill Of Materials (SBOM) from a container image as the predicate of an in-toto attestation",
|
Long: "Generate a packaged-based Software Bill Of Materials (SBOM) from a container image as the predicate of an in-toto attestation that will be uploaded to the image registry",
|
||||||
Example: internal.Tprintf(attestHelp, map[string]interface{}{
|
Example: internal.Tprintf(attestHelp, map[string]interface{}{
|
||||||
"appName": internal.ApplicationName,
|
"appName": internal.ApplicationName,
|
||||||
"command": "attest",
|
"command": "attest",
|
||||||
}),
|
}),
|
||||||
Args: func(cmd *cobra.Command, args []string) error {
|
Args: func(cmd *cobra.Command, args []string) error {
|
||||||
// run to unmarshal viper object onto app config
|
|
||||||
// the viper object correctly
|
|
||||||
if err := app.LoadAllValues(v, ro.Config); err != nil {
|
if err := app.LoadAllValues(v, ro.Config); err != nil {
|
||||||
return fmt.Errorf("invalid application config: %w", err)
|
return fmt.Errorf("unable to load configuration: %w", err)
|
||||||
}
|
}
|
||||||
// configure logging for command
|
|
||||||
newLogWrapper(app)
|
newLogWrapper(app)
|
||||||
logApplicationConfig(app)
|
logApplicationConfig(app)
|
||||||
return validateArgs(cmd, args)
|
return validateArgs(cmd, args)
|
||||||
@ -49,29 +42,16 @@ func Attest(v *viper.Viper, app *config.Application, ro *options.RootOptions) *c
|
|||||||
SilenceUsage: true,
|
SilenceUsage: true,
|
||||||
SilenceErrors: true,
|
SilenceErrors: true,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
// this MUST be called first to make sure app config decodes
|
|
||||||
// the viper object correctly
|
|
||||||
if app.CheckForAppUpdate {
|
if app.CheckForAppUpdate {
|
||||||
checkForApplicationUpdate()
|
checkForApplicationUpdate()
|
||||||
}
|
}
|
||||||
|
|
||||||
// build cosign key options for attestation
|
return attest.Run(cmd.Context(), app, args)
|
||||||
ko := sigopts.KeyOpts{
|
|
||||||
KeyRef: app.Attest.KeyRef,
|
|
||||||
FulcioURL: app.Attest.FulcioURL,
|
|
||||||
IDToken: app.Attest.FulcioIdentityToken,
|
|
||||||
InsecureSkipFulcioVerify: app.Attest.InsecureSkipFulcioVerify,
|
|
||||||
RekorURL: app.Attest.RekorURL,
|
|
||||||
OIDCIssuer: app.Attest.OIDCIssuer,
|
|
||||||
OIDCClientID: app.Attest.OIDCClientID,
|
|
||||||
OIDCRedirectURL: app.Attest.OIDCRedirectURL,
|
|
||||||
}
|
|
||||||
|
|
||||||
return attest.Run(cmd.Context(), app, ko, args)
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
err := ao.AddFlags(cmd, v)
|
// syft attest is an enhancment of the packages command, so it should have the same flags
|
||||||
|
err := po.AddFlags(cmd, v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,105 +1,60 @@
|
|||||||
package attest
|
package attest
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
|
||||||
"github.com/google/go-containerregistry/pkg/name"
|
|
||||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
|
||||||
"github.com/in-toto/in-toto-golang/in_toto"
|
|
||||||
sigopts "github.com/sigstore/cosign/cmd/cosign/cli/options"
|
|
||||||
"github.com/sigstore/cosign/cmd/cosign/cli/rekor"
|
|
||||||
"github.com/sigstore/cosign/cmd/cosign/cli/sign"
|
|
||||||
"github.com/sigstore/cosign/pkg/cosign"
|
|
||||||
"github.com/sigstore/cosign/pkg/cosign/attestation"
|
|
||||||
cbundle "github.com/sigstore/cosign/pkg/cosign/bundle"
|
|
||||||
"github.com/sigstore/cosign/pkg/oci/mutate"
|
|
||||||
ociremote "github.com/sigstore/cosign/pkg/oci/remote"
|
|
||||||
"github.com/sigstore/cosign/pkg/oci/static"
|
|
||||||
sigs "github.com/sigstore/cosign/pkg/signature"
|
|
||||||
"github.com/sigstore/cosign/pkg/types"
|
|
||||||
"github.com/sigstore/rekor/pkg/generated/client"
|
|
||||||
"github.com/sigstore/rekor/pkg/generated/models"
|
|
||||||
"github.com/sigstore/sigstore/pkg/signature/dsse"
|
|
||||||
signatureoptions "github.com/sigstore/sigstore/pkg/signature/options"
|
|
||||||
"github.com/wagoodman/go-partybus"
|
"github.com/wagoodman/go-partybus"
|
||||||
"github.com/wagoodman/go-progress"
|
"github.com/wagoodman/go-progress"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
|
|
||||||
"github.com/anchore/stereoscope"
|
"github.com/anchore/stereoscope"
|
||||||
"github.com/anchore/stereoscope/pkg/image"
|
|
||||||
"github.com/anchore/syft/cmd/syft/cli/eventloop"
|
"github.com/anchore/syft/cmd/syft/cli/eventloop"
|
||||||
"github.com/anchore/syft/cmd/syft/cli/options"
|
"github.com/anchore/syft/cmd/syft/cli/options"
|
||||||
"github.com/anchore/syft/cmd/syft/cli/packages"
|
"github.com/anchore/syft/cmd/syft/cli/packages"
|
||||||
"github.com/anchore/syft/internal/bus"
|
"github.com/anchore/syft/internal/bus"
|
||||||
"github.com/anchore/syft/internal/config"
|
"github.com/anchore/syft/internal/config"
|
||||||
"github.com/anchore/syft/internal/log"
|
|
||||||
"github.com/anchore/syft/internal/ui"
|
"github.com/anchore/syft/internal/ui"
|
||||||
"github.com/anchore/syft/syft"
|
"github.com/anchore/syft/syft"
|
||||||
"github.com/anchore/syft/syft/event"
|
"github.com/anchore/syft/syft/event"
|
||||||
"github.com/anchore/syft/syft/formats/cyclonedxjson"
|
"github.com/anchore/syft/syft/event/monitor"
|
||||||
"github.com/anchore/syft/syft/formats/spdxjson"
|
|
||||||
"github.com/anchore/syft/syft/formats/syftjson"
|
"github.com/anchore/syft/syft/formats/syftjson"
|
||||||
|
"github.com/anchore/syft/syft/formats/table"
|
||||||
|
"github.com/anchore/syft/syft/formats/template"
|
||||||
"github.com/anchore/syft/syft/sbom"
|
"github.com/anchore/syft/syft/sbom"
|
||||||
"github.com/anchore/syft/syft/source"
|
"github.com/anchore/syft/syft/source"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
func Run(ctx context.Context, app *config.Application, args []string) error {
|
||||||
allowedAttestFormats = []sbom.FormatID{
|
err := ValidateOutputOptions(app)
|
||||||
syftjson.ID,
|
|
||||||
spdxjson.ID,
|
|
||||||
cyclonedxjson.ID,
|
|
||||||
}
|
|
||||||
|
|
||||||
intotoJSONDsseType = `application/vnd.in-toto+json`
|
|
||||||
)
|
|
||||||
|
|
||||||
func Run(ctx context.Context, app *config.Application, ko sigopts.KeyOpts, args []string) error {
|
|
||||||
// We cannot generate an attestation for more than one output
|
|
||||||
if len(app.Outputs) > 1 {
|
|
||||||
return fmt.Errorf("unable to generate attestation for more than one output")
|
|
||||||
}
|
|
||||||
|
|
||||||
// can only be an image for attestation or OCI DIR
|
|
||||||
userInput := args[0]
|
|
||||||
si, err := parseImageSource(userInput, app)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
output := parseAttestationOutput(app.Outputs)
|
writer, err := options.MakeWriter(app.Outputs, app.File, app.OutputTemplatePath)
|
||||||
|
if err != nil {
|
||||||
format := syft.FormatByName(output)
|
return fmt.Errorf("unable to write to report destination: %w", err)
|
||||||
|
|
||||||
// user typo or unknown outputs provided
|
|
||||||
if format == nil {
|
|
||||||
format = syft.FormatByID(syftjson.ID) // default attestation format
|
|
||||||
}
|
|
||||||
predicateType := formatPredicateType(format)
|
|
||||||
if predicateType == "" {
|
|
||||||
return fmt.Errorf(
|
|
||||||
"could not produce attestation predicate for given format: %q. Available formats: %+v",
|
|
||||||
options.FormatAliases(format.ID()),
|
|
||||||
options.FormatAliases(allowedAttestFormats...),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if app.Attest.KeyRef != "" {
|
defer func() {
|
||||||
passFunc, err := selectPassFunc(app.Attest.KeyRef, app.Attest.Password)
|
if err := writer.Close(); err != nil {
|
||||||
if err != nil {
|
fmt.Printf("unable to close report destination: %+v", err)
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
ko.PassFunc = passFunc
|
// could be an image or a directory, with or without a scheme
|
||||||
}
|
// TODO: validate that source is image
|
||||||
|
userInput := args[0]
|
||||||
sv, err := sign.SignerFromKeyOpts(ctx, "", "", ko)
|
si, err := source.ParseInputWithName(userInput, app.Platform, true, app.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("could not generate source input for packages command: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if si.Scheme != source.ImageScheme {
|
||||||
|
return fmt.Errorf("attestations are only supported for oci images at this time")
|
||||||
}
|
}
|
||||||
defer sv.Close()
|
|
||||||
|
|
||||||
eventBus := partybus.NewBus()
|
eventBus := partybus.NewBus()
|
||||||
stereoscope.SetBus(eventBus)
|
stereoscope.SetBus(eventBus)
|
||||||
@ -107,7 +62,7 @@ func Run(ctx context.Context, app *config.Application, ko sigopts.KeyOpts, args
|
|||||||
subscription := eventBus.Subscribe()
|
subscription := eventBus.Subscribe()
|
||||||
|
|
||||||
return eventloop.EventLoop(
|
return eventloop.EventLoop(
|
||||||
execWorker(app, *si, format, predicateType, sv),
|
execWorker(app, *si, writer),
|
||||||
eventloop.SetupSignals(),
|
eventloop.SetupSignals(),
|
||||||
subscription,
|
subscription,
|
||||||
stereoscope.Cleanup,
|
stereoscope.Cleanup,
|
||||||
@ -115,284 +70,158 @@ func Run(ctx context.Context, app *config.Application, ko sigopts.KeyOpts, args
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseAttestationOutput(outputs []string) (format string) {
|
func buildSBOM(app *config.Application, si source.Input, writer sbom.Writer, errs chan error) ([]byte, error) {
|
||||||
if len(outputs) == 0 {
|
src, cleanup, err := source.New(si, app.Registry.ToOptions(), app.Exclusions)
|
||||||
outputs = append(outputs, string(syftjson.ID))
|
if cleanup != nil {
|
||||||
|
defer cleanup()
|
||||||
}
|
}
|
||||||
|
|
||||||
return outputs[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseImageSource(userInput string, app *config.Application) (s *source.Input, err error) {
|
|
||||||
si, err := source.ParseInputWithName(userInput, app.Platform, false, app.Name)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not generate source input for attest command: %w", err)
|
return nil, fmt.Errorf("failed to construct source from user input %q: %w", si.UserInput, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch si.Scheme {
|
s, err := packages.GenerateSBOM(src, errs, app)
|
||||||
case source.ImageScheme, source.UnknownScheme:
|
if err != nil {
|
||||||
// at this point we know that it cannot be dir: or file: schemes;
|
return nil, err
|
||||||
// we will assume that the unknown scheme could represent an image;
|
|
||||||
si.Scheme = source.ImageScheme
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("attest command can only be used with image sources but discovered %q when given %q", si.Scheme, userInput)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the original detection was from the local daemon we want to short circuit
|
if s == nil {
|
||||||
// that and attempt to generate the image source from its current registry source instead
|
return nil, fmt.Errorf("no SBOM produced for %q", si.UserInput)
|
||||||
switch si.ImageSource {
|
|
||||||
case image.UnknownSource, image.OciRegistrySource:
|
|
||||||
si.ImageSource = image.OciRegistrySource
|
|
||||||
case image.SingularitySource:
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("attest command can only be used with image sources fetch directly from the registry, but discovered an image source of %q when given %q", si.ImageSource, userInput)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return si, nil
|
// note: only works for single format no multi writer support
|
||||||
|
sBytes, err := writer.Bytes(*s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to build SBOM bytes: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return sBytes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func execWorker(app *config.Application, sourceInput source.Input, format sbom.Format, predicateType string, sv *sign.SignerVerifier) <-chan error {
|
func execWorker(app *config.Application, si source.Input, writer sbom.Writer) <-chan error {
|
||||||
errs := make(chan error)
|
errs := make(chan error)
|
||||||
go func() {
|
go func() {
|
||||||
defer close(errs)
|
defer close(errs)
|
||||||
|
sBytes, err := buildSBOM(app, si, writer, errs)
|
||||||
src, cleanup, err := source.NewFromRegistry(sourceInput, app.Registry.ToOptions(), app.Exclusions)
|
|
||||||
if cleanup != nil {
|
|
||||||
defer cleanup()
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errs <- fmt.Errorf("failed to construct source from user input %q: %w", sourceInput.UserInput, err)
|
errs <- fmt.Errorf("unable to build SBOM: %w", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
s, err := packages.GenerateSBOM(src, errs, app)
|
// TODO: add multi writer support
|
||||||
if err != nil {
|
for _, o := range app.Outputs {
|
||||||
errs <- err
|
f, err := os.CreateTemp("", o)
|
||||||
return
|
if err != nil {
|
||||||
}
|
errs <- fmt.Errorf("unable to create temp file: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
sbomBytes, err := syft.Encode(*s, format)
|
defer f.Close()
|
||||||
if err != nil {
|
defer os.Remove(f.Name())
|
||||||
errs <- err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
signedPayload, err := generateAttestation(sourceInput, sbomBytes, src, sv, predicateType)
|
if _, err := f.Write(sBytes); err != nil {
|
||||||
if err != nil {
|
errs <- fmt.Errorf("unable to write SBOM to temp file: %w", err)
|
||||||
errs <- err
|
return
|
||||||
return
|
}
|
||||||
}
|
|
||||||
|
|
||||||
err = publishAttestation(app, signedPayload, src, sv)
|
// TODO: what other validation here besides binary name?
|
||||||
if err != nil {
|
cmd := "cosign"
|
||||||
errs <- err
|
if !commandExists(cmd) {
|
||||||
return
|
errs <- fmt.Errorf("unable to find cosign in PATH; make sure you have it installed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
args := []string{"attest", si.UserInput, "--type", "custom", "--predicate", f.Name()}
|
||||||
|
execCmd := exec.Command(cmd, args...)
|
||||||
|
execCmd.Env = os.Environ()
|
||||||
|
execCmd.Env = append(execCmd.Env, "COSIGN_EXPERIMENTAL=1")
|
||||||
|
|
||||||
|
// bus adapter for ui to hook into stdout via an os pipe
|
||||||
|
r, w, err := os.Pipe()
|
||||||
|
if err != nil {
|
||||||
|
errs <- fmt.Errorf("unable to create os pipe: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer w.Close()
|
||||||
|
|
||||||
|
b := &busWriter{r: r, w: w, mon: &progress.Manual{N: -1}}
|
||||||
|
execCmd.Stdout = b
|
||||||
|
execCmd.Stderr = b
|
||||||
|
defer b.mon.SetCompleted()
|
||||||
|
|
||||||
|
// attest the SBOM
|
||||||
|
err = execCmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
b.mon.Err = err
|
||||||
|
errs <- fmt.Errorf("unable to attest SBOM: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bus.Publish(partybus.Event{
|
bus.Publish(partybus.Event{
|
||||||
Type: event.Exit,
|
Type: event.Exit,
|
||||||
Value: func() error {
|
Value: func() error { return nil },
|
||||||
return nil
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
}()
|
}()
|
||||||
return errs
|
return errs
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateAttestation(si source.Input, predicate []byte, src *source.Source, sv *sign.SignerVerifier, predicateType string) ([]byte, error) {
|
func ValidateOutputOptions(app *config.Application) error {
|
||||||
var h v1.Hash
|
var usesTemplateOutput bool
|
||||||
|
for _, o := range app.Outputs {
|
||||||
switch si.ImageSource {
|
if o == template.ID.String() {
|
||||||
case image.OciRegistrySource:
|
usesTemplateOutput = true
|
||||||
switch len(src.Image.Metadata.RepoDigests) {
|
break
|
||||||
case 0:
|
|
||||||
return nil, fmt.Errorf("cannot generate attestation since no repo digests were found; make sure you're passing an OCI registry source for the attest command")
|
|
||||||
case 1:
|
|
||||||
d, err := name.NewDigest(src.Image.Metadata.RepoDigests[0])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
h, err = v1.NewHash(d.Identifier())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("cannot generate attestation since multiple repo digests were found for the image: %+v", src.Image.Metadata.RepoDigests)
|
|
||||||
}
|
|
||||||
|
|
||||||
case image.SingularitySource:
|
|
||||||
var err error
|
|
||||||
h, err = v1.NewHash(src.Image.Metadata.ID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sh, err := attestation.GenerateStatement(attestation.GenerateOpts{
|
if usesTemplateOutput && app.OutputTemplatePath == "" {
|
||||||
Predicate: bytes.NewBuffer(predicate),
|
return fmt.Errorf(`must specify path to template file when using "template" output format`)
|
||||||
Type: predicateType,
|
|
||||||
Digest: h.Hex,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
payload, err := json.Marshal(sh)
|
if len(app.Outputs) > 1 {
|
||||||
if err != nil {
|
return fmt.Errorf("multiple SBOM format is not supported for attest at this time")
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
wrapped := dsse.WrapSigner(sv, intotoJSONDsseType)
|
// cannot use table as default output format when using template output
|
||||||
return wrapped.SignMessage(bytes.NewReader(payload), signatureoptions.WithContext(context.Background()))
|
if slices.Contains(app.Outputs, table.ID.String()) {
|
||||||
}
|
app.Outputs = []string{syftjson.ID.String()}
|
||||||
|
|
||||||
// publishAttestation publishes signedPayload to the location specified by the user.
|
|
||||||
func publishAttestation(app *config.Application, signedPayload []byte, src *source.Source, sv *sign.SignerVerifier) error {
|
|
||||||
switch {
|
|
||||||
// We want to give the option to not upload the generated attestation
|
|
||||||
// if passed or if the user is using local PKI
|
|
||||||
case app.Attest.NoUpload || app.Attest.KeyRef != "":
|
|
||||||
if app.File != "" {
|
|
||||||
return os.WriteFile(app.File, signedPayload, 0600)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := os.Stdout.Write(signedPayload)
|
|
||||||
return err
|
|
||||||
|
|
||||||
default:
|
|
||||||
ref, err := name.ParseReference(src.Metadata.ImageMetadata.UserInput)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
digest, err := ociremote.ResolveDigest(ref)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return uploadAttestation(app, signedPayload, digest, sv)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func trackUploadAttestation() (*progress.Stage, *progress.Manual) {
|
|
||||||
stage := &progress.Stage{}
|
|
||||||
prog := &progress.Manual{}
|
|
||||||
|
|
||||||
bus.Publish(partybus.Event{
|
|
||||||
Type: event.UploadAttestation,
|
|
||||||
Value: progress.StagedProgressable(&struct {
|
|
||||||
progress.Stager
|
|
||||||
progress.Progressable
|
|
||||||
}{
|
|
||||||
Stager: stage,
|
|
||||||
Progressable: prog,
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
|
|
||||||
return stage, prog
|
|
||||||
}
|
|
||||||
|
|
||||||
// uploads signed SBOM payload to Rekor transparency log along with key information;
|
|
||||||
// returns a bundle for attestation annotations
|
|
||||||
// rekor bundle includes a signed payload and rekor timestamp;
|
|
||||||
// the bundle is then wrapped onto an OCI signed entity and uploaded to
|
|
||||||
// the user's image's OCI registry repository as *.att
|
|
||||||
func uploadAttestation(app *config.Application, signedPayload []byte, digest name.Digest, sv *sign.SignerVerifier) error {
|
|
||||||
// add application/vnd.dsse.envelope.v1+json as media type for other applications to decode attestation
|
|
||||||
opts := []static.Option{static.WithLayerMediaType(types.DssePayloadType)}
|
|
||||||
if sv.Cert != nil {
|
|
||||||
opts = append(opts, static.WithCertChain(sv.Cert, sv.Chain))
|
|
||||||
}
|
|
||||||
|
|
||||||
stage, prog := trackUploadAttestation()
|
|
||||||
defer prog.SetCompleted() // just in case we return early
|
|
||||||
|
|
||||||
prog.Total = 2
|
|
||||||
stage.Current = "uploading signing information to transparency log"
|
|
||||||
|
|
||||||
// uploads payload to Rekor transparency log along with key information;
|
|
||||||
// returns bundle for attesation annotations
|
|
||||||
// rekor bundle includes a signed payload and rekor timestamp;
|
|
||||||
// the bundle is then wrapped onto an OCI signed entity and uploaded to
|
|
||||||
// the user's image's OCI registry repository as *.att
|
|
||||||
bundle, err := uploadToTlog(context.TODO(), sv, app.Attest.RekorURL, func(r *client.Rekor, b []byte) (*models.LogEntryAnon, error) {
|
|
||||||
return cosign.TLogUploadInTotoAttestation(context.TODO(), r, signedPayload, b)
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
prog.N = 1
|
|
||||||
stage.Current = "uploading attestation to OCI registry"
|
|
||||||
|
|
||||||
// add bundle OCI attestation that is uploaded to
|
|
||||||
opts = append(opts, static.WithBundle(bundle))
|
|
||||||
sig, err := static.NewAttestation(signedPayload, opts...)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
se, err := ociremote.SignedEntity(digest)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
newSE, err := mutate.AttachAttestationToEntity(se, sig)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Publish the attestations associated with this entity
|
|
||||||
err = ociremote.WriteAttestations(digest.Repository, newSE)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
prog.SetCompleted()
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func formatPredicateType(format sbom.Format) string {
|
type busWriter struct {
|
||||||
switch format.ID() {
|
w *os.File
|
||||||
case spdxjson.ID:
|
r *os.File
|
||||||
return in_toto.PredicateSPDX
|
hasWritten bool
|
||||||
case cyclonedxjson.ID:
|
mon *progress.Manual
|
||||||
return in_toto.PredicateCycloneDX
|
|
||||||
case syftjson.ID:
|
|
||||||
return "https://syft.dev/bom"
|
|
||||||
default:
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type tlogUploadFn func(*client.Rekor, []byte) (*models.LogEntryAnon, error)
|
func (b *busWriter) Write(p []byte) (n int, err error) {
|
||||||
|
if !b.hasWritten {
|
||||||
func uploadToTlog(ctx context.Context, sv *sign.SignerVerifier, rekorURL string, upload tlogUploadFn) (*cbundle.RekorBundle, error) {
|
b.hasWritten = true
|
||||||
var rekorBytes []byte
|
bus.Publish(
|
||||||
// Upload the cert or the public key, depending on what we have
|
partybus.Event{
|
||||||
if sv.Cert != nil {
|
Type: event.AttestationStarted,
|
||||||
rekorBytes = sv.Cert
|
Source: monitor.GenericTask{
|
||||||
} else {
|
Title: monitor.Title{
|
||||||
pemBytes, err := sigs.PublicKeyPem(sv, signatureoptions.WithContext(ctx))
|
Default: "Create attestation",
|
||||||
if err != nil {
|
WhileRunning: "Creating attestation",
|
||||||
return nil, err
|
OnSuccess: "Created attestation",
|
||||||
}
|
},
|
||||||
rekorBytes = pemBytes
|
Context: "cosign",
|
||||||
|
},
|
||||||
|
Value: &monitor.ShellProgress{
|
||||||
|
Reader: b.r,
|
||||||
|
Manual: b.mon,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
return b.w.Write(p)
|
||||||
rekorClient, err := rekor.NewClient(rekorURL)
|
}
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
func commandExists(cmd string) bool {
|
||||||
}
|
_, err := exec.LookPath(cmd)
|
||||||
entry, err := upload(rekorClient, rekorBytes)
|
return err == nil
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if entry.LogIndex != nil {
|
|
||||||
log.Debugf("transparency log entry created with index: %v", *entry.LogIndex)
|
|
||||||
}
|
|
||||||
return cbundle.EntryToBundle(entry), nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,57 +0,0 @@
|
|||||||
package attest
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/sigstore/cosign/pkg/cosign"
|
|
||||||
|
|
||||||
"github.com/anchore/syft/internal"
|
|
||||||
"github.com/anchore/syft/internal/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
func selectPassFunc(keypath, password string) (cosign.PassFunc, error) {
|
|
||||||
keyContents, err := os.ReadFile(keypath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var fn cosign.PassFunc = func(bool) (b []byte, err error) { return nil, nil }
|
|
||||||
|
|
||||||
_, err = cosign.LoadPrivateKey(keyContents, nil)
|
|
||||||
if err != nil {
|
|
||||||
fn = func(bool) (b []byte, err error) {
|
|
||||||
return fetchPassword(password)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return fn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func fetchPassword(password string) (b []byte, err error) {
|
|
||||||
potentiallyPipedInput, err := internal.IsPipedInput()
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("unable to determine if there is piped input: %+v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case password != "":
|
|
||||||
return []byte(password), nil
|
|
||||||
case potentiallyPipedInput:
|
|
||||||
// handle piped in passwords
|
|
||||||
pwBytes, err := io.ReadAll(os.Stdin)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to get password from stdin: %w", err)
|
|
||||||
}
|
|
||||||
// be resilient to input that may have newline characters (in case someone is using echo without -n)
|
|
||||||
cleanPw := strings.TrimRight(string(pwBytes), "\n")
|
|
||||||
return []byte(cleanPw), nil
|
|
||||||
case internal.IsTerminal():
|
|
||||||
return cosign.GetPassFromTerm(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, errors.New("no method available to fetch password")
|
|
||||||
}
|
|
||||||
@ -26,8 +26,10 @@ import (
|
|||||||
const indent = " "
|
const indent = " "
|
||||||
|
|
||||||
// New constructs the `syft packages` command, aliases the root command to `syft packages`,
|
// New constructs the `syft packages` command, aliases the root command to `syft packages`,
|
||||||
// and constructs the `syft power-user` and `syft attest` commands. It is also responsible for
|
// and constructs the `syft power-user` command. It is also responsible for
|
||||||
// organizing flag usage and injecting the application config for each command.
|
// organizing flag usage and injecting the application config for each command.
|
||||||
|
// It also constructs the syft attest command and the syft version command.
|
||||||
|
|
||||||
// Because of how the `cobra` library behaves, the application's configuration is initialized
|
// Because of how the `cobra` library behaves, the application's configuration is initialized
|
||||||
// at this level. Values from the config should only be used after `app.LoadAllValues` has been called.
|
// at this level. Values from the config should only be used after `app.LoadAllValues` has been called.
|
||||||
// Cobra does not have knowledge of the user provided flags until the `RunE` block of each command.
|
// Cobra does not have knowledge of the user provided flags until the `RunE` block of each command.
|
||||||
@ -46,9 +48,9 @@ func New() (*cobra.Command, error) {
|
|||||||
packagesCmd := Packages(v, app, ro, po)
|
packagesCmd := Packages(v, app, ro, po)
|
||||||
|
|
||||||
// root options are also passed to the attestCmd so that a user provided config location can be discovered
|
// root options are also passed to the attestCmd so that a user provided config location can be discovered
|
||||||
attestCmd := Attest(v, app, ro)
|
|
||||||
poweruserCmd := PowerUser(v, app, ro)
|
poweruserCmd := PowerUser(v, app, ro)
|
||||||
convertCmd := Convert(v, app, ro, po)
|
convertCmd := Convert(v, app, ro, po)
|
||||||
|
attestCmd := Attest(v, app, ro, po)
|
||||||
|
|
||||||
// rootCmd is currently an alias for the packages command
|
// rootCmd is currently an alias for the packages command
|
||||||
rootCmd := &cobra.Command{
|
rootCmd := &cobra.Command{
|
||||||
@ -73,11 +75,7 @@ func New() (*cobra.Command, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// attest also uses flags from the packagesCmd since it generates an sbom
|
|
||||||
err = po.AddFlags(attestCmd, v)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// poweruser also uses the packagesCmd flags since it is a specialized version of the command
|
// poweruser also uses the packagesCmd flags since it is a specialized version of the command
|
||||||
err = po.AddFlags(poweruserCmd, v)
|
err = po.AddFlags(poweruserCmd, v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -87,13 +85,11 @@ func New() (*cobra.Command, error) {
|
|||||||
// commands to add to root
|
// commands to add to root
|
||||||
cmds := []*cobra.Command{
|
cmds := []*cobra.Command{
|
||||||
packagesCmd,
|
packagesCmd,
|
||||||
attestCmd,
|
poweruserCmd,
|
||||||
convertCmd,
|
convertCmd,
|
||||||
poweruserCmd,
|
attestCmd,
|
||||||
poweruserCmd,
|
|
||||||
Completion(),
|
|
||||||
Version(v, app),
|
Version(v, app),
|
||||||
cranecmd.NewCmdAuthLogin("syft"),
|
cranecmd.NewCmdAuthLogin("syft"), // syft login uses the same command as crane
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add sub-commands.
|
// Add sub-commands.
|
||||||
|
|||||||
@ -1,54 +0,0 @@
|
|||||||
package cli
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Completion() *cobra.Command {
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "completion [bash|zsh|fish]",
|
|
||||||
Short: "Generate a shell completion for Syft (listing local docker images)",
|
|
||||||
Long: `To load completions (docker image list):
|
|
||||||
Bash:
|
|
||||||
$ source <(syft completion bash)
|
|
||||||
# To load completions for each session, execute once:
|
|
||||||
Linux:
|
|
||||||
$ syft completion bash > /etc/bash_completion.d/syft
|
|
||||||
MacOS:
|
|
||||||
$ syft completion bash > /usr/local/etc/bash_completion.d/syft
|
|
||||||
Zsh:
|
|
||||||
# If shell completion is not already enabled in your environment you will need
|
|
||||||
# to enable it. You can execute the following once:
|
|
||||||
$ echo "autoload -U compinit; compinit" >> ~/.zshrc
|
|
||||||
# To load completions for each session, execute once:
|
|
||||||
$ syft completion zsh > "${fpath[1]}/_syft"
|
|
||||||
# You will need to start a new shell for this setup to take effect.
|
|
||||||
Fish:
|
|
||||||
$ syft completion fish | source
|
|
||||||
# To load completions for each session, execute once:
|
|
||||||
$ syft completion fish > ~/.config/fish/completions/syft.fish
|
|
||||||
`,
|
|
||||||
DisableFlagsInUseLine: true,
|
|
||||||
ValidArgs: []string{"bash", "zsh", "fish"},
|
|
||||||
Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
var err error
|
|
||||||
switch args[0] {
|
|
||||||
case "bash":
|
|
||||||
err = cmd.Root().GenBashCompletion(os.Stdout)
|
|
||||||
case "zsh":
|
|
||||||
err = cmd.Root().GenZshCompletion(os.Stdout)
|
|
||||||
case "fish":
|
|
||||||
err = cmd.Root().GenFishCompletion(os.Stdout, true)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
@ -1,77 +0,0 @@
|
|||||||
package options
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"github.com/spf13/pflag"
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
|
||||||
|
|
||||||
const defaultKeyFileName = "cosign.key"
|
|
||||||
|
|
||||||
type AttestOptions struct {
|
|
||||||
Key string
|
|
||||||
Cert string
|
|
||||||
CertChain string
|
|
||||||
NoUpload bool
|
|
||||||
Force bool
|
|
||||||
Recursive bool
|
|
||||||
|
|
||||||
Rekor RekorOptions
|
|
||||||
Fulcio FulcioOptions
|
|
||||||
OIDC OIDCOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ Interface = (*AttestOptions)(nil)
|
|
||||||
|
|
||||||
func (o *AttestOptions) AddFlags(cmd *cobra.Command, v *viper.Viper) error {
|
|
||||||
if err := o.Rekor.AddFlags(cmd, v); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := o.Fulcio.AddFlags(cmd, v); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := o.OIDC.AddFlags(cmd, v); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd.Flags().StringVarP(&o.Key, "key", "", defaultKeyFileName,
|
|
||||||
"path to the private key file to use for attestation")
|
|
||||||
|
|
||||||
cmd.Flags().StringVarP(&o.Cert, "cert", "", "",
|
|
||||||
"path to the x.509 certificate in PEM format to include in the OCI Signature")
|
|
||||||
|
|
||||||
cmd.Flags().BoolVarP(&o.NoUpload, "no-upload", "", false,
|
|
||||||
"do not upload the generated attestation")
|
|
||||||
|
|
||||||
cmd.Flags().BoolVarP(&o.Force, "force", "", false,
|
|
||||||
"skip warnings and confirmations")
|
|
||||||
|
|
||||||
cmd.Flags().BoolVarP(&o.Recursive, "recursive", "", false,
|
|
||||||
"if a multi-arch image is specified, additionally sign each discrete image")
|
|
||||||
|
|
||||||
return bindAttestationConfigOptions(cmd.Flags(), v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func bindAttestationConfigOptions(flags *pflag.FlagSet, v *viper.Viper) error {
|
|
||||||
if err := v.BindPFlag("attest.key", flags.Lookup("key")); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := v.BindPFlag("attest.cert", flags.Lookup("cert")); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := v.BindPFlag("attest.no-upload", flags.Lookup("no-upload")); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := v.BindPFlag("attest.force", flags.Lookup("force")); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := v.BindPFlag("attest.recursive", flags.Lookup("recursive")); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@ -9,7 +9,7 @@ type VersionOptions struct {
|
|||||||
Output string
|
Output string
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ Interface = (*PackagesOptions)(nil)
|
var _ Interface = (*VersionOptions)(nil)
|
||||||
|
|
||||||
func (o *VersionOptions) AddFlags(cmd *cobra.Command, v *viper.Viper) error {
|
func (o *VersionOptions) AddFlags(cmd *cobra.Command, v *viper.Viper) error {
|
||||||
cmd.Flags().StringVarP(&o.Output, "output", "o", "text", "format to show version information (available=[text, json])")
|
cmd.Flags().StringVarP(&o.Output, "output", "o", "text", "format to show version information (available=[text, json])")
|
||||||
|
|||||||
@ -24,7 +24,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func Run(ctx context.Context, app *config.Application, args []string) error {
|
func Run(ctx context.Context, app *config.Application, args []string) error {
|
||||||
err := validateOutputOptions(app)
|
err := ValidateOutputOptions(app)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -134,7 +134,7 @@ func MergeRelationships(cs ...<-chan artifact.Relationship) (relationships []art
|
|||||||
return relationships
|
return relationships
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateOutputOptions(app *config.Application) error {
|
func ValidateOutputOptions(app *config.Application) error {
|
||||||
var usesTemplateOutput bool
|
var usesTemplateOutput bool
|
||||||
for _, o := range app.Outputs {
|
for _, o := range app.Outputs {
|
||||||
if o == template.ID.String() {
|
if o == template.ID.String() {
|
||||||
|
|||||||
189
go.mod
189
go.mod
@ -3,7 +3,6 @@ module github.com/anchore/syft
|
|||||||
go 1.18
|
go 1.18
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/CycloneDX/cyclonedx-go v0.7.1-0.20221222100750-41a1ac565cce
|
|
||||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d
|
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d
|
||||||
github.com/acobaugh/osrelease v0.1.0
|
github.com/acobaugh/osrelease v0.1.0
|
||||||
github.com/adrg/xdg v0.3.3
|
github.com/adrg/xdg v0.3.3
|
||||||
@ -50,268 +49,90 @@ require (
|
|||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/CycloneDX/cyclonedx-go v0.7.1-0.20221222100750-41a1ac565cce
|
||||||
github.com/Masterminds/sprig/v3 v3.2.2
|
github.com/Masterminds/sprig/v3 v3.2.2
|
||||||
github.com/anchore/go-logger v0.0.0-20220728155337-03b66a5207d8
|
github.com/anchore/go-logger v0.0.0-20220728155337-03b66a5207d8
|
||||||
github.com/anchore/stereoscope v0.0.0-20221208011002-c5ff155d72f1
|
github.com/anchore/stereoscope v0.0.0-20221208011002-c5ff155d72f1
|
||||||
github.com/docker/docker v20.10.17+incompatible
|
github.com/docker/docker v20.10.17+incompatible
|
||||||
github.com/google/go-containerregistry v0.11.0
|
github.com/google/go-containerregistry v0.11.0
|
||||||
github.com/in-toto/in-toto-golang v0.4.1-0.20221018183522-731d0640b65f
|
|
||||||
github.com/invopop/jsonschema v0.7.0
|
github.com/invopop/jsonschema v0.7.0
|
||||||
github.com/knqyf263/go-rpmdb v0.0.0-20221030135625-4082a22221ce
|
github.com/knqyf263/go-rpmdb v0.0.0-20221030135625-4082a22221ce
|
||||||
github.com/opencontainers/go-digest v1.0.0
|
github.com/opencontainers/go-digest v1.0.0
|
||||||
github.com/sassoftware/go-rpmutils v0.2.0
|
github.com/sassoftware/go-rpmutils v0.2.0
|
||||||
github.com/sigstore/cosign v1.13.1
|
|
||||||
github.com/sigstore/rekor v0.12.1-0.20220915152154-4bb6f441c1b2
|
|
||||||
github.com/sigstore/sigstore v1.4.4
|
|
||||||
github.com/vbatts/go-mtree v0.5.0
|
github.com/vbatts/go-mtree v0.5.0
|
||||||
golang.org/x/exp v0.0.0-20220823124025-807a23277127
|
golang.org/x/exp v0.0.0-20220823124025-807a23277127
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
bitbucket.org/creachadair/shell v0.0.7 // indirect
|
|
||||||
cloud.google.com/go/compute v1.10.0 // indirect
|
|
||||||
github.com/AliyunContainerService/ack-ram-tool/pkg/credentials/alibabacloudsdkgo/helper v0.2.0 // indirect
|
|
||||||
github.com/Azure/azure-sdk-for-go v66.0.0+incompatible // indirect
|
|
||||||
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
|
|
||||||
github.com/Azure/go-autorest/autorest v0.11.28 // indirect
|
|
||||||
github.com/Azure/go-autorest/autorest/adal v0.9.21 // indirect
|
|
||||||
github.com/Azure/go-autorest/autorest/azure/auth v0.5.11 // indirect
|
|
||||||
github.com/Azure/go-autorest/autorest/azure/cli v0.4.6 // indirect
|
|
||||||
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
|
|
||||||
github.com/Azure/go-autorest/logger v0.2.1 // indirect
|
|
||||||
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
|
|
||||||
github.com/DataDog/zstd v1.4.5 // indirect
|
github.com/DataDog/zstd v1.4.5 // indirect
|
||||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||||
github.com/Masterminds/semver/v3 v3.1.1 // indirect
|
github.com/Masterminds/semver/v3 v3.1.1 // indirect
|
||||||
github.com/Microsoft/go-winio v0.5.2 // indirect
|
github.com/Microsoft/go-winio v0.5.2 // indirect
|
||||||
github.com/ThalesIgnite/crypto11 v1.2.5 // indirect
|
|
||||||
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4 // indirect
|
|
||||||
github.com/alibabacloud-go/cr-20160607 v1.0.1 // indirect
|
|
||||||
github.com/alibabacloud-go/cr-20181201 v1.0.10 // indirect
|
|
||||||
github.com/alibabacloud-go/darabonba-openapi v0.1.18 // indirect
|
|
||||||
github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68 // indirect
|
|
||||||
github.com/alibabacloud-go/endpoint-util v1.1.1 // indirect
|
|
||||||
github.com/alibabacloud-go/openapi-util v0.0.11 // indirect
|
|
||||||
github.com/alibabacloud-go/tea v1.1.18 // indirect
|
|
||||||
github.com/alibabacloud-go/tea-utils v1.4.4 // indirect
|
|
||||||
github.com/alibabacloud-go/tea-xml v1.1.2 // indirect
|
|
||||||
github.com/aliyun/credentials-go v1.2.3 // indirect
|
|
||||||
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect
|
|
||||||
github.com/aws/aws-sdk-go-v2 v1.16.16 // indirect
|
|
||||||
github.com/aws/aws-sdk-go-v2/config v1.17.8 // indirect
|
|
||||||
github.com/aws/aws-sdk-go-v2/credentials v1.12.21 // indirect
|
|
||||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.17 // indirect
|
|
||||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.23 // indirect
|
|
||||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.17 // indirect
|
|
||||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.24 // indirect
|
|
||||||
github.com/aws/aws-sdk-go-v2/service/ecr v1.15.0 // indirect
|
|
||||||
github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.12.0 // indirect
|
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.17 // indirect
|
|
||||||
github.com/aws/aws-sdk-go-v2/service/sso v1.11.23 // indirect
|
|
||||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.6 // indirect
|
|
||||||
github.com/aws/aws-sdk-go-v2/service/sts v1.16.19 // indirect
|
|
||||||
github.com/aws/smithy-go v1.13.3 // indirect
|
|
||||||
github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20220517224237-e6f29200ae04 // indirect
|
|
||||||
github.com/benbjohnson/clock v1.1.0 // indirect
|
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
|
||||||
github.com/bgentry/speakeasy v0.1.0 // indirect
|
|
||||||
github.com/blang/semver v3.5.1+incompatible // indirect
|
|
||||||
github.com/cenkalti/backoff/v4 v4.1.3 // indirect
|
|
||||||
github.com/census-instrumentation/opencensus-proto v0.3.0 // indirect
|
|
||||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
|
||||||
github.com/chrismellard/docker-credential-acr-env v0.0.0-20220119192733-fe33c00cee21 // indirect
|
|
||||||
github.com/clbanning/mxj/v2 v2.5.6 // indirect
|
|
||||||
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4 // indirect
|
|
||||||
github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490 // indirect
|
|
||||||
github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be // indirect
|
|
||||||
github.com/containerd/containerd v1.6.12 // indirect
|
github.com/containerd/containerd v1.6.12 // indirect
|
||||||
github.com/containerd/stargz-snapshotter/estargz v0.12.0 // indirect
|
github.com/containerd/stargz-snapshotter/estargz v0.12.0 // indirect
|
||||||
github.com/coreos/go-oidc/v3 v3.4.0 // indirect
|
|
||||||
github.com/coreos/go-semver v0.3.0 // indirect
|
|
||||||
github.com/coreos/go-systemd/v22 v22.3.2 // indirect
|
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
|
||||||
github.com/cyberphone/json-canonicalization v0.0.0-20210823021906-dc406ceaf94b // indirect
|
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/dimchansky/utfbom v1.1.1 // indirect
|
|
||||||
github.com/docker/cli v20.10.17+incompatible // indirect
|
github.com/docker/cli v20.10.17+incompatible // indirect
|
||||||
github.com/docker/distribution v2.8.1+incompatible // indirect
|
github.com/docker/distribution v2.8.1+incompatible // indirect
|
||||||
github.com/docker/docker-credential-helpers v0.6.4 // indirect
|
github.com/docker/docker-credential-helpers v0.6.4 // indirect
|
||||||
github.com/docker/go-connections v0.4.0 // indirect
|
github.com/docker/go-connections v0.4.0 // indirect
|
||||||
github.com/docker/go-units v0.4.0 // indirect
|
github.com/docker/go-units v0.4.0 // indirect
|
||||||
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect
|
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect
|
||||||
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1 // indirect
|
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.6.2 // indirect
|
|
||||||
github.com/fsnotify/fsnotify v1.5.4 // indirect
|
github.com/fsnotify/fsnotify v1.5.4 // indirect
|
||||||
github.com/fullstorydev/grpcurl v1.8.7 // indirect
|
|
||||||
github.com/gabriel-vasile/mimetype v1.4.0 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.0 // indirect
|
||||||
github.com/go-chi/chi v4.1.2+incompatible // indirect
|
|
||||||
github.com/go-logr/logr v1.2.3 // indirect
|
|
||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
|
||||||
github.com/go-openapi/analysis v0.21.4 // indirect
|
|
||||||
github.com/go-openapi/errors v0.20.3 // indirect
|
|
||||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
|
||||||
github.com/go-openapi/jsonreference v0.20.0 // indirect
|
|
||||||
github.com/go-openapi/loads v0.21.2 // indirect
|
|
||||||
github.com/go-openapi/runtime v0.24.2 // indirect
|
|
||||||
github.com/go-openapi/spec v0.20.7 // indirect
|
|
||||||
github.com/go-openapi/strfmt v0.21.3 // indirect
|
|
||||||
github.com/go-openapi/swag v0.22.3 // indirect
|
|
||||||
github.com/go-openapi/validate v0.22.0 // indirect
|
|
||||||
github.com/go-piv/piv-go v1.10.0 // indirect
|
|
||||||
github.com/go-playground/locales v0.14.0 // indirect
|
|
||||||
github.com/go-playground/universal-translator v0.18.0 // indirect
|
|
||||||
github.com/go-playground/validator/v10 v10.11.0 // indirect
|
|
||||||
github.com/go-restruct/restruct v1.2.0-alpha // indirect
|
github.com/go-restruct/restruct v1.2.0-alpha // indirect
|
||||||
github.com/gogo/protobuf v1.3.2 // indirect
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
|
|
||||||
github.com/golang-jwt/jwt/v4 v4.4.2 // indirect
|
|
||||||
github.com/golang/glog v1.0.0 // indirect
|
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
|
||||||
github.com/golang/mock v1.6.0 // indirect
|
|
||||||
github.com/golang/protobuf v1.5.2 // indirect
|
github.com/golang/protobuf v1.5.2 // indirect
|
||||||
github.com/golang/snappy v0.0.4 // indirect
|
github.com/golang/snappy v0.0.4 // indirect
|
||||||
github.com/google/btree v1.1.2 // indirect
|
|
||||||
github.com/google/certificate-transparency-go v1.1.3 // indirect
|
|
||||||
github.com/google/go-github/v45 v45.2.0 // indirect
|
|
||||||
github.com/google/go-querystring v1.1.0 // indirect
|
|
||||||
github.com/google/gofuzz v1.2.0 // indirect
|
|
||||||
github.com/google/trillian v1.5.0 // indirect
|
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.2.0 // indirect
|
|
||||||
github.com/googleapis/gnostic v0.5.5 // indirect
|
|
||||||
github.com/gorilla/websocket v1.4.2 // indirect
|
|
||||||
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
|
|
||||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect
|
|
||||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
|
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3 // indirect
|
|
||||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
|
||||||
github.com/hashicorp/go-retryablehttp v0.7.1 // indirect
|
|
||||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||||
github.com/huandu/xstrings v1.3.2 // indirect
|
github.com/huandu/xstrings v1.3.2 // indirect
|
||||||
github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 // indirect
|
github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 // indirect
|
||||||
github.com/imdario/mergo v0.3.12 // indirect
|
github.com/imdario/mergo v0.3.12 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.0.1 // indirect
|
github.com/inconshreveable/mousetrap v1.0.1 // indirect
|
||||||
github.com/jedisct1/go-minisign v0.0.0-20211028175153-1c139d1cc84b // indirect
|
|
||||||
github.com/jhump/protoreflect v1.13.0 // indirect
|
|
||||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
|
||||||
github.com/jonboulle/clockwork v0.3.0 // indirect
|
|
||||||
github.com/josharian/intern v1.0.0 // indirect
|
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||||
github.com/klauspost/compress v1.15.9 // indirect
|
github.com/klauspost/compress v1.15.9 // indirect
|
||||||
github.com/klauspost/pgzip v1.2.5 // indirect
|
github.com/klauspost/pgzip v1.2.5 // indirect
|
||||||
github.com/leodido/go-urn v1.2.1 // indirect
|
|
||||||
github.com/letsencrypt/boulder v0.0.0-20220929215747-76583552c2be // indirect
|
|
||||||
github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381 // indirect
|
github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381 // indirect
|
||||||
github.com/magiconair/properties v1.8.6 // indirect
|
github.com/magiconair/properties v1.8.6 // indirect
|
||||||
github.com/mailru/easyjson v0.7.7 // indirect
|
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.16 // indirect
|
github.com/mattn/go-isatty v0.0.16 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.13 // indirect
|
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
|
||||||
github.com/miekg/pkcs11 v1.1.1 // indirect
|
|
||||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
|
||||||
github.com/mozillazg/docker-credential-acr-helper v0.3.0 // indirect
|
|
||||||
github.com/nwaples/rardecode v1.1.0 // indirect
|
github.com/nwaples/rardecode v1.1.0 // indirect
|
||||||
github.com/oklog/ulid v1.3.1 // indirect
|
|
||||||
github.com/opencontainers/image-spec v1.0.3-0.20220114050600-8b9d41f48198 // indirect
|
github.com/opencontainers/image-spec v1.0.3-0.20220114050600-8b9d41f48198 // indirect
|
||||||
github.com/opentracing/opentracing-go v1.2.0 // indirect
|
|
||||||
github.com/pelletier/go-toml/v2 v2.0.5 // indirect
|
github.com/pelletier/go-toml/v2 v2.0.5 // indirect
|
||||||
github.com/pierrec/lz4/v4 v4.1.15 // indirect
|
github.com/pierrec/lz4/v4 v4.1.15 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/prometheus/client_golang v1.13.0 // indirect
|
|
||||||
github.com/prometheus/client_model v0.2.0 // indirect
|
|
||||||
github.com/prometheus/common v0.37.0 // indirect
|
|
||||||
github.com/prometheus/procfs v0.8.0 // indirect
|
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
|
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
|
||||||
github.com/rivo/uniseg v0.2.0 // indirect
|
github.com/rivo/uniseg v0.2.0 // indirect
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
github.com/rogpeppe/go-internal v1.8.0 // indirect
|
||||||
github.com/sassoftware/relic v0.0.0-20210427151427-dfb082b79b74 // indirect
|
|
||||||
github.com/secure-systems-lab/go-securesystemslib v0.4.0 // indirect
|
|
||||||
github.com/segmentio/ksuid v1.0.4 // indirect
|
|
||||||
github.com/shibumi/go-pathspec v1.3.0 // indirect
|
|
||||||
github.com/shopspring/decimal v1.2.0 // indirect
|
github.com/shopspring/decimal v1.2.0 // indirect
|
||||||
github.com/sigstore/fulcio v0.6.0 // indirect
|
|
||||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect
|
|
||||||
github.com/soheilhy/cmux v0.1.5 // indirect
|
|
||||||
github.com/spf13/cast v1.5.0 // indirect
|
github.com/spf13/cast v1.5.0 // indirect
|
||||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||||
github.com/spiffe/go-spiffe/v2 v2.1.1 // indirect
|
|
||||||
github.com/stretchr/objx v0.5.0 // indirect
|
github.com/stretchr/objx v0.5.0 // indirect
|
||||||
github.com/subosito/gotenv v1.4.1 // indirect
|
github.com/subosito/gotenv v1.4.1 // indirect
|
||||||
github.com/sylabs/sif/v2 v2.8.1 // indirect
|
github.com/sylabs/sif/v2 v2.8.1 // indirect
|
||||||
github.com/sylabs/squashfs v0.6.1 // indirect
|
github.com/sylabs/squashfs v0.6.1 // indirect
|
||||||
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect
|
|
||||||
github.com/tent/canonical-json-go v0.0.0-20130607151641-96e4ba3a7613 // indirect
|
|
||||||
github.com/thales-e-security/pool v0.0.2 // indirect
|
|
||||||
github.com/therootcompany/xz v1.0.1 // indirect
|
github.com/therootcompany/xz v1.0.1 // indirect
|
||||||
github.com/theupdateframework/go-tuf v0.5.2-0.20220930112810-3890c1e7ace4 // indirect
|
|
||||||
github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect
|
|
||||||
github.com/tjfoc/gmsm v1.3.2 // indirect
|
|
||||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 // indirect
|
|
||||||
github.com/transparency-dev/merkle v0.0.1 // indirect
|
|
||||||
github.com/ulikunitz/xz v0.5.10 // indirect
|
github.com/ulikunitz/xz v0.5.10 // indirect
|
||||||
github.com/urfave/cli v1.22.7 // indirect
|
|
||||||
github.com/vbatts/tar-split v0.11.2 // indirect
|
github.com/vbatts/tar-split v0.11.2 // indirect
|
||||||
github.com/xanzy/go-gitlab v0.73.1 // indirect
|
|
||||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
|
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
|
||||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect
|
|
||||||
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect
|
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect
|
||||||
github.com/zeebo/errs v1.2.2 // indirect
|
|
||||||
go.etcd.io/bbolt v1.3.6 // indirect
|
|
||||||
go.etcd.io/etcd/api/v3 v3.6.0-alpha.0 // indirect
|
|
||||||
go.etcd.io/etcd/client/pkg/v3 v3.6.0-alpha.0 // indirect
|
|
||||||
go.etcd.io/etcd/client/v2 v2.306.0-alpha.0 // indirect
|
|
||||||
go.etcd.io/etcd/client/v3 v3.6.0-alpha.0 // indirect
|
|
||||||
go.etcd.io/etcd/etcdctl/v3 v3.6.0-alpha.0 // indirect
|
|
||||||
go.etcd.io/etcd/etcdutl/v3 v3.6.0-alpha.0 // indirect
|
|
||||||
go.etcd.io/etcd/pkg/v3 v3.6.0-alpha.0 // indirect
|
|
||||||
go.etcd.io/etcd/raft/v3 v3.6.0-alpha.0 // indirect
|
|
||||||
go.etcd.io/etcd/server/v3 v3.6.0-alpha.0 // indirect
|
|
||||||
go.etcd.io/etcd/tests/v3 v3.6.0-alpha.0 // indirect
|
|
||||||
go.etcd.io/etcd/v3 v3.6.0-alpha.0 // indirect
|
|
||||||
go.mongodb.org/mongo-driver v1.10.0 // indirect
|
|
||||||
go.opencensus.io v0.23.0 // indirect
|
|
||||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.28.0 // indirect
|
|
||||||
go.opentelemetry.io/otel v1.7.0 // indirect
|
|
||||||
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.7.0 // indirect
|
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.7.0 // indirect
|
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.7.0 // indirect
|
|
||||||
go.opentelemetry.io/otel/sdk v1.7.0 // indirect
|
|
||||||
go.opentelemetry.io/otel/trace v1.7.0 // indirect
|
|
||||||
go.opentelemetry.io/proto/otlp v0.16.0 // indirect
|
|
||||||
go.uber.org/atomic v1.10.0 // indirect
|
|
||||||
go.uber.org/multierr v1.8.0 // indirect
|
|
||||||
go.uber.org/zap v1.23.0 // indirect
|
|
||||||
golang.org/x/oauth2 v0.0.0-20221006150949-b44042a4b9c1 // indirect
|
|
||||||
golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0 // indirect
|
golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0 // indirect
|
||||||
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec // indirect
|
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec // indirect
|
||||||
golang.org/x/text v0.3.8-0.20211004125949-5bd84dd9b33b // indirect
|
golang.org/x/text v0.3.8-0.20211004125949-5bd84dd9b33b // indirect
|
||||||
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af // indirect
|
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af // indirect
|
||||||
golang.org/x/tools v0.1.12 // indirect
|
golang.org/x/tools v0.1.12 // indirect
|
||||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
||||||
google.golang.org/api v0.99.0 // indirect
|
|
||||||
google.golang.org/appengine v1.6.7 // indirect
|
|
||||||
google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e // indirect
|
google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e // indirect
|
||||||
google.golang.org/grpc v1.50.1 // indirect
|
google.golang.org/grpc v1.50.1 // indirect
|
||||||
google.golang.org/protobuf v1.28.1 // indirect
|
google.golang.org/protobuf v1.28.1 // indirect
|
||||||
gopkg.in/cheggaaa/pb.v1 v1.0.28 // indirect
|
|
||||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
|
||||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
|
|
||||||
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
|
|
||||||
k8s.io/api v0.23.5 // indirect
|
|
||||||
k8s.io/apimachinery v0.23.5 // indirect
|
|
||||||
k8s.io/client-go v0.23.5 // indirect
|
|
||||||
k8s.io/klog/v2 v2.60.1-0.20220317184644-43cc75f9ae89 // indirect
|
|
||||||
k8s.io/kube-openapi v0.0.0-20220124234850-424119656bbf // indirect
|
|
||||||
k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 // indirect
|
|
||||||
lukechampine.com/uint128 v1.1.1 // indirect
|
lukechampine.com/uint128 v1.1.1 // indirect
|
||||||
modernc.org/cc/v3 v3.36.0 // indirect
|
modernc.org/cc/v3 v3.36.0 // indirect
|
||||||
modernc.org/ccgo/v3 v3.16.6 // indirect
|
modernc.org/ccgo/v3 v3.16.6 // indirect
|
||||||
@ -322,10 +143,6 @@ require (
|
|||||||
modernc.org/sqlite v1.17.3 // indirect
|
modernc.org/sqlite v1.17.3 // indirect
|
||||||
modernc.org/strutil v1.1.1 // indirect
|
modernc.org/strutil v1.1.1 // indirect
|
||||||
modernc.org/token v1.0.0 // indirect
|
modernc.org/token v1.0.0 // indirect
|
||||||
sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 // indirect
|
|
||||||
sigs.k8s.io/release-utils v0.7.3 // indirect
|
|
||||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect
|
|
||||||
sigs.k8s.io/yaml v1.3.0 // indirect
|
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
|||||||
@ -53,7 +53,6 @@ type Application struct {
|
|||||||
Secrets secrets `yaml:"secrets" json:"secrets" mapstructure:"secrets"`
|
Secrets secrets `yaml:"secrets" json:"secrets" mapstructure:"secrets"`
|
||||||
Registry registry `yaml:"registry" json:"registry" mapstructure:"registry"`
|
Registry registry `yaml:"registry" json:"registry" mapstructure:"registry"`
|
||||||
Exclusions []string `yaml:"exclude" json:"exclude" mapstructure:"exclude"`
|
Exclusions []string `yaml:"exclude" json:"exclude" mapstructure:"exclude"`
|
||||||
Attest attest `yaml:"attest" json:"attest" mapstructure:"attest"`
|
|
||||||
Platform string `yaml:"platform" json:"platform" mapstructure:"platform"`
|
Platform string `yaml:"platform" json:"platform" mapstructure:"platform"`
|
||||||
Name string `yaml:"name" json:"name" mapstructure:"name"`
|
Name string `yaml:"name" json:"name" mapstructure:"name"`
|
||||||
Parallelism int `yaml:"parallelism" json:"parallelism" mapstructure:"parallelism"` // the number of catalog workers to run in parallel
|
Parallelism int `yaml:"parallelism" json:"parallelism" mapstructure:"parallelism"` // the number of catalog workers to run in parallel
|
||||||
|
|||||||
@ -1,56 +0,0 @@
|
|||||||
package config
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/mitchellh/go-homedir"
|
|
||||||
"github.com/sigstore/cosign/cmd/cosign/cli/options"
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
|
||||||
|
|
||||||
// IMPORTANT: do not show the password in any YAML/JSON output (sensitive information)
|
|
||||||
type attest struct {
|
|
||||||
KeyRef string `yaml:"key" json:"key" mapstructure:"key"` // same as --key, file path to the private key
|
|
||||||
Cert string `yaml:"cert" json:"cert" mapstructure:"cert"`
|
|
||||||
NoUpload bool `yaml:"no_upload" json:"noUpload" mapstructure:"no_upload"`
|
|
||||||
Force bool `yaml:"force" json:"force" mapstructure:"force"`
|
|
||||||
Recursive bool `yaml:"recursive" json:"recursive" mapstructure:"recursive"`
|
|
||||||
Replace bool `yaml:"replace" json:"replace" mapstructure:"replace"`
|
|
||||||
Password string `yaml:"-" json:"-" mapstructure:"password"` // password for the private key
|
|
||||||
FulcioURL string `yaml:"fulcio_url" json:"fulcioUrl" mapstructure:"fulcio_url"`
|
|
||||||
FulcioIdentityToken string `yaml:"fulcio_identity_token" json:"fulcio_identity_token" mapstructure:"fulcio_identity_token"`
|
|
||||||
InsecureSkipFulcioVerify bool `yaml:"insecure_skip_verify" json:"insecure_skip_verify" mapstructure:"insecure_skip_verify"`
|
|
||||||
RekorURL string `yaml:"rekor_url" json:"rekorUrl" mapstructure:"rekor_url"`
|
|
||||||
OIDCIssuer string `yaml:"oidc_issuer" json:"oidcIssuer" mapstructure:"oidc_issuer"`
|
|
||||||
OIDCClientID string `yaml:"oidc_client_id" json:"oidcClientId" mapstructure:"oidc_client_id"`
|
|
||||||
OIDCRedirectURL string `yaml:"oidc_redirect_url" json:"OIDCRedirectURL" mapstructure:"oidc_redirect_url"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *attest) parseConfigValues() error {
|
|
||||||
if cfg.KeyRef != "" {
|
|
||||||
expandedPath, err := homedir.Expand(cfg.KeyRef)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to expand key path=%q: %w", cfg.KeyRef, err)
|
|
||||||
}
|
|
||||||
cfg.KeyRef = expandedPath
|
|
||||||
}
|
|
||||||
|
|
||||||
if cfg.Password == "" {
|
|
||||||
// we allow for configuration via syft config/env vars and additionally interop with known cosign config env vars
|
|
||||||
if pw, ok := os.LookupEnv("COSIGN_PASSWORD"); ok {
|
|
||||||
cfg.Password = pw
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg attest) loadDefaultValues(v *viper.Viper) {
|
|
||||||
v.SetDefault("attest.key", "")
|
|
||||||
v.SetDefault("attest.password", "")
|
|
||||||
v.SetDefault("attest.fulcio_url", options.DefaultFulcioURL)
|
|
||||||
v.SetDefault("attest.rekor_url", options.DefaultRekorURL)
|
|
||||||
v.SetDefault("attest.oidc_issuer", options.DefaultOIDCIssuerURL)
|
|
||||||
v.SetDefault("attest.oidc_client_id", "sigstore")
|
|
||||||
}
|
|
||||||
@ -32,6 +32,6 @@ const (
|
|||||||
// ImportStarted is a partybus event that occurs when an SBOM upload process has begun
|
// ImportStarted is a partybus event that occurs when an SBOM upload process has begun
|
||||||
ImportStarted partybus.EventType = "syft-import-started-event"
|
ImportStarted partybus.EventType = "syft-import-started-event"
|
||||||
|
|
||||||
// UploadAttestation is a partybus event that occurs when syft uploads an attestation to an OCI registry (+ any transparency log)
|
// AttestationStarted is a partybus event that occurs when starting an SBOM attestation process
|
||||||
UploadAttestation partybus.EventType = "syft-upload-attestation"
|
AttestationStarted partybus.EventType = "syft-attestation-started-event"
|
||||||
)
|
)
|
||||||
|
|||||||
23
syft/event/monitor/generic_task.go
Normal file
23
syft/event/monitor/generic_task.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package monitor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/wagoodman/go-progress"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ShellProgress struct {
|
||||||
|
io.Reader
|
||||||
|
*progress.Manual
|
||||||
|
}
|
||||||
|
|
||||||
|
type Title struct {
|
||||||
|
Default string
|
||||||
|
WhileRunning string
|
||||||
|
OnSuccess string
|
||||||
|
}
|
||||||
|
|
||||||
|
type GenericTask struct {
|
||||||
|
Title Title
|
||||||
|
Context string
|
||||||
|
}
|
||||||
@ -5,11 +5,13 @@ package parsers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
"github.com/wagoodman/go-partybus"
|
"github.com/wagoodman/go-partybus"
|
||||||
"github.com/wagoodman/go-progress"
|
"github.com/wagoodman/go-progress"
|
||||||
|
|
||||||
"github.com/anchore/syft/syft/event"
|
"github.com/anchore/syft/syft/event"
|
||||||
|
"github.com/anchore/syft/syft/event/monitor"
|
||||||
"github.com/anchore/syft/syft/file"
|
"github.com/anchore/syft/syft/file"
|
||||||
"github.com/anchore/syft/syft/pkg/cataloger"
|
"github.com/anchore/syft/syft/pkg/cataloger"
|
||||||
)
|
)
|
||||||
@ -153,15 +155,20 @@ func ParseImportStarted(e partybus.Event) (string, progress.StagedProgressable,
|
|||||||
return host, prog, nil
|
return host, prog, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseUploadAttestation(e partybus.Event) (progress.StagedProgressable, error) {
|
func ParseAttestationStartedEvent(e partybus.Event) (io.Reader, progress.Progressable, *monitor.GenericTask, error) {
|
||||||
if err := checkEventType(e.Type, event.UploadAttestation); err != nil {
|
if err := checkEventType(e.Type, event.AttestationStarted); err != nil {
|
||||||
return nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
prog, ok := e.Value.(progress.StagedProgressable)
|
source, ok := e.Source.(monitor.GenericTask)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, newPayloadErr(e.Type, "Value", e.Value)
|
return nil, nil, nil, newPayloadErr(e.Type, "Source", e.Source)
|
||||||
}
|
}
|
||||||
|
|
||||||
return prog, nil
|
sp, ok := e.Value.(*monitor.ShellProgress)
|
||||||
|
if !ok {
|
||||||
|
return nil, nil, nil, newPayloadErr(e.Type, "Value", e.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return sp.Reader, sp.Manual, &source, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -99,6 +99,18 @@ func (m *multiWriter) Write(s SBOM) (errs error) {
|
|||||||
return errs
|
return errs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Bytes returns the bytes of the SBOM that would be written
|
||||||
|
func (m *multiWriter) Bytes(s SBOM) (bytes []byte, err error) {
|
||||||
|
for _, w := range m.writers {
|
||||||
|
b, err := w.Bytes(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
bytes = append(bytes, b...)
|
||||||
|
}
|
||||||
|
return bytes, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Close closes all writers
|
// Close closes all writers
|
||||||
func (m *multiWriter) Close() (errs error) {
|
func (m *multiWriter) Close() (errs error) {
|
||||||
for _, w := range m.writers {
|
for _, w := range m.writers {
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package sbom
|
package sbom
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"io"
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -16,6 +17,16 @@ func (w *streamWriter) Write(s SBOM) error {
|
|||||||
return w.format.Encode(w.out, s)
|
return w.format.Encode(w.out, s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Bytes returns the bytes of the SBOM that would be written
|
||||||
|
func (w *streamWriter) Bytes(s SBOM) ([]byte, error) {
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
err := w.format.Encode(&buffer, s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return buffer.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
// Close any resources, such as open files
|
// Close any resources, such as open files
|
||||||
func (w *streamWriter) Close() error {
|
func (w *streamWriter) Close() error {
|
||||||
if w.close != nil {
|
if w.close != nil {
|
||||||
|
|||||||
@ -7,6 +7,9 @@ type Writer interface {
|
|||||||
// Write writes the provided SBOM
|
// Write writes the provided SBOM
|
||||||
Write(SBOM) error
|
Write(SBOM) error
|
||||||
|
|
||||||
|
// Bytes returns the bytes of the SBOM that would be written
|
||||||
|
Bytes(SBOM) ([]byte, error)
|
||||||
|
|
||||||
// Closer a resource cleanup hook which will be called after SBOM
|
// Closer a resource cleanup hook which will be called after SBOM
|
||||||
// is written or if an error occurs before Write is called
|
// is written or if an error occurs before Write is called
|
||||||
io.Closer
|
io.Closer
|
||||||
|
|||||||
@ -1,68 +0,0 @@
|
|||||||
package cli
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestAttestCmd(t *testing.T) {
|
|
||||||
img := "registry:busybox:latest"
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
args []string
|
|
||||||
env map[string]string
|
|
||||||
assertions []traitAssertion
|
|
||||||
pw string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "no-args-shows-help",
|
|
||||||
args: []string{"attest"},
|
|
||||||
assertions: []traitAssertion{
|
|
||||||
assertInOutput("an image/directory argument is required"), // specific error that should be shown
|
|
||||||
assertInOutput("from a container image as the predicate of an in-toto attestation"), // excerpt from help description
|
|
||||||
assertFailingReturnCode,
|
|
||||||
},
|
|
||||||
pw: "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "can encode syft.json as the predicate given a password",
|
|
||||||
args: []string{"attest", "-o", "json", "--key", "cosign.key", img},
|
|
||||||
assertions: []traitAssertion{
|
|
||||||
assertSuccessfulReturnCode,
|
|
||||||
},
|
|
||||||
pw: "test",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "can encode syft.json as the predicate given a blank password",
|
|
||||||
args: []string{"attest", "-o", "json", "--key", "cosign.key", img},
|
|
||||||
assertions: []traitAssertion{
|
|
||||||
assertSuccessfulReturnCode,
|
|
||||||
},
|
|
||||||
pw: "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "can encode syft.json as the predicate given a user format typo",
|
|
||||||
args: []string{"attest", "-o", "spdx-jsonx", "--key", "cosign.key", img},
|
|
||||||
assertions: []traitAssertion{
|
|
||||||
assertSuccessfulReturnCode,
|
|
||||||
},
|
|
||||||
pw: "",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
t.Run(test.name, func(t *testing.T) {
|
|
||||||
cleanup := setupPKI(t, test.pw)
|
|
||||||
defer cleanup()
|
|
||||||
cmd, stdout, stderr := runSyft(t, test.env, test.args...)
|
|
||||||
for _, traitFn := range test.assertions {
|
|
||||||
traitFn(t, stdout, stderr, cmd.ProcessState.ExitCode())
|
|
||||||
}
|
|
||||||
if t.Failed() {
|
|
||||||
t.Log("STDOUT:\n", stdout)
|
|
||||||
t.Log("STDERR:\n", stderr)
|
|
||||||
t.Log("COMMAND:", strings.Join(cmd.Args, " "))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,150 +0,0 @@
|
|||||||
package cli
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
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",
|
|
||||||
"--key",
|
|
||||||
"cosign.key",
|
|
||||||
img,
|
|
||||||
},
|
|
||||||
// cosign attach attestation
|
|
||||||
cosignAttachArgs: []string{
|
|
||||||
"attach",
|
|
||||||
"attestation",
|
|
||||||
"--attestation",
|
|
||||||
attestationFile,
|
|
||||||
img,
|
|
||||||
},
|
|
||||||
// cosign verify-attestation
|
|
||||||
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, " "))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,7 +1,10 @@
|
|||||||
package ui
|
package ui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
|
"container/list"
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
@ -25,6 +28,7 @@ import (
|
|||||||
const maxBarWidth = 50
|
const maxBarWidth = 50
|
||||||
const statusSet = components.SpinnerDotSet
|
const statusSet = components.SpinnerDotSet
|
||||||
const completedStatus = "✔"
|
const completedStatus = "✔"
|
||||||
|
const failedStatus = "✘"
|
||||||
const tileFormat = color.Bold
|
const tileFormat = color.Bold
|
||||||
const interval = 150 * time.Millisecond
|
const interval = 150 * time.Millisecond
|
||||||
|
|
||||||
@ -226,48 +230,6 @@ func FetchImageHandler(ctx context.Context, fr *frame.Frame, event partybus.Even
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func UploadAttestationHandler(ctx context.Context, fr *frame.Frame, event partybus.Event, wg *sync.WaitGroup) error {
|
|
||||||
prog, err := syftEventParsers.ParseUploadAttestation(event)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("bad %s event: %w", event.Type, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
line, err := fr.Append()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
wg.Add(1)
|
|
||||||
|
|
||||||
formatter, spinner := startProcess()
|
|
||||||
stream := progress.Stream(ctx, prog, interval)
|
|
||||||
title := tileFormat.Sprint("Uploading attestation")
|
|
||||||
|
|
||||||
formatFn := func(p progress.Progress) {
|
|
||||||
progStr, err := formatter.Format(p)
|
|
||||||
spin := color.Magenta.Sprint(spinner.Next())
|
|
||||||
if err != nil {
|
|
||||||
_, _ = io.WriteString(line, fmt.Sprintf("Error: %+v", err))
|
|
||||||
} else {
|
|
||||||
auxInfo := auxInfoFormat.Sprintf("[%s]", prog.Stage())
|
|
||||||
_, _ = io.WriteString(line, fmt.Sprintf(statusTitleTemplate+"%s %s", spin, title, progStr, auxInfo))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
|
|
||||||
formatFn(progress.Progress{})
|
|
||||||
for p := range stream {
|
|
||||||
formatFn(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
spin := color.Green.Sprint(completedStatus)
|
|
||||||
title = tileFormat.Sprint("Uploaded attestation")
|
|
||||||
_, _ = io.WriteString(line, fmt.Sprintf(statusTitleTemplate, spin, title))
|
|
||||||
}()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadImageHandler periodically writes a the image read/parse/build-tree status in the form of a progress bar.
|
// ReadImageHandler periodically writes a the image read/parse/build-tree status in the form of a progress bar.
|
||||||
func ReadImageHandler(ctx context.Context, fr *frame.Frame, event partybus.Event, wg *sync.WaitGroup) error {
|
func ReadImageHandler(ctx context.Context, fr *frame.Frame, event partybus.Event, wg *sync.WaitGroup) error {
|
||||||
_, prog, err := stereoEventParsers.ParseReadImage(event)
|
_, prog, err := stereoEventParsers.ParseReadImage(event)
|
||||||
@ -570,3 +532,110 @@ func ImportStartedHandler(ctx context.Context, fr *frame.Frame, event partybus.E
|
|||||||
}()
|
}()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AttestationStartedHandler takes bytes from a event.ShellOutput and publishes them to the frame.
|
||||||
|
//
|
||||||
|
//nolint:funlen,gocognit
|
||||||
|
func AttestationStartedHandler(ctx context.Context, fr *frame.Frame, event partybus.Event, wg *sync.WaitGroup) error {
|
||||||
|
reader, prog, taskInfo, err := syftEventParsers.ParseAttestationStartedEvent(event)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("bad %s event: %w", event.Type, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
titleLine, err := fr.Append()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
wg.Add(2)
|
||||||
|
|
||||||
|
_, spinner := startProcess()
|
||||||
|
|
||||||
|
title := tileFormat.Sprintf(taskInfo.Title.WhileRunning)
|
||||||
|
|
||||||
|
s := bufio.NewScanner(reader)
|
||||||
|
l := list.New()
|
||||||
|
|
||||||
|
formatFn := func() {
|
||||||
|
auxInfo := auxInfoFormat.Sprintf("[running %s]", taskInfo.Context)
|
||||||
|
spin := color.Magenta.Sprint(spinner.Next())
|
||||||
|
_, _ = io.WriteString(titleLine, fmt.Sprintf(statusTitleTemplate+"%s", spin, title, auxInfo))
|
||||||
|
}
|
||||||
|
|
||||||
|
formatFn()
|
||||||
|
var failed bool
|
||||||
|
formatComplete := func(aux string) {
|
||||||
|
spin := color.Green.Sprint(completedStatus)
|
||||||
|
if failed {
|
||||||
|
spin = color.Red.Sprint(failedStatus)
|
||||||
|
aux = prog.Error().Error()
|
||||||
|
} else {
|
||||||
|
title = tileFormat.Sprintf(taskInfo.Title.OnSuccess)
|
||||||
|
}
|
||||||
|
|
||||||
|
auxInfo := auxInfoFormat.Sprintf("[%s]", aux)
|
||||||
|
|
||||||
|
_, _ = io.WriteString(titleLine, fmt.Sprintf(statusTitleTemplate+"%s", spin, title, auxInfo))
|
||||||
|
}
|
||||||
|
|
||||||
|
endWg := &sync.WaitGroup{}
|
||||||
|
endWg.Add(1)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
defer endWg.Done()
|
||||||
|
|
||||||
|
stream := progress.Stream(ctx, prog, interval)
|
||||||
|
for range stream {
|
||||||
|
formatFn()
|
||||||
|
}
|
||||||
|
err := prog.Error()
|
||||||
|
if err != nil && !errors.Is(err, io.EOF) {
|
||||||
|
failed = true
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
var tlogEntry string
|
||||||
|
|
||||||
|
// only show the last 5 lines of the shell output
|
||||||
|
for s.Scan() {
|
||||||
|
line, _ := fr.Append()
|
||||||
|
if l.Len() > 5 {
|
||||||
|
elem := l.Front()
|
||||||
|
line := elem.Value.(*frame.Line)
|
||||||
|
err = line.Remove()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
l.Remove(elem)
|
||||||
|
}
|
||||||
|
l.PushBack(line)
|
||||||
|
text := s.Text()
|
||||||
|
if strings.Contains(text, "tlog entry created with index") {
|
||||||
|
tlogEntry = text
|
||||||
|
}
|
||||||
|
_, err = line.Write([]byte(fmt.Sprintf(" %s %s", auxInfoFormat.Sprintf("░░"), text)))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
endWg.Wait()
|
||||||
|
|
||||||
|
if !failed {
|
||||||
|
// roll up logs into completed status (only if successful)
|
||||||
|
for e := l.Back(); e != nil; e = e.Prev() {
|
||||||
|
line := e.Value.(*frame.Line)
|
||||||
|
err = line.Remove()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
formatComplete(tlogEntry)
|
||||||
|
}()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@ -31,13 +31,13 @@ func (r *Handler) RespondsTo(event partybus.Event) bool {
|
|||||||
case stereoscopeEvent.PullDockerImage,
|
case stereoscopeEvent.PullDockerImage,
|
||||||
stereoscopeEvent.ReadImage,
|
stereoscopeEvent.ReadImage,
|
||||||
stereoscopeEvent.FetchImage,
|
stereoscopeEvent.FetchImage,
|
||||||
syftEvent.UploadAttestation,
|
|
||||||
syftEvent.PackageCatalogerStarted,
|
syftEvent.PackageCatalogerStarted,
|
||||||
syftEvent.SecretsCatalogerStarted,
|
syftEvent.SecretsCatalogerStarted,
|
||||||
syftEvent.FileDigestsCatalogerStarted,
|
syftEvent.FileDigestsCatalogerStarted,
|
||||||
syftEvent.FileMetadataCatalogerStarted,
|
syftEvent.FileMetadataCatalogerStarted,
|
||||||
syftEvent.FileIndexingStarted,
|
syftEvent.FileIndexingStarted,
|
||||||
syftEvent.ImportStarted:
|
syftEvent.ImportStarted,
|
||||||
|
syftEvent.AttestationStarted:
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
@ -56,9 +56,6 @@ func (r *Handler) Handle(ctx context.Context, fr *frame.Frame, event partybus.Ev
|
|||||||
case stereoscopeEvent.FetchImage:
|
case stereoscopeEvent.FetchImage:
|
||||||
return FetchImageHandler(ctx, fr, event, wg)
|
return FetchImageHandler(ctx, fr, event, wg)
|
||||||
|
|
||||||
case syftEvent.UploadAttestation:
|
|
||||||
return UploadAttestationHandler(ctx, fr, event, wg)
|
|
||||||
|
|
||||||
case syftEvent.PackageCatalogerStarted:
|
case syftEvent.PackageCatalogerStarted:
|
||||||
return PackageCatalogerStartedHandler(ctx, fr, event, wg)
|
return PackageCatalogerStartedHandler(ctx, fr, event, wg)
|
||||||
|
|
||||||
@ -76,6 +73,9 @@ func (r *Handler) Handle(ctx context.Context, fr *frame.Frame, event partybus.Ev
|
|||||||
|
|
||||||
case syftEvent.ImportStarted:
|
case syftEvent.ImportStarted:
|
||||||
return ImportStartedHandler(ctx, fr, event, wg)
|
return ImportStartedHandler(ctx, fr, event, wg)
|
||||||
|
|
||||||
|
case syftEvent.AttestationStarted:
|
||||||
|
return AttestationStartedHandler(ctx, fr, event, wg)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user