From 06be3a67a10c16c8c382e33a0f6b49e0ff0f89cd Mon Sep 17 00:00:00 2001 From: Christopher Phillips Date: Tue, 15 Mar 2022 11:01:21 -0400 Subject: [PATCH 01/42] wip Signed-off-by: Christopher Phillips --- cmd/attest.go | 83 ++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 72 insertions(+), 11 deletions(-) diff --git a/cmd/attest.go b/cmd/attest.go index b2e0e9ff3ce..467f71957f6 100644 --- a/cmd/attest.go +++ b/cmd/attest.go @@ -12,6 +12,12 @@ import ( "github.com/anchore/syft/internal/formats/cyclonedxjson" "github.com/anchore/syft/internal/formats/spdx22json" "github.com/anchore/syft/internal/formats/syftjson" + cbundle "github.com/sigstore/cosign/pkg/cosign/bundle" + "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/anchore/stereoscope" "github.com/anchore/stereoscope/pkg/image" @@ -26,6 +32,7 @@ import ( "github.com/in-toto/in-toto-golang/in_toto" "github.com/pkg/errors" "github.com/pkg/profile" + "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" @@ -113,19 +120,22 @@ func fetchPassword(_ bool) (b []byte, err error) { } func selectPassFunc(keypath string) (cosign.PassFunc, error) { - keyContents, err := os.ReadFile(keypath) - if err != nil { - return nil, err - } + if keypath != "" { + keyContents, err := os.ReadFile(keypath) + if err != nil { + return nil, err + } + var fn cosign.PassFunc = func(bool) (b []byte, err error) { return nil, nil } - var fn cosign.PassFunc = func(bool) (b []byte, err error) { return nil, nil } + _, err = cosign.LoadPrivateKey(keyContents, nil) + if err != nil { + fn = fetchPassword + } - _, err = cosign.LoadPrivateKey(keyContents, nil) - if err != nil { - fn = fetchPassword + return fn, nil } - return fn, nil + return nil, nil } func attestExec(ctx context.Context, _ *cobra.Command, args []string) error { @@ -163,14 +173,23 @@ func attestExec(ctx context.Context, _ *cobra.Command, args []string) error { return fmt.Errorf("could not produce attestation predicate for given format: %q. Available formats: %+v", formatAliases(format.ID()), formatAliases(attestFormats...)) } + appConfig.Attest.Key = "" passFunc, err := selectPassFunc(appConfig.Attest.Key) if err != nil { return err } ko := sign.KeyOpts{ - KeyRef: appConfig.Attest.Key, - PassFunc: passFunc, + KeyRef: "", + PassFunc: passFunc, + Sk: false, + Slot: "signature", + FulcioURL: "https://fulcio.sigstore.dev", + InsecureSkipFulcioVerify: true, + RekorURL: "https://rekor.sigstore.dev", + OIDCIssuer: "https://oauth2.sigstore.dev/auth", + OIDCClientID: "sigstore", + OIDCClientSecret: "", } sv, err := sign.SignerFromKeyOpts(ctx, "", ko) @@ -223,6 +242,33 @@ func attestationExecWorker(sourceInput source.Input, format sbom.Format, predica return errs } +type tlogUploadFn func(*client.Rekor, []byte) (*models.LogEntryAnon, error) + +func uploadToTlog(ctx context.Context, sv *sign.SignerVerifier, rekorURL string, upload tlogUploadFn) (*cbundle.RekorBundle, error) { + var rekorBytes []byte + // Upload the cert or the public key, depending on what we have + if sv.Cert != nil { + rekorBytes = sv.Cert + } else { + pemBytes, err := sigs.PublicKeyPem(sv, signatureoptions.WithContext(ctx)) + if err != nil { + return nil, err + } + rekorBytes = pemBytes + } + + rekorClient, err := rekor.NewClient(rekorURL) + if err != nil { + return nil, err + } + entry, err := upload(rekorClient, rekorBytes) + if err != nil { + return nil, err + } + fmt.Fprintln(os.Stderr, "tlog entry created with index:", *entry.LogIndex) + return cbundle.EntryToBundle(entry), nil +} + func formatPredicateType(format sbom.Format) string { switch format.ID() { case spdx22json.ID: @@ -269,11 +315,26 @@ func generateAttestation(predicate []byte, src *source.Source, sv *sign.SignerVe return err } + opts := []static.Option{static.WithLayerMediaType(types.DssePayloadType)} + if sv.Cert != nil { + opts = append(opts, static.WithCertChain(sv.Cert, sv.Chain)) + } + signedPayload, err := wrapped.SignMessage(bytes.NewReader(payload), signatureoptions.WithContext(context.Background())) if err != nil { return errors.Wrap(err, "unable to sign SBOM") } + ctx := context.Background() + + bundle, err := uploadToTlog(ctx, sv, "https://rekor.sigstore.dev", func(r *client.Rekor, b []byte) (*models.LogEntryAnon, error) { + return cosign.TLogUploadInTotoAttestation(ctx, r, signedPayload, b) + }) + if err != nil { + return err + } + opts = append(opts, static.WithBundle(bundle)) + bus.Publish(partybus.Event{ Type: event.Exit, Value: func() error { From bfb3827453ebb8f3131ef6f374bd3de010123199 Mon Sep 17 00:00:00 2001 From: Christopher Phillips Date: Tue, 15 Mar 2022 13:00:02 -0400 Subject: [PATCH 02/42] clean up and consolodate validation functions Signed-off-by: Christopher Phillips --- cmd/attest.go | 87 +++++++++++++++++++++------------------ go.mod | 2 +- internal/config/attest.go | 10 ++--- 3 files changed, 53 insertions(+), 46 deletions(-) diff --git a/cmd/attest.go b/cmd/attest.go index 467f71957f6..28ebe44fded 100644 --- a/cmd/attest.go +++ b/cmd/attest.go @@ -9,6 +9,7 @@ import ( "os" "strings" + "github.com/anchore/syft/internal/config" "github.com/anchore/syft/internal/formats/cyclonedxjson" "github.com/anchore/syft/internal/formats/spdx22json" "github.com/anchore/syft/internal/formats/syftjson" @@ -20,7 +21,6 @@ import ( "github.com/sigstore/rekor/pkg/generated/models" "github.com/anchore/stereoscope" - "github.com/anchore/stereoscope/pkg/image" "github.com/anchore/syft/internal" "github.com/anchore/syft/internal/bus" "github.com/anchore/syft/internal/log" @@ -138,61 +138,66 @@ func selectPassFunc(keypath string) (cosign.PassFunc, error) { return nil, nil } -func attestExec(ctx context.Context, _ *cobra.Command, args []string) error { - // can only be an image for attestation or OCI DIR - userInput := args[0] - si, err := source.ParseInput(userInput, appConfig.Platform, false) - if err != nil { - return fmt.Errorf("could not generate source input for attest command: %w", err) +func validateAttestationArgs(appConfig *config.Application, si *source.Input) (format sbom.Format, predicateType string, ko *sign.KeyOpts, err error) { + ko = &sign.KeyOpts{ + Sk: false, + Slot: "signature", + FulcioURL: "https://fulcio.sigstore.dev", + InsecureSkipFulcioVerify: false, + RekorURL: "https://rekor.sigstore.dev", + OIDCIssuer: "https://oauth2.sigstore.dev/auth", + OIDCClientID: "sigstore", + OIDCClientSecret: "", } + // if the original detection was from a local daemon we want to short circuit + // that and attempt to generate the image source from a registry source instead switch si.Scheme { case source.ImageScheme, source.UnknownScheme: - // at this point we know that it cannot be dir: or file: schemes, so we will assume that the unknown scheme could represent an image + // at this point we know that it cannot be dir: or file: schemes + // so we will assume that the unknown scheme could represent an image si.Scheme = source.ImageScheme default: - return fmt.Errorf("attest command can only be used with image sources but discovered %q when given %q", si.Scheme, userInput) + return format, predicateType, ko, fmt.Errorf("attest command can only be used with image sources but discovered %q when given %q", si.Scheme, si.UserInput) } - // if the original detection was from a local daemon we want to short circuit - // that and attempt to generate the image source from a registry source instead - switch si.ImageSource { - case image.UnknownSource, image.OciRegistrySource: - si.ImageSource = image.OciRegistrySource - default: - return 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) + if len(appConfig.Outputs) > 1 { + return format, predicateType, ko, fmt.Errorf("unable to generate attestation for more than one output") } - if len(appConfig.Outputs) > 1 { - return fmt.Errorf("unable to generate attestation for more than one output") + if appConfig.Attest.KeyRef != "" { + passFunc, err := selectPassFunc(appConfig.Attest.KeyRef) + if err != nil { + return format, predicateType, ko, err + } + + ko.PassFunc = passFunc + ko.KeyRef = appConfig.Attest.KeyRef } - format := syft.FormatByName(appConfig.Outputs[0]) - predicateType := formatPredicateType(format) + format = syft.FormatByName(appConfig.Outputs[0]) + predicateType = formatPredicateType(format) if predicateType == "" { - return fmt.Errorf("could not produce attestation predicate for given format: %q. Available formats: %+v", formatAliases(format.ID()), formatAliases(attestFormats...)) + return format, predicateType, ko, fmt.Errorf("could not produce attestation predicate for given format: %q. Available formats: %+v", formatAliases(format.ID()), formatAliases(attestFormats...)) } - appConfig.Attest.Key = "" - passFunc, err := selectPassFunc(appConfig.Attest.Key) + return format, predicateType, ko, err +} + +func attestExec(ctx context.Context, _ *cobra.Command, args []string) error { + // can only be an image from an OCI registry for attestation + userInput := args[0] + si, err := source.ParseInput(userInput, appConfig.Platform, false) if err != nil { - return err + return fmt.Errorf("could not generate source input for attest command: %w", err) } - ko := sign.KeyOpts{ - KeyRef: "", - PassFunc: passFunc, - Sk: false, - Slot: "signature", - FulcioURL: "https://fulcio.sigstore.dev", - InsecureSkipFulcioVerify: true, - RekorURL: "https://rekor.sigstore.dev", - OIDCIssuer: "https://oauth2.sigstore.dev/auth", - OIDCClientID: "sigstore", - OIDCClientSecret: "", + format, predicateType, ko, err := validateAttestationArgs(appConfig, si) + if err != nil { + return err } - sv, err := sign.SignerFromKeyOpts(ctx, "", ko) + sv, err := sign.SignerFromKeyOpts(ctx, "", *ko) if err != nil { return err } @@ -265,7 +270,10 @@ func uploadToTlog(ctx context.Context, sv *sign.SignerVerifier, rekorURL string, if err != nil { return nil, err } - fmt.Fprintln(os.Stderr, "tlog entry created with index:", *entry.LogIndex) + _, err = fmt.Fprintln(os.Stderr, "tlog entry created with index:", *entry.LogIndex) + if err != nil { + return nil, err + } return cbundle.EntryToBundle(entry), nil } @@ -327,13 +335,12 @@ func generateAttestation(predicate []byte, src *source.Source, sv *sign.SignerVe ctx := context.Background() - bundle, err := uploadToTlog(ctx, sv, "https://rekor.sigstore.dev", func(r *client.Rekor, b []byte) (*models.LogEntryAnon, error) { + _, err = uploadToTlog(ctx, sv, "https://rekor.sigstore.dev", func(r *client.Rekor, b []byte) (*models.LogEntryAnon, error) { return cosign.TLogUploadInTotoAttestation(ctx, r, signedPayload, b) }) if err != nil { return err } - opts = append(opts, static.WithBundle(bundle)) bus.Publish(partybus.Event{ Type: event.Exit, @@ -356,7 +363,7 @@ func init() { func setAttestFlags(flags *pflag.FlagSet) { // key options - flags.StringP("key", "", "cosign.key", + flags.StringP("key", "", "", "path to the private key file to use for attestation", ) diff --git a/go.mod b/go.mod index 54b2a48171f..7794d43e8b2 100644 --- a/go.mod +++ b/go.mod @@ -59,6 +59,7 @@ require ( require ( github.com/docker/docker v20.10.12+incompatible github.com/sigstore/cosign v1.6.0 + github.com/sigstore/rekor v0.4.1-0.20220114213500-23f583409af3 ) require ( @@ -217,7 +218,6 @@ require ( github.com/segmentio/ksuid v1.0.4 // indirect github.com/shibumi/go-pathspec v1.3.0 // indirect github.com/sigstore/fulcio v0.1.2-0.20220114150912-86a2036f9bc7 // indirect - github.com/sigstore/rekor v0.4.1-0.20220114213500-23f583409af3 // indirect github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect github.com/soheilhy/cmux v0.1.5 // indirect github.com/spiffe/go-spiffe/v2 v2.0.0-beta.12 // indirect diff --git a/internal/config/attest.go b/internal/config/attest.go index f658b944ff0..8c5648ecc94 100644 --- a/internal/config/attest.go +++ b/internal/config/attest.go @@ -9,18 +9,18 @@ import ( ) type attest struct { - Key string `yaml:"key" json:"key" mapstructure:"key"` // same as --key, file path to the private key + KeyRef string `yaml:"key" json:"key" mapstructure:"key"` // same as --key, file path to the private key // IMPORTANT: do not show the password in any YAML/JSON output (sensitive information) Password string `yaml:"-" json:"-" mapstructure:"password"` // password for the private key } func (cfg *attest) parseConfigValues() error { - if cfg.Key != "" { - expandedPath, err := homedir.Expand(cfg.Key) + if cfg.KeyRef != "" { + expandedPath, err := homedir.Expand(cfg.KeyRef) if err != nil { - return fmt.Errorf("unable to expand key path=%q: %w", cfg.Key, err) + return fmt.Errorf("unable to expand key path=%q: %w", cfg.KeyRef, err) } - cfg.Key = expandedPath + cfg.KeyRef = expandedPath } if cfg.Password == "" { From ce069ae59195b1ac98803defc19024bd06391b7d Mon Sep 17 00:00:00 2001 From: Christopher Phillips Date: Tue, 15 Mar 2022 14:11:20 -0400 Subject: [PATCH 03/42] update tests with new path and new launcher Signed-off-by: Christopher Phillips --- cmd/attest.go | 3 +- test/cli/cosign_test.go | 76 ++++++++++++++++++++- test/cli/test-fixtures/attestation/Makefile | 23 +++++++ 3 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 test/cli/test-fixtures/attestation/Makefile diff --git a/cmd/attest.go b/cmd/attest.go index 28ebe44fded..4a8e24ef436 100644 --- a/cmd/attest.go +++ b/cmd/attest.go @@ -187,7 +187,7 @@ func validateAttestationArgs(appConfig *config.Application, si *source.Input) (f func attestExec(ctx context.Context, _ *cobra.Command, args []string) error { // can only be an image from an OCI registry for attestation userInput := args[0] - si, err := source.ParseInput(userInput, appConfig.Platform, false) + si, err := source.ParseInput(userInput, appConfig.Platform, true) if err != nil { return fmt.Errorf("could not generate source input for attest command: %w", err) } @@ -299,6 +299,7 @@ func findValidDigest(digests []string) string { } func generateAttestation(predicate []byte, src *source.Source, sv *sign.SignerVerifier, predicateType string) error { + // TODO add ghcr registry parsing to get correct digest based on user input switch len(src.Image.Metadata.RepoDigests) { case 0: return fmt.Errorf("cannot generate attestation since no repo digests were found; make sure you're passing an OCI registry source for the attest command") diff --git a/test/cli/cosign_test.go b/test/cli/cosign_test.go index 82318099163..ffd19856731 100644 --- a/test/cli/cosign_test.go +++ b/test/cli/cosign_test.go @@ -54,7 +54,7 @@ func TestCosignWorkflow(t *testing.T) { cleanup func() }{ { - name: "cosign verify syft attest", + name: "cosign verify syft attest hard pki", syftArgs: []string{ "attest", "-o", @@ -127,6 +127,80 @@ func TestCosignWorkflow(t *testing.T) { cmd := exec.Command("make", "stop") cmd.Dir = fixturesPath + runAndShow(t, cmd) + }, + }, + { + name: "cosign verify syft attest keyless", + 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{ + 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", "attestation") + 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", "attestation") + 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) }, }, diff --git a/test/cli/test-fixtures/attestation/Makefile b/test/cli/test-fixtures/attestation/Makefile new file mode 100644 index 00000000000..4a55e2aaa98 --- /dev/null +++ b/test/cli/test-fixtures/attestation/Makefile @@ -0,0 +1,23 @@ +all: build start + +.PHONY: stop +stop: + docker kill registry + +.PHONY: build +build: + docker build -t localhost:5000/attest:latest . + +.PHONY: start +start: + docker run --rm \ + -d \ + --name registry \ + -it \ + --privileged \ + -p 5000:5000 \ + registry:2.8 + +.PHONY: push +push: + docker push localhost:5000/attest:latest From 26a8ffebe803f09f4629a5b5bda2b425a8dc1c73 Mon Sep 17 00:00:00 2001 From: Christopher Phillips Date: Tue, 15 Mar 2022 14:31:00 -0400 Subject: [PATCH 04/42] update docker-compose for e2e testing Signed-off-by: Christopher Phillips --- .../attestation/docker-compose.yaml | 149 ++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 test/cli/test-fixtures/attestation/docker-compose.yaml diff --git a/test/cli/test-fixtures/attestation/docker-compose.yaml b/test/cli/test-fixtures/attestation/docker-compose.yaml new file mode 100644 index 00000000000..5dc6bed1f76 --- /dev/null +++ b/test/cli/test-fixtures/attestation/docker-compose.yaml @@ -0,0 +1,149 @@ +version: '3.2' +services: + fulcio-server: + build: + context: . + target: "deploy" + command: [ + "fulcio-server", + "serve", + "--host=0.0.0.0", + "--port=5555", + "--ca=googleca", + "--gcp_private_ca_parent=projects/project-rekor/locations/us-central1/certificateAuthorities/sigstore", + "--ct-log-url=http://ct_server:6962/test", + # Uncomment this for production logging + # "--log_type=prod", + ] + restart: always # keep the server running + ports: + - "5555:5555" + volumes: + - ~/.config/gcloud:/root/.config/gcloud/:z + - ./config/config.jsn:/etc/fulcio-config/config.json:z + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:5555/ping"] + interval: 10s + timeout: 3s + retries: 3 + start_period: 5s + depends_on: + - dex-idp + + dex-idp: + image: dexidp/dex:v2.30.0 + user: root + command: [ + "dex", + "serve", + "/etc/config/docker-compose-config.yaml", + ] + restart: always # keep the server running + ports: + - "8888:8888" + volumes: + - ./config/dex:/etc/config/:ro + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8888/auth/healthz"] + interval: 10s + timeout: 3s + retries: 3 + start_period: 5s + + mysql: + platform: linux/amd64 + image: gcr.io/trillian-opensource-ci/db_server:v1.4.0 + environment: + - MYSQL_ROOT_PASSWORD=zaphod + - MYSQL_DATABASE=test + - MYSQL_USER=test + - MYSQL_PASSWORD=zaphod + restart: always # keep the MySQL server running + healthcheck: + test: ["CMD", "/etc/init.d/mysql", "status"] + interval: 30s + timeout: 3s + retries: 3 + start_period: 10s + + redis-server: + image: docker.io/redis:5.0.10 + command: [ + "--bind", + "0.0.0.0", + "--appendonly", + "yes" + ] + restart: always # keep the redis server running + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + timeout: 3s + retries: 3 + start_period: 5s + trillian-log-server: + image: gcr.io/projectsigstore/trillian_log_server@sha256:f850a0defd089ea844822030c67ae05bc93c91168a7dd4aceb0b6648c39f696b + command: [ + "--storage_system=mysql", + "--mysql_uri=test:zaphod@tcp(mysql:3306)/test", + "--rpc_endpoint=0.0.0.0:8090", + "--http_endpoint=0.0.0.0:8091", + "--alsologtostderr", + ] + restart: always # retry while mysql is starting up + ports: + - "8090:8090" + - "8091:8091" + depends_on: + - mysql + + trillian-log-signer: + image: gcr.io/projectsigstore/trillian_log_signer@sha256:fe90d523f6617974f70878918e4b31d49b2b46a86024bb2d6b01d2bbfed8edbf + command: [ + "--storage_system=mysql", + "--mysql_uri=test:zaphod@tcp(mysql:3306)/test", + "--rpc_endpoint=0.0.0.0:8090", + "--http_endpoint=0.0.0.0:8091", + "--force_master", + "--alsologtostderr", + ] + restart: always # retry while mysql is starting up + ports: + - "8092:8091" + depends_on: + - mysql + + rekor-server: + build: + context: . + target: "deploy" + command: [ + "rekor-server", + "serve", + "--trillian_log_server.address=trillian-log-server", + "--trillian_log_server.port=8090", + "--redis_server.address=redis-server", + "--redis_server.port=6379", + "--rekor_server.address=0.0.0.0", + "--rekor_server.signer=memory", + "--enable_attestation_storage", + "--attestation_storage_bucket=file:///var/run/attestations", + # Uncomment this for production logging + # "--log_type=prod", + ] + volumes: + - "/var/run/attestations:/var/run/attestations:z" + restart: always # keep the server running + ports: + - "3000:3000" + - "2112:2112" + depends_on: + - mysql + - redis-server + - trillian-log-server + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:3000/ping"] + interval: 10s + timeout: 3s + retries: 3 + start_period: 5s From f101e9a3032b4bc72840aac1bde82ff9f195ebb5 Mon Sep 17 00:00:00 2001 From: Christopher Phillips Date: Thu, 17 Mar 2022 05:02:07 -0400 Subject: [PATCH 05/42] wip Signed-off-by: Christopher Phillips --- cmd/attest.go | 2 +- .../attestation/Dockerfile.ctfe_init | 10 ++ .../attestation/config/config.jsn | 31 +++++ .../config/dex/docker-compose-config.yaml | 33 ++++++ .../attestation/docker-compose.yaml | 109 +----------------- 5 files changed, 78 insertions(+), 107 deletions(-) create mode 100644 test/cli/test-fixtures/attestation/Dockerfile.ctfe_init create mode 100644 test/cli/test-fixtures/attestation/config/config.jsn create mode 100644 test/cli/test-fixtures/attestation/config/dex/docker-compose-config.yaml diff --git a/cmd/attest.go b/cmd/attest.go index 4a8e24ef436..c7c0cda09ec 100644 --- a/cmd/attest.go +++ b/cmd/attest.go @@ -142,7 +142,7 @@ func validateAttestationArgs(appConfig *config.Application, si *source.Input) (f ko = &sign.KeyOpts{ Sk: false, Slot: "signature", - FulcioURL: "https://fulcio.sigstore.dev", + FulcioURL: "http://localhost:5555", InsecureSkipFulcioVerify: false, RekorURL: "https://rekor.sigstore.dev", OIDCIssuer: "https://oauth2.sigstore.dev/auth", diff --git a/test/cli/test-fixtures/attestation/Dockerfile.ctfe_init b/test/cli/test-fixtures/attestation/Dockerfile.ctfe_init new file mode 100644 index 00000000000..5b131e03bfe --- /dev/null +++ b/test/cli/test-fixtures/attestation/Dockerfile.ctfe_init @@ -0,0 +1,10 @@ +FROM golang:1.17.8@sha256:c7c94588b6445f5254fbc34df941afa10de04706deb330e62831740c9f0f2030 AS builder + +WORKDIR /root/ + +RUN go install github.com/google/trillian/cmd/createtree@v1.3.10 +ADD ./config/logid.sh /root/ +ADD ./config/ctfe /root/ctfe +RUN chmod +x /root/logid.sh + +CMD /root/logid.sh diff --git a/test/cli/test-fixtures/attestation/config/config.jsn b/test/cli/test-fixtures/attestation/config/config.jsn new file mode 100644 index 00000000000..59d58c3db42 --- /dev/null +++ b/test/cli/test-fixtures/attestation/config/config.jsn @@ -0,0 +1,31 @@ +{ + "OIDCIssuers": { + "https://accounts.google.com": { + "IssuerURL": "https://accounts.google.com", + "ClientID": "sigstore", + "Type": "email" + }, + "https://oauth2.sigstore.dev/auth": { + "IssuerURL": "https://oauth2.sigstore.dev/auth", + "ClientID": "sigstore", + "Type": "email" + }, + "http://dex-idp:8888/auth": { + "IssuerURL": "http://dex-idp:8888/auth", + "ClientID": "fulcio", + "IssuerClaim": "$.federated_claims.connector_id", + "Type": "email" + }, + "https://oidc.dlorenc.dev": { + "IssuerURL": "https://oidc.dlorenc.dev", + "ClientID": "sigstore", + "Type": "spiffe" + }, + "https://token.actions.githubusercontent.com": { + "IssuerURL": "https://token.actions.githubusercontent.com", + "ClientID": "sigstore", + "Type": "github-workflow" + } + } +} + diff --git a/test/cli/test-fixtures/attestation/config/dex/docker-compose-config.yaml b/test/cli/test-fixtures/attestation/config/dex/docker-compose-config.yaml new file mode 100644 index 00000000000..55b8d58a15e --- /dev/null +++ b/test/cli/test-fixtures/attestation/config/dex/docker-compose-config.yaml @@ -0,0 +1,33 @@ +issuer: http://dex-idp:8888/auth + +storage: + type: memory + +web: + http: 0.0.0.0:8888 + +frontend: + issuer: Fulcio in Docker Compose + +expiry: + signingKeys: "24h" + idTokens: "1m" + authRequests: "24h" + +oauth2: + responseTypes: [ "code" ] + alwaysShowLoginScreen: true + skipApprovalScreen: true + +connectors: +- type: mockCallback + id: https://any.valid.url/ + name: AlwaysApprovesOIDCProvider + +staticClients: + - id: fulcio + public: true + name: 'Fulcio in Docker Compose' + +# Dex's issuer URL + "/callback" +redirectURI: http://dex-idp:8888/auth/callback diff --git a/test/cli/test-fixtures/attestation/docker-compose.yaml b/test/cli/test-fixtures/attestation/docker-compose.yaml index 5dc6bed1f76..896ed4fb61f 100644 --- a/test/cli/test-fixtures/attestation/docker-compose.yaml +++ b/test/cli/test-fixtures/attestation/docker-compose.yaml @@ -1,25 +1,20 @@ version: '3.2' services: fulcio-server: - build: - context: . - target: "deploy" + image: bcallawa/fulcio-bd4d600f674b081a535a886a15cb8d9a command: [ - "fulcio-server", "serve", "--host=0.0.0.0", "--port=5555", - "--ca=googleca", - "--gcp_private_ca_parent=projects/project-rekor/locations/us-central1/certificateAuthorities/sigstore", + "--ca=ephemeralca", "--ct-log-url=http://ct_server:6962/test", # Uncomment this for production logging # "--log_type=prod", - ] + ] restart: always # keep the server running ports: - "5555:5555" volumes: - - ~/.config/gcloud:/root/.config/gcloud/:z - ./config/config.jsn:/etc/fulcio-config/config.json:z healthcheck: test: ["CMD", "curl", "-f", "http://localhost:5555/ping"] @@ -29,7 +24,6 @@ services: start_period: 5s depends_on: - dex-idp - dex-idp: image: dexidp/dex:v2.30.0 user: root @@ -50,100 +44,3 @@ services: retries: 3 start_period: 5s - mysql: - platform: linux/amd64 - image: gcr.io/trillian-opensource-ci/db_server:v1.4.0 - environment: - - MYSQL_ROOT_PASSWORD=zaphod - - MYSQL_DATABASE=test - - MYSQL_USER=test - - MYSQL_PASSWORD=zaphod - restart: always # keep the MySQL server running - healthcheck: - test: ["CMD", "/etc/init.d/mysql", "status"] - interval: 30s - timeout: 3s - retries: 3 - start_period: 10s - - redis-server: - image: docker.io/redis:5.0.10 - command: [ - "--bind", - "0.0.0.0", - "--appendonly", - "yes" - ] - restart: always # keep the redis server running - healthcheck: - test: ["CMD", "redis-cli", "ping"] - interval: 10s - timeout: 3s - retries: 3 - start_period: 5s - trillian-log-server: - image: gcr.io/projectsigstore/trillian_log_server@sha256:f850a0defd089ea844822030c67ae05bc93c91168a7dd4aceb0b6648c39f696b - command: [ - "--storage_system=mysql", - "--mysql_uri=test:zaphod@tcp(mysql:3306)/test", - "--rpc_endpoint=0.0.0.0:8090", - "--http_endpoint=0.0.0.0:8091", - "--alsologtostderr", - ] - restart: always # retry while mysql is starting up - ports: - - "8090:8090" - - "8091:8091" - depends_on: - - mysql - - trillian-log-signer: - image: gcr.io/projectsigstore/trillian_log_signer@sha256:fe90d523f6617974f70878918e4b31d49b2b46a86024bb2d6b01d2bbfed8edbf - command: [ - "--storage_system=mysql", - "--mysql_uri=test:zaphod@tcp(mysql:3306)/test", - "--rpc_endpoint=0.0.0.0:8090", - "--http_endpoint=0.0.0.0:8091", - "--force_master", - "--alsologtostderr", - ] - restart: always # retry while mysql is starting up - ports: - - "8092:8091" - depends_on: - - mysql - - rekor-server: - build: - context: . - target: "deploy" - command: [ - "rekor-server", - "serve", - "--trillian_log_server.address=trillian-log-server", - "--trillian_log_server.port=8090", - "--redis_server.address=redis-server", - "--redis_server.port=6379", - "--rekor_server.address=0.0.0.0", - "--rekor_server.signer=memory", - "--enable_attestation_storage", - "--attestation_storage_bucket=file:///var/run/attestations", - # Uncomment this for production logging - # "--log_type=prod", - ] - volumes: - - "/var/run/attestations:/var/run/attestations:z" - restart: always # keep the server running - ports: - - "3000:3000" - - "2112:2112" - depends_on: - - mysql - - redis-server - - trillian-log-server - healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:3000/ping"] - interval: 10s - timeout: 3s - retries: 3 - start_period: 5s From ab4998c13b23727177c2570a870c52c10a412b4b Mon Sep 17 00:00:00 2001 From: Christopher Phillips Date: Thu, 17 Mar 2022 15:09:33 -0400 Subject: [PATCH 06/42] wip Signed-off-by: Christopher Phillips --- cmd/attest.go | 8 +- .../attestation/config/ctfe/ct_server.cfg | 0 .../attestation/docker-compose.yaml | 88 ++++++++++++++++++- 3 files changed, 89 insertions(+), 7 deletions(-) create mode 100644 test/cli/test-fixtures/attestation/config/ctfe/ct_server.cfg diff --git a/cmd/attest.go b/cmd/attest.go index c7c0cda09ec..ff877ba1535 100644 --- a/cmd/attest.go +++ b/cmd/attest.go @@ -142,7 +142,7 @@ func validateAttestationArgs(appConfig *config.Application, si *source.Input) (f ko = &sign.KeyOpts{ Sk: false, Slot: "signature", - FulcioURL: "http://localhost:5555", + FulcioURL: "https://fulcio.sigstore.dev", InsecureSkipFulcioVerify: false, RekorURL: "https://rekor.sigstore.dev", OIDCIssuer: "https://oauth2.sigstore.dev/auth", @@ -300,12 +300,8 @@ func findValidDigest(digests []string) string { func generateAttestation(predicate []byte, src *source.Source, sv *sign.SignerVerifier, predicateType string) error { // TODO add ghcr registry parsing to get correct digest based on user input - switch len(src.Image.Metadata.RepoDigests) { - case 0: + if len(src.Image.Metadata.RepoDigests) < 1 { return 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: - default: - return fmt.Errorf("cannot generate attestation since multiple repo digests were found for the image: %+v", src.Image.Metadata.RepoDigests) } wrapped := dsse.WrapSigner(sv, intotoJSONDsseType) diff --git a/test/cli/test-fixtures/attestation/config/ctfe/ct_server.cfg b/test/cli/test-fixtures/attestation/config/ctfe/ct_server.cfg new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/cli/test-fixtures/attestation/docker-compose.yaml b/test/cli/test-fixtures/attestation/docker-compose.yaml index 896ed4fb61f..027d85788b4 100644 --- a/test/cli/test-fixtures/attestation/docker-compose.yaml +++ b/test/cli/test-fixtures/attestation/docker-compose.yaml @@ -1,5 +1,35 @@ version: '3.2' services: + rekor-server: + image: caphill4/rekor-server:latest + command: [ + "rekor-server", + "serve", + "--trillian_log_server.address=trillian-log-server", + "--trillian_log_server.port=8090", + "--rekor_server.address=0.0.0.0", + "--rekor_server.signer=memory", + "--enable_attestation_storage", + "--attestation_storage_bucket=file:///var/run/attestations", + # Uncomment this for production logging + # "--log_type=prod", + ] + volumes: + - "/var/run/attestations:/var/run/attestations:z" + restart: always # keep the server running + ports: + - "3000:3000" + - "2112:2112" + depends_on: + - mysql + - trillian-log-server + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:3000/ping"] + interval: 10s + timeout: 3s + retries: 3 + start_period: 5s + fulcio-server: image: bcallawa/fulcio-bd4d600f674b081a535a886a15cb8d9a command: [ @@ -43,4 +73,60 @@ services: timeout: 3s retries: 3 start_period: 5s - + ct_server: + image: gcr.io/trillian-opensource-ci/ctfe + command: [ + "--log_rpc_server", "trillian-log-server:8096", + "--http_endpoint", "0.0.0.0:6962", + "--alsologtostderr", + ] + restart: always # retry while ctfe_init is running + depends_on: + - trillian-log-server + - trillian-log-signer + ports: + - "6962:6962" + mysql: + image: gcr.io/trillian-opensource-ci/db_server:3c8193ebb2d7fedb44d18e9c810d0d2e4dbb7e4d + environment: + - MYSQL_ROOT_PASSWORD=password + - MYSQL_DATABASE=test + - MYSQL_USER=test + - MYSQL_PASSWORD=password + restart: always # keep the MySQL server running + healthcheck: + test: ["CMD", "/etc/init.d/mysql", "status"] + interval: 30s + timeout: 3s + retries: 3 + start_period: 10s + trillian-log-server: + image: gcr.io/trillian-opensource-ci/log_server + command: [ + "--storage_system=mysql", + "--mysql_uri=test:password@tcp(mysql:3306)/test", + "--rpc_endpoint=0.0.0.0:8096", + "--http_endpoint=0.0.0.0:8095", + "--alsologtostderr", + ] + restart: always # retry while mysql is starting up + ports: + - "8095:8090" + - "8096:8091" + depends_on: + - mysql + trillian-log-signer: + image: gcr.io/trillian-opensource-ci/log_signer + command: [ + "--storage_system=mysql", + "--mysql_uri=test:password@tcp(mysql:3306)/test", + "--rpc_endpoint=0.0.0.0:8095", + "--http_endpoint=0.0.0.0:8096", + "--force_master", + "--alsologtostderr", + ] + restart: always # retry while mysql is starting up + ports: + - "8097:8096" + depends_on: + - mysql From e49bdbcdf10a0d107b2b9d8de79fc3953103d434 Mon Sep 17 00:00:00 2001 From: Christopher Phillips Date: Fri, 18 Mar 2022 13:24:29 -0400 Subject: [PATCH 07/42] wip Signed-off-by: Christopher Phillips --- .../attestation/config/config.jsn | 20 ----- .../attestation/config/ctfe/ct_server.cfg | 11 +++ .../attestation/config/ctfe/privkey.pem | 8 ++ .../attestation/config/ctfe/pubkey.pem | 4 + .../attestation/config/ctfe/root.pem | 13 +++ .../test-fixtures/attestation/config/logid.sh | 45 +++++++++++ .../attestation/docker-compose.yaml | 79 +++---------------- 7 files changed, 90 insertions(+), 90 deletions(-) create mode 100644 test/cli/test-fixtures/attestation/config/ctfe/privkey.pem create mode 100644 test/cli/test-fixtures/attestation/config/ctfe/pubkey.pem create mode 100644 test/cli/test-fixtures/attestation/config/ctfe/root.pem create mode 100644 test/cli/test-fixtures/attestation/config/logid.sh diff --git a/test/cli/test-fixtures/attestation/config/config.jsn b/test/cli/test-fixtures/attestation/config/config.jsn index 59d58c3db42..0688d2bdf65 100644 --- a/test/cli/test-fixtures/attestation/config/config.jsn +++ b/test/cli/test-fixtures/attestation/config/config.jsn @@ -1,31 +1,11 @@ { "OIDCIssuers": { - "https://accounts.google.com": { - "IssuerURL": "https://accounts.google.com", - "ClientID": "sigstore", - "Type": "email" - }, - "https://oauth2.sigstore.dev/auth": { - "IssuerURL": "https://oauth2.sigstore.dev/auth", - "ClientID": "sigstore", - "Type": "email" - }, "http://dex-idp:8888/auth": { "IssuerURL": "http://dex-idp:8888/auth", "ClientID": "fulcio", "IssuerClaim": "$.federated_claims.connector_id", "Type": "email" }, - "https://oidc.dlorenc.dev": { - "IssuerURL": "https://oidc.dlorenc.dev", - "ClientID": "sigstore", - "Type": "spiffe" - }, - "https://token.actions.githubusercontent.com": { - "IssuerURL": "https://token.actions.githubusercontent.com", - "ClientID": "sigstore", - "Type": "github-workflow" - } } } diff --git a/test/cli/test-fixtures/attestation/config/ctfe/ct_server.cfg b/test/cli/test-fixtures/attestation/config/ctfe/ct_server.cfg index e69de29bb2d..7ef4ebae06f 100644 --- a/test/cli/test-fixtures/attestation/config/ctfe/ct_server.cfg +++ b/test/cli/test-fixtures/attestation/config/ctfe/ct_server.cfg @@ -0,0 +1,11 @@ +config { + log_id: %LOGID% + prefix: "test" + roots_pem_file: "/etc/config/root.pem" + private_key: { + [type.googleapis.com/keyspb.PEMKeyFile] { + path: "/etc/config/privkey.pem" + password: "foobar" + } + } +} diff --git a/test/cli/test-fixtures/attestation/config/ctfe/privkey.pem b/test/cli/test-fixtures/attestation/config/ctfe/privkey.pem new file mode 100644 index 00000000000..64b1950a5ba --- /dev/null +++ b/test/cli/test-fixtures/attestation/config/ctfe/privkey.pem @@ -0,0 +1,8 @@ +-----BEGIN EC PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: DES-CBC,05BAAA9143C46320 + +AttbquLclNy7ZEnlDFpReZvV2PZKuv89YMWqDvGGtnBVw+3eXYIa54Xli1CyXEPn +qNGvibjIxj+Q19+VhA3n42SE2fHyULHKPZHebSL5qcVvZTqmbtAe/dZNH1SiGG2f +bWauIw0oeHhXW5i9isxrLggPMRmPA65Ii3W7gyWFmjE= +-----END EC PRIVATE KEY----- diff --git a/test/cli/test-fixtures/attestation/config/ctfe/pubkey.pem b/test/cli/test-fixtures/attestation/config/ctfe/pubkey.pem new file mode 100644 index 00000000000..8a3e7b56c95 --- /dev/null +++ b/test/cli/test-fixtures/attestation/config/ctfe/pubkey.pem @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEbbQiLx6GKy6ivhc11wJGbQjc2VX/ +mnuk5d670MTXR3p+LIAcxd5MhqIHpLmyYJ5mDKLEoZ/pC0nPuje3JueBcA== +-----END PUBLIC KEY----- diff --git a/test/cli/test-fixtures/attestation/config/ctfe/root.pem b/test/cli/test-fixtures/attestation/config/ctfe/root.pem new file mode 100644 index 00000000000..f658126af11 --- /dev/null +++ b/test/cli/test-fixtures/attestation/config/ctfe/root.pem @@ -0,0 +1,13 @@ +-----BEGIN CERTIFICATE----- +MIIB+DCCAX6gAwIBAgITNVkDZoCiofPDsy7dfm6geLbuhzAKBggqhkjOPQQDAzAq +MRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxETAPBgNVBAMTCHNpZ3N0b3JlMB4XDTIx +MDMwNzAzMjAyOVoXDTMxMDIyMzAzMjAyOVowKjEVMBMGA1UEChMMc2lnc3RvcmUu +ZGV2MREwDwYDVQQDEwhzaWdzdG9yZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABLSy +A7Ii5k+pNO8ZEWY0ylemWDowOkNa3kL+GZE5Z5GWehL9/A9bRNA3RbrsZ5i0Jcas +taRL7Sp5fp/jD5dxqc/UdTVnlvS16an+2Yfswe/QuLolRUCrcOE2+2iA5+tzd6Nm +MGQwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYE +FMjFHQBBmiQpMlEk6w2uSu1KBtPsMB8GA1UdIwQYMBaAFMjFHQBBmiQpMlEk6w2u +Su1KBtPsMAoGCCqGSM49BAMDA2gAMGUCMH8liWJfMui6vXXBhjDgY4MwslmN/TJx +Ve/83WrFomwmNf056y1X48F9c4m3a3ozXAIxAKjRay5/aj/jsKKGIkmQatjI8uup +Hr/+CxFvaJWmpYqNkLDGRU+9orzh5hI2RrcuaQ== +-----END CERTIFICATE----- diff --git a/test/cli/test-fixtures/attestation/config/logid.sh b/test/cli/test-fixtures/attestation/config/logid.sh new file mode 100644 index 00000000000..bffed30ef15 --- /dev/null +++ b/test/cli/test-fixtures/attestation/config/logid.sh @@ -0,0 +1,45 @@ +#!/bin/bash +# +# Copyright 2021 The Sigstore Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +function get_log_id() { + curl -s --retry-connrefused --retry 10 http://trillian-log-server:8090/metrics |grep "^quota_acquired_tokens{spec=\"trees"|head -1|awk ' { print $1 } '|sed -e 's/[^0-9]*//g' > /tmp/logid +} + +function create_log () { + /go/bin/createtree -admin_server trillian-log-server:8091 > /tmp/logid + echo -n "Created log ID " && cat /tmp/logid +} + +function update_config() { + cat /root/ctfe/ct_server.cfg | sed -e "s/%LOGID%/"`cat /tmp/logid`"/g" > /etc/config/ct_server.cfg + cp /root/ctfe/*.pem /etc/config/ +} + +# check to see if log id exists; if so, use that +echo "Checking for existing configuration..." +get_log_id +if ! [[ -s /tmp/logid ]]; then + echo "No log found; let's create one..." + create_log + # update config file accordingly + update_config +else + echo "Log ID known but config not found" + update_config +fi +configid=`cat /etc/config/ct_server.cfg|grep log_id|awk ' { print $2 } '` +echo "Exisiting configuration uses log ID $configid, exiting" diff --git a/test/cli/test-fixtures/attestation/docker-compose.yaml b/test/cli/test-fixtures/attestation/docker-compose.yaml index 027d85788b4..dd0db53145e 100644 --- a/test/cli/test-fixtures/attestation/docker-compose.yaml +++ b/test/cli/test-fixtures/attestation/docker-compose.yaml @@ -1,87 +1,26 @@ version: '3.2' services: - rekor-server: - image: caphill4/rekor-server:latest - command: [ - "rekor-server", - "serve", - "--trillian_log_server.address=trillian-log-server", - "--trillian_log_server.port=8090", - "--rekor_server.address=0.0.0.0", - "--rekor_server.signer=memory", - "--enable_attestation_storage", - "--attestation_storage_bucket=file:///var/run/attestations", - # Uncomment this for production logging - # "--log_type=prod", - ] - volumes: - - "/var/run/attestations:/var/run/attestations:z" - restart: always # keep the server running - ports: - - "3000:3000" - - "2112:2112" + ctfe_init: + build: + context: . + dockerfile: Dockerfile.ctfe_init depends_on: - - mysql - trillian-log-server - healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:3000/ping"] - interval: 10s - timeout: 3s - retries: 3 - start_period: 5s - - fulcio-server: - image: bcallawa/fulcio-bd4d600f674b081a535a886a15cb8d9a - command: [ - "serve", - "--host=0.0.0.0", - "--port=5555", - "--ca=ephemeralca", - "--ct-log-url=http://ct_server:6962/test", - # Uncomment this for production logging - # "--log_type=prod", - ] - restart: always # keep the server running - ports: - - "5555:5555" volumes: - - ./config/config.jsn:/etc/fulcio-config/config.json:z - healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:5555/ping"] - interval: 10s - timeout: 3s - retries: 3 - start_period: 5s - depends_on: - - dex-idp - dex-idp: - image: dexidp/dex:v2.30.0 - user: root - command: [ - "dex", - "serve", - "/etc/config/docker-compose-config.yaml", - ] - restart: always # keep the server running - ports: - - "8888:8888" - volumes: - - ./config/dex:/etc/config/:ro - healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:8888/auth/healthz"] - interval: 10s - timeout: 3s - retries: 3 - start_period: 5s + - ./config/ctfe/:/etc/config/:rw ct_server: image: gcr.io/trillian-opensource-ci/ctfe + volumes: + - ./config/ctfe/:/etc/config/:ro command: [ + "--log_config" ,"/etc/config/ct_server.cfg", "--log_rpc_server", "trillian-log-server:8096", "--http_endpoint", "0.0.0.0:6962", "--alsologtostderr", ] restart: always # retry while ctfe_init is running depends_on: + - ctfe_init - trillian-log-server - trillian-log-signer ports: From 8afc1fbe6a2998866b5503bbee723c532f5cdabd Mon Sep 17 00:00:00 2001 From: Christopher Phillips Date: Sun, 20 Mar 2022 14:19:58 -0400 Subject: [PATCH 08/42] master election working for trillian Signed-off-by: Christopher Phillips --- test/cli/test-fixtures/attestation/Makefile | 23 ---------- .../attestation/config/config.jsn | 11 ----- .../config/ctfe/{privkey.pem => privkey,pem} | 0 .../config/dex/docker-compose-config.yaml | 33 -------------- .../test-fixtures/attestation/config/logid.sh | 9 ++-- .../attestation/docker-compose.yaml | 43 ++++++------------- 6 files changed, 20 insertions(+), 99 deletions(-) delete mode 100644 test/cli/test-fixtures/attestation/Makefile delete mode 100644 test/cli/test-fixtures/attestation/config/config.jsn rename test/cli/test-fixtures/attestation/config/ctfe/{privkey.pem => privkey,pem} (100%) delete mode 100644 test/cli/test-fixtures/attestation/config/dex/docker-compose-config.yaml diff --git a/test/cli/test-fixtures/attestation/Makefile b/test/cli/test-fixtures/attestation/Makefile deleted file mode 100644 index 4a55e2aaa98..00000000000 --- a/test/cli/test-fixtures/attestation/Makefile +++ /dev/null @@ -1,23 +0,0 @@ -all: build start - -.PHONY: stop -stop: - docker kill registry - -.PHONY: build -build: - docker build -t localhost:5000/attest:latest . - -.PHONY: start -start: - docker run --rm \ - -d \ - --name registry \ - -it \ - --privileged \ - -p 5000:5000 \ - registry:2.8 - -.PHONY: push -push: - docker push localhost:5000/attest:latest diff --git a/test/cli/test-fixtures/attestation/config/config.jsn b/test/cli/test-fixtures/attestation/config/config.jsn deleted file mode 100644 index 0688d2bdf65..00000000000 --- a/test/cli/test-fixtures/attestation/config/config.jsn +++ /dev/null @@ -1,11 +0,0 @@ -{ - "OIDCIssuers": { - "http://dex-idp:8888/auth": { - "IssuerURL": "http://dex-idp:8888/auth", - "ClientID": "fulcio", - "IssuerClaim": "$.federated_claims.connector_id", - "Type": "email" - }, - } -} - diff --git a/test/cli/test-fixtures/attestation/config/ctfe/privkey.pem b/test/cli/test-fixtures/attestation/config/ctfe/privkey,pem similarity index 100% rename from test/cli/test-fixtures/attestation/config/ctfe/privkey.pem rename to test/cli/test-fixtures/attestation/config/ctfe/privkey,pem diff --git a/test/cli/test-fixtures/attestation/config/dex/docker-compose-config.yaml b/test/cli/test-fixtures/attestation/config/dex/docker-compose-config.yaml deleted file mode 100644 index 55b8d58a15e..00000000000 --- a/test/cli/test-fixtures/attestation/config/dex/docker-compose-config.yaml +++ /dev/null @@ -1,33 +0,0 @@ -issuer: http://dex-idp:8888/auth - -storage: - type: memory - -web: - http: 0.0.0.0:8888 - -frontend: - issuer: Fulcio in Docker Compose - -expiry: - signingKeys: "24h" - idTokens: "1m" - authRequests: "24h" - -oauth2: - responseTypes: [ "code" ] - alwaysShowLoginScreen: true - skipApprovalScreen: true - -connectors: -- type: mockCallback - id: https://any.valid.url/ - name: AlwaysApprovesOIDCProvider - -staticClients: - - id: fulcio - public: true - name: 'Fulcio in Docker Compose' - -# Dex's issuer URL + "/callback" -redirectURI: http://dex-idp:8888/auth/callback diff --git a/test/cli/test-fixtures/attestation/config/logid.sh b/test/cli/test-fixtures/attestation/config/logid.sh index bffed30ef15..0af1020ff55 100644 --- a/test/cli/test-fixtures/attestation/config/logid.sh +++ b/test/cli/test-fixtures/attestation/config/logid.sh @@ -16,11 +16,11 @@ function get_log_id() { - curl -s --retry-connrefused --retry 10 http://trillian-log-server:8090/metrics |grep "^quota_acquired_tokens{spec=\"trees"|head -1|awk ' { print $1 } '|sed -e 's/[^0-9]*//g' > /tmp/logid + curl -s --retry-connrefused --retry 10 http://trillian-log-server:8095/metrics |grep "^quota_acquired_tokens{spec=\"trees"|head -1|awk ' { print $1 } '|sed -e 's/[^0-9]*//g' > /tmp/logid } function create_log () { - /go/bin/createtree -admin_server trillian-log-server:8091 > /tmp/logid + /go/bin/createtree -admin_server trillian-log-server:8096 > /tmp/logid echo -n "Created log ID " && cat /tmp/logid } @@ -30,8 +30,10 @@ function update_config() { } # check to see if log id exists; if so, use that -echo "Checking for existing configuration..." +echo -n "Checking for existing configuration..." +echo "Checking for preexisting logs..." get_log_id +# else create one if ! [[ -s /tmp/logid ]]; then echo "No log found; let's create one..." create_log @@ -41,5 +43,6 @@ else echo "Log ID known but config not found" update_config fi + configid=`cat /etc/config/ct_server.cfg|grep log_id|awk ' { print $2 } '` echo "Exisiting configuration uses log ID $configid, exiting" diff --git a/test/cli/test-fixtures/attestation/docker-compose.yaml b/test/cli/test-fixtures/attestation/docker-compose.yaml index dd0db53145e..777211b6261 100644 --- a/test/cli/test-fixtures/attestation/docker-compose.yaml +++ b/test/cli/test-fixtures/attestation/docker-compose.yaml @@ -1,30 +1,5 @@ version: '3.2' services: - ctfe_init: - build: - context: . - dockerfile: Dockerfile.ctfe_init - depends_on: - - trillian-log-server - volumes: - - ./config/ctfe/:/etc/config/:rw - ct_server: - image: gcr.io/trillian-opensource-ci/ctfe - volumes: - - ./config/ctfe/:/etc/config/:ro - command: [ - "--log_config" ,"/etc/config/ct_server.cfg", - "--log_rpc_server", "trillian-log-server:8096", - "--http_endpoint", "0.0.0.0:6962", - "--alsologtostderr", - ] - restart: always # retry while ctfe_init is running - depends_on: - - ctfe_init - - trillian-log-server - - trillian-log-signer - ports: - - "6962:6962" mysql: image: gcr.io/trillian-opensource-ci/db_server:3c8193ebb2d7fedb44d18e9c810d0d2e4dbb7e4d environment: @@ -39,6 +14,14 @@ services: timeout: 3s retries: 3 start_period: 10s + ctfe_init: + build: + context: . + dockerfile: Dockerfile.ctfe_init + depends_on: + - trillian-log-server + volumes: + - ctfeConfig:/etc/config/:rw trillian-log-server: image: gcr.io/trillian-opensource-ci/log_server command: [ @@ -50,8 +33,8 @@ services: ] restart: always # retry while mysql is starting up ports: - - "8095:8090" - - "8096:8091" + - "8096:8096" + - "8095:8095" depends_on: - mysql trillian-log-signer: @@ -60,12 +43,14 @@ services: "--storage_system=mysql", "--mysql_uri=test:password@tcp(mysql:3306)/test", "--rpc_endpoint=0.0.0.0:8095", - "--http_endpoint=0.0.0.0:8096", + "--http_endpoint=0.0.0.0:8097", "--force_master", "--alsologtostderr", ] restart: always # retry while mysql is starting up ports: - - "8097:8096" + - "8097:8097" depends_on: - mysql +volumes: + ctfeConfig: {} From a9a59204f3a581773a1126bc16b7f4e67904baf7 Mon Sep 17 00:00:00 2001 From: Christopher Phillips Date: Sun, 20 Mar 2022 14:43:01 -0400 Subject: [PATCH 09/42] ct_server up and running Signed-off-by: Christopher Phillips --- .../config/ctfe/{privkey,pem => privkey.pem} | 0 .../test-fixtures/attestation/config/logid.sh | 10 +++++----- .../attestation/docker-compose.yaml | 17 +++++++++++++++++ 3 files changed, 22 insertions(+), 5 deletions(-) rename test/cli/test-fixtures/attestation/config/ctfe/{privkey,pem => privkey.pem} (100%) diff --git a/test/cli/test-fixtures/attestation/config/ctfe/privkey,pem b/test/cli/test-fixtures/attestation/config/ctfe/privkey.pem similarity index 100% rename from test/cli/test-fixtures/attestation/config/ctfe/privkey,pem rename to test/cli/test-fixtures/attestation/config/ctfe/privkey.pem diff --git a/test/cli/test-fixtures/attestation/config/logid.sh b/test/cli/test-fixtures/attestation/config/logid.sh index 0af1020ff55..2e56a2a9226 100644 --- a/test/cli/test-fixtures/attestation/config/logid.sh +++ b/test/cli/test-fixtures/attestation/config/logid.sh @@ -37,12 +37,12 @@ get_log_id if ! [[ -s /tmp/logid ]]; then echo "No log found; let's create one..." create_log - # update config file accordingly - update_config -else - echo "Log ID known but config not found" - update_config fi +echo "Updating config with current log" +update_config configid=`cat /etc/config/ct_server.cfg|grep log_id|awk ' { print $2 } '` echo "Exisiting configuration uses log ID $configid, exiting" +echo "Printing shared directory contents" + +ls -alt /etc/config diff --git a/test/cli/test-fixtures/attestation/docker-compose.yaml b/test/cli/test-fixtures/attestation/docker-compose.yaml index 777211b6261..7e522fa599b 100644 --- a/test/cli/test-fixtures/attestation/docker-compose.yaml +++ b/test/cli/test-fixtures/attestation/docker-compose.yaml @@ -22,6 +22,23 @@ services: - trillian-log-server volumes: - ctfeConfig:/etc/config/:rw + ct_server: + image: gcr.io/trillian-opensource-ci/ctfe + volumes: + - ctfeConfig:/etc/config/:rw + command: [ + "--log_config" ,"/etc/config/ct_server.cfg", + "--log_rpc_server", "trillian-log-server:8096", + "--http_endpoint", "0.0.0.0:6962", + "--alsologtostderr", + ] + restart: always # retry while ctfe_init is running + depends_on: + - trillian-log-server + - trillian-log-signer + - ctfe_init + ports: + - "6962:6962" trillian-log-server: image: gcr.io/trillian-opensource-ci/log_server command: [ From 56410ec497de8e992d5a6c26ac9844b407514644 Mon Sep 17 00:00:00 2001 From: Christopher Phillips Date: Sun, 20 Mar 2022 15:49:47 -0400 Subject: [PATCH 10/42] update hard values for local testing Signed-off-by: Christopher Phillips --- cmd/attest.go | 4 +- .../attestation/config/config.json | 10 ++++ .../config/dex/docker-compose-config.yaml | 48 +++++++++++++++++++ .../test-fixtures/attestation/config/logid.sh | 3 -- .../attestation/docker-compose-config.yaml | 48 +++++++++++++++++++ .../attestation/docker-compose.yaml | 43 +++++++++++++++++ 6 files changed, 151 insertions(+), 5 deletions(-) create mode 100644 test/cli/test-fixtures/attestation/config/config.json create mode 100644 test/cli/test-fixtures/attestation/config/dex/docker-compose-config.yaml create mode 100644 test/cli/test-fixtures/attestation/docker-compose-config.yaml diff --git a/cmd/attest.go b/cmd/attest.go index ff877ba1535..926df61dcb0 100644 --- a/cmd/attest.go +++ b/cmd/attest.go @@ -142,10 +142,10 @@ func validateAttestationArgs(appConfig *config.Application, si *source.Input) (f ko = &sign.KeyOpts{ Sk: false, Slot: "signature", - FulcioURL: "https://fulcio.sigstore.dev", + FulcioURL: "http://localhost:5555", InsecureSkipFulcioVerify: false, RekorURL: "https://rekor.sigstore.dev", - OIDCIssuer: "https://oauth2.sigstore.dev/auth", + OIDCIssuer: "http://localhost:8888/auth", OIDCClientID: "sigstore", OIDCClientSecret: "", } diff --git a/test/cli/test-fixtures/attestation/config/config.json b/test/cli/test-fixtures/attestation/config/config.json new file mode 100644 index 00000000000..1f9f5eeefdb --- /dev/null +++ b/test/cli/test-fixtures/attestation/config/config.json @@ -0,0 +1,10 @@ +{ + "OIDCIssuers": { + "http://dex-idp:8888/auth": { + "IssuerURL": "http://dex-idp:8888/auth", + "ClientID": "fulcio", + "IssuerClaim": "$.federated_claims.connector_id", + "Type": "email" + } + } +} diff --git a/test/cli/test-fixtures/attestation/config/dex/docker-compose-config.yaml b/test/cli/test-fixtures/attestation/config/dex/docker-compose-config.yaml new file mode 100644 index 00000000000..f0e01b5668d --- /dev/null +++ b/test/cli/test-fixtures/attestation/config/dex/docker-compose-config.yaml @@ -0,0 +1,48 @@ +# +# Copyright 2021 The Sigstore Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +issuer: http://dex-idp:8888/auth + +storage: + type: memory + +web: + http: 0.0.0.0:8888 + +frontend: + issuer: Fulcio in Docker Compose + +expiry: + signingKeys: "24h" + idTokens: "1m" + authRequests: "24h" + +oauth2: + responseTypes: [ "code" ] + alwaysShowLoginScreen: true + skipApprovalScreen: true + +connectors: +- type: mockCallback + id: https://any.valid.url/ + name: AlwaysApprovesOIDCProvider + +staticClients: + - id: fulcio + public: true + name: 'Fulcio in Docker Compose' + +# Dex's issuer URL + "/callback" +redirectURI: http://dex-idp:8888/auth/callback diff --git a/test/cli/test-fixtures/attestation/config/logid.sh b/test/cli/test-fixtures/attestation/config/logid.sh index 2e56a2a9226..2e3aea37234 100644 --- a/test/cli/test-fixtures/attestation/config/logid.sh +++ b/test/cli/test-fixtures/attestation/config/logid.sh @@ -43,6 +43,3 @@ update_config configid=`cat /etc/config/ct_server.cfg|grep log_id|awk ' { print $2 } '` echo "Exisiting configuration uses log ID $configid, exiting" -echo "Printing shared directory contents" - -ls -alt /etc/config diff --git a/test/cli/test-fixtures/attestation/docker-compose-config.yaml b/test/cli/test-fixtures/attestation/docker-compose-config.yaml new file mode 100644 index 00000000000..f0e01b5668d --- /dev/null +++ b/test/cli/test-fixtures/attestation/docker-compose-config.yaml @@ -0,0 +1,48 @@ +# +# Copyright 2021 The Sigstore Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +issuer: http://dex-idp:8888/auth + +storage: + type: memory + +web: + http: 0.0.0.0:8888 + +frontend: + issuer: Fulcio in Docker Compose + +expiry: + signingKeys: "24h" + idTokens: "1m" + authRequests: "24h" + +oauth2: + responseTypes: [ "code" ] + alwaysShowLoginScreen: true + skipApprovalScreen: true + +connectors: +- type: mockCallback + id: https://any.valid.url/ + name: AlwaysApprovesOIDCProvider + +staticClients: + - id: fulcio + public: true + name: 'Fulcio in Docker Compose' + +# Dex's issuer URL + "/callback" +redirectURI: http://dex-idp:8888/auth/callback diff --git a/test/cli/test-fixtures/attestation/docker-compose.yaml b/test/cli/test-fixtures/attestation/docker-compose.yaml index 7e522fa599b..71630905067 100644 --- a/test/cli/test-fixtures/attestation/docker-compose.yaml +++ b/test/cli/test-fixtures/attestation/docker-compose.yaml @@ -1,5 +1,29 @@ version: '3.2' services: + fulcio-server: + image: fulcio_fulcio-server + command: [ + "fulcio-server", + "serve", + "--host=0.0.0.0", + "--port=5555", + "--ca=ephemeralca", + "--ct-log-url=http://ct_server:6962/test", + ] + restart: always # keep the server running + ports: + - "5555:5555" + volumes: + - ./config/config.json:/etc/fulcio-config/config.json:z + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:5555/ping"] + interval: 10s + timeout: 3s + retries: 3 + start_period: 5s + depends_on: + - dex-idp + - ct_server mysql: image: gcr.io/trillian-opensource-ci/db_server:3c8193ebb2d7fedb44d18e9c810d0d2e4dbb7e4d environment: @@ -39,6 +63,25 @@ services: - ctfe_init ports: - "6962:6962" + dex-idp: + image: dexidp/dex:v2.30.0 + user: root + command: [ + "dex", + "serve", + "/etc/config/docker-compose-config.yaml", + ] + restart: always # keep the server running + ports: + - "8888:8888" + volumes: + - ./config/dex:/etc/config/:ro + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8888/auth/healthz"] + interval: 10s + timeout: 3s + retries: 3 + start_period: 5s trillian-log-server: image: gcr.io/trillian-opensource-ci/log_server command: [ From 8e18b902ebe901d3a3c5dad8b96d3aa2fc77fbf4 Mon Sep 17 00:00:00 2001 From: Christopher Phillips Date: Sun, 20 Mar 2022 16:13:59 -0400 Subject: [PATCH 11/42] dex issues Signed-off-by: Christopher Phillips --- cmd/attest.go | 2 +- test/cli/test-fixtures/attestation/config/config.json | 11 +++++------ .../attestation/config/dex/docker-compose-config.yaml | 2 +- .../attestation/docker-compose-config.yaml | 4 ++-- 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/cmd/attest.go b/cmd/attest.go index 926df61dcb0..bfba2728c22 100644 --- a/cmd/attest.go +++ b/cmd/attest.go @@ -145,7 +145,7 @@ func validateAttestationArgs(appConfig *config.Application, si *source.Input) (f FulcioURL: "http://localhost:5555", InsecureSkipFulcioVerify: false, RekorURL: "https://rekor.sigstore.dev", - OIDCIssuer: "http://localhost:8888/auth", + OIDCIssuer: "http://dex-idp:8888/auth", OIDCClientID: "sigstore", OIDCClientSecret: "", } diff --git a/test/cli/test-fixtures/attestation/config/config.json b/test/cli/test-fixtures/attestation/config/config.json index 1f9f5eeefdb..11ad0de6ff0 100644 --- a/test/cli/test-fixtures/attestation/config/config.json +++ b/test/cli/test-fixtures/attestation/config/config.json @@ -1,10 +1,9 @@ { "OIDCIssuers": { - "http://dex-idp:8888/auth": { - "IssuerURL": "http://dex-idp:8888/auth", - "ClientID": "fulcio", - "IssuerClaim": "$.federated_claims.connector_id", - "Type": "email" - } + "https://token.actions.githubusercontent.com": { + "IssuerURL": "https://token.actions.githubusercontent.com", + "ClientID": "anchore", + "Type": "github-workflow" + } } } diff --git a/test/cli/test-fixtures/attestation/config/dex/docker-compose-config.yaml b/test/cli/test-fixtures/attestation/config/dex/docker-compose-config.yaml index f0e01b5668d..8fe230af067 100644 --- a/test/cli/test-fixtures/attestation/config/dex/docker-compose-config.yaml +++ b/test/cli/test-fixtures/attestation/config/dex/docker-compose-config.yaml @@ -19,7 +19,7 @@ storage: type: memory web: - http: 0.0.0.0:8888 + http: dex-idp:8888 frontend: issuer: Fulcio in Docker Compose diff --git a/test/cli/test-fixtures/attestation/docker-compose-config.yaml b/test/cli/test-fixtures/attestation/docker-compose-config.yaml index f0e01b5668d..14d88dd8e22 100644 --- a/test/cli/test-fixtures/attestation/docker-compose-config.yaml +++ b/test/cli/test-fixtures/attestation/docker-compose-config.yaml @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -issuer: http://dex-idp:8888/auth +issuer: http://0.0.0.0:8888/auth storage: type: memory @@ -31,7 +31,7 @@ expiry: oauth2: responseTypes: [ "code" ] - alwaysShowLoginScreen: true + alwaysShowLoginScreen: false skipApprovalScreen: true connectors: From 4443d4f7f4ca8e12701c385c862404ab17197615 Mon Sep 17 00:00:00 2001 From: Christopher Phillips Date: Mon, 21 Mar 2022 11:50:51 -0400 Subject: [PATCH 12/42] x509 CT server failure from fulcio Signed-off-by: Christopher Phillips --- cmd/attest.go | 2 +- test/cli/test-fixtures/attestation/config/config.json | 10 +++++----- .../attestation/config/dex/docker-compose-config.yaml | 4 ++-- .../attestation/docker-compose-config.yaml | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/cmd/attest.go b/cmd/attest.go index bfba2728c22..3910309e4ce 100644 --- a/cmd/attest.go +++ b/cmd/attest.go @@ -146,7 +146,7 @@ func validateAttestationArgs(appConfig *config.Application, si *source.Input) (f InsecureSkipFulcioVerify: false, RekorURL: "https://rekor.sigstore.dev", OIDCIssuer: "http://dex-idp:8888/auth", - OIDCClientID: "sigstore", + OIDCClientID: "fulcio", OIDCClientSecret: "", } diff --git a/test/cli/test-fixtures/attestation/config/config.json b/test/cli/test-fixtures/attestation/config/config.json index 11ad0de6ff0..92ef253623f 100644 --- a/test/cli/test-fixtures/attestation/config/config.json +++ b/test/cli/test-fixtures/attestation/config/config.json @@ -1,9 +1,9 @@ { "OIDCIssuers": { - "https://token.actions.githubusercontent.com": { - "IssuerURL": "https://token.actions.githubusercontent.com", - "ClientID": "anchore", - "Type": "github-workflow" - } + "http://dex-idp:8888/auth": { + "IssuerURL": "http://dex-idp:8888/auth", + "ClientID": "fulcio", + "Type": "email" + } } } diff --git a/test/cli/test-fixtures/attestation/config/dex/docker-compose-config.yaml b/test/cli/test-fixtures/attestation/config/dex/docker-compose-config.yaml index 8fe230af067..e04402909fb 100644 --- a/test/cli/test-fixtures/attestation/config/dex/docker-compose-config.yaml +++ b/test/cli/test-fixtures/attestation/config/dex/docker-compose-config.yaml @@ -31,12 +31,12 @@ expiry: oauth2: responseTypes: [ "code" ] - alwaysShowLoginScreen: true + alwaysShowLoginScreen: false skipApprovalScreen: true connectors: - type: mockCallback - id: https://any.valid.url/ + id: approved name: AlwaysApprovesOIDCProvider staticClients: diff --git a/test/cli/test-fixtures/attestation/docker-compose-config.yaml b/test/cli/test-fixtures/attestation/docker-compose-config.yaml index 14d88dd8e22..f0e01b5668d 100644 --- a/test/cli/test-fixtures/attestation/docker-compose-config.yaml +++ b/test/cli/test-fixtures/attestation/docker-compose-config.yaml @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -issuer: http://0.0.0.0:8888/auth +issuer: http://dex-idp:8888/auth storage: type: memory @@ -31,7 +31,7 @@ expiry: oauth2: responseTypes: [ "code" ] - alwaysShowLoginScreen: false + alwaysShowLoginScreen: true skipApprovalScreen: true connectors: From 3259c71656dd842fc6aebb1ced7fb2a4ca3a0c5b Mon Sep 17 00:00:00 2001 From: Christopher Phillips Date: Mon, 21 Mar 2022 12:56:36 -0400 Subject: [PATCH 13/42] local flow works Signed-off-by: Christopher Phillips --- cmd/attest.go | 2 +- .../test-fixtures/attestation/config/ctfe/root.pem | 13 ------------- test/cli/test-fixtures/attestation/config/logid.sh | 9 +++++++++ .../test-fixtures/attestation/docker-compose.yaml | 2 +- 4 files changed, 11 insertions(+), 15 deletions(-) delete mode 100644 test/cli/test-fixtures/attestation/config/ctfe/root.pem diff --git a/cmd/attest.go b/cmd/attest.go index 3910309e4ce..d843351908c 100644 --- a/cmd/attest.go +++ b/cmd/attest.go @@ -143,7 +143,7 @@ func validateAttestationArgs(appConfig *config.Application, si *source.Input) (f Sk: false, Slot: "signature", FulcioURL: "http://localhost:5555", - InsecureSkipFulcioVerify: false, + InsecureSkipFulcioVerify: true, RekorURL: "https://rekor.sigstore.dev", OIDCIssuer: "http://dex-idp:8888/auth", OIDCClientID: "fulcio", diff --git a/test/cli/test-fixtures/attestation/config/ctfe/root.pem b/test/cli/test-fixtures/attestation/config/ctfe/root.pem deleted file mode 100644 index f658126af11..00000000000 --- a/test/cli/test-fixtures/attestation/config/ctfe/root.pem +++ /dev/null @@ -1,13 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIB+DCCAX6gAwIBAgITNVkDZoCiofPDsy7dfm6geLbuhzAKBggqhkjOPQQDAzAq -MRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxETAPBgNVBAMTCHNpZ3N0b3JlMB4XDTIx -MDMwNzAzMjAyOVoXDTMxMDIyMzAzMjAyOVowKjEVMBMGA1UEChMMc2lnc3RvcmUu -ZGV2MREwDwYDVQQDEwhzaWdzdG9yZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABLSy -A7Ii5k+pNO8ZEWY0ylemWDowOkNa3kL+GZE5Z5GWehL9/A9bRNA3RbrsZ5i0Jcas -taRL7Sp5fp/jD5dxqc/UdTVnlvS16an+2Yfswe/QuLolRUCrcOE2+2iA5+tzd6Nm -MGQwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYE -FMjFHQBBmiQpMlEk6w2uSu1KBtPsMB8GA1UdIwQYMBaAFMjFHQBBmiQpMlEk6w2u -Su1KBtPsMAoGCCqGSM49BAMDA2gAMGUCMH8liWJfMui6vXXBhjDgY4MwslmN/TJx -Ve/83WrFomwmNf056y1X48F9c4m3a3ozXAIxAKjRay5/aj/jsKKGIkmQatjI8uup -Hr/+CxFvaJWmpYqNkLDGRU+9orzh5hI2RrcuaQ== ------END CERTIFICATE----- diff --git a/test/cli/test-fixtures/attestation/config/logid.sh b/test/cli/test-fixtures/attestation/config/logid.sh index 2e3aea37234..1e54f40f4de 100644 --- a/test/cli/test-fixtures/attestation/config/logid.sh +++ b/test/cli/test-fixtures/attestation/config/logid.sh @@ -19,6 +19,10 @@ function get_log_id() { curl -s --retry-connrefused --retry 10 http://trillian-log-server:8095/metrics |grep "^quota_acquired_tokens{spec=\"trees"|head -1|awk ' { print $1 } '|sed -e 's/[^0-9]*//g' > /tmp/logid } +function get_ephemeral_ca() { + curl -s --retry-connrefused --retry 10 http://fulcio-server:5555/api/v1/rootCert > /etc/config/root.pem +} + function create_log () { /go/bin/createtree -admin_server trillian-log-server:8096 > /tmp/logid echo -n "Created log ID " && cat /tmp/logid @@ -43,3 +47,8 @@ update_config configid=`cat /etc/config/ct_server.cfg|grep log_id|awk ' { print $2 } '` echo "Exisiting configuration uses log ID $configid, exiting" + +echo "Grabing fulcio root pem file" +get_ephemeral_ca + +echo "Finished ct_server configuration" diff --git a/test/cli/test-fixtures/attestation/docker-compose.yaml b/test/cli/test-fixtures/attestation/docker-compose.yaml index 71630905067..fa2b8da4d78 100644 --- a/test/cli/test-fixtures/attestation/docker-compose.yaml +++ b/test/cli/test-fixtures/attestation/docker-compose.yaml @@ -23,7 +23,6 @@ services: start_period: 5s depends_on: - dex-idp - - ct_server mysql: image: gcr.io/trillian-opensource-ci/db_server:3c8193ebb2d7fedb44d18e9c810d0d2e4dbb7e4d environment: @@ -44,6 +43,7 @@ services: dockerfile: Dockerfile.ctfe_init depends_on: - trillian-log-server + - fulcio-server volumes: - ctfeConfig:/etc/config/:rw ct_server: From f3c7ab5b9168860ccf0218c1c665f8a63b743caa Mon Sep 17 00:00:00 2001 From: Christopher Phillips Date: Mon, 21 Mar 2022 13:37:51 -0400 Subject: [PATCH 14/42] add skeleton flags for keyless workflow wip Signed-off-by: Christopher Phillips --- cmd/attest.go | 56 +++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 52 insertions(+), 4 deletions(-) diff --git a/cmd/attest.go b/cmd/attest.go index d843351908c..77e0521425d 100644 --- a/cmd/attest.go +++ b/cmd/attest.go @@ -9,6 +9,8 @@ import ( "os" "strings" + "github.com/sigstore/cosign/cmd/cosign/cli/options" + "github.com/anchore/syft/internal/config" "github.com/anchore/syft/internal/formats/cyclonedxjson" "github.com/anchore/syft/internal/formats/spdx22json" @@ -359,12 +361,39 @@ func init() { } func setAttestFlags(flags *pflag.FlagSet) { - // key options - flags.StringP("key", "", "", + // attestation options + flags.StringP( + "key", "", "", "path to the private key file to use for attestation", ) - // in-toto attestations only support JSON predicates, so not all SBOM formats that syft can output are supported + flags.StringP( + "fulcio_url", "", options.DefaultFulcioURL, + "", + ) + + flags.StringP( + "rekor_url", "", options.DefaultRekorURL, + "", + ) + + flags.StringP( + "oidc_issuer", "", options.DefaultOIDCIssuerURL, + "", + ) + + flags.StringP( + "oidc_client_id", "", "", + "", + ) + + flags.StringP( + "oidc_client_secret", "", "", + "", + ) + + // in-toto attestations only support JSON predicates + // not all SBOM formats that syft can output are supported flags.StringP( "output", "o", formatAliases(syftjson.ID)[0], fmt.Sprintf("the SBOM format encapsulated within the attestation, available options=%v", formatAliases(attestFormats...)), @@ -378,10 +407,29 @@ func setAttestFlags(flags *pflag.FlagSet) { func bindAttestConfigOptions(flags *pflag.FlagSet) error { // note: output is not included since this configuration option is shared between multiple subcommands - if err := viper.BindPFlag("attest.key", flags.Lookup("key")); err != nil { return err } + if err := viper.BindPFlag("attest.fulcio_url", flags.Lookup("fulcio_url")); err != nil { + return err + } + + if err := viper.BindPFlag("attest.rekor_url", flags.Lookup("rekor_url")); err != nil { + return err + } + + if err := viper.BindPFlag("attest.oidc_issuer", flags.Lookup("oidc_issuer")); err != nil { + return err + } + + if err := viper.BindPFlag("attest.oidc_client_id", flags.Lookup("oidc_client_id")); err != nil { + return err + } + + if err := viper.BindPFlag("attest.oidc_client_secret", flags.Lookup("oidc_client_secret")); err != nil { + return err + } + return nil } From 659e1da5d6ad8b89442b9908669ef50895666081 Mon Sep 17 00:00:00 2001 From: Christopher Phillips Date: Tue, 22 Mar 2022 11:57:47 -0400 Subject: [PATCH 15/42] update options to sane defaults Signed-off-by: Christopher Phillips --- cmd/attest.go | 13 +++++++------ internal/config/attest.go | 13 ++++++++++++- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/cmd/attest.go b/cmd/attest.go index 77e0521425d..634708198cf 100644 --- a/cmd/attest.go +++ b/cmd/attest.go @@ -142,14 +142,15 @@ func selectPassFunc(keypath string) (cosign.PassFunc, error) { func validateAttestationArgs(appConfig *config.Application, si *source.Input) (format sbom.Format, predicateType string, ko *sign.KeyOpts, err error) { ko = &sign.KeyOpts{ + KeyRef: appConfig.Attest.KeyRef, Sk: false, Slot: "signature", - FulcioURL: "http://localhost:5555", - InsecureSkipFulcioVerify: true, - RekorURL: "https://rekor.sigstore.dev", - OIDCIssuer: "http://dex-idp:8888/auth", - OIDCClientID: "fulcio", - OIDCClientSecret: "", + FulcioURL: appConfig.Attest.FulcioURL, + InsecureSkipFulcioVerify: false, + RekorURL: appConfig.Attest.RekorURL, + OIDCIssuer: appConfig.Attest.OIDCIssuer, + OIDCClientID: appConfig.Attest.OIDCClientID, + OIDCClientSecret: appConfig.Attest.OIDCClientSecret, } // if the original detection was from a local daemon we want to short circuit diff --git a/internal/config/attest.go b/internal/config/attest.go index 8c5648ecc94..81f1880f4e2 100644 --- a/internal/config/attest.go +++ b/internal/config/attest.go @@ -5,13 +5,20 @@ import ( "os" "github.com/mitchellh/go-homedir" + "github.com/sigstore/cosign/cmd/cosign/cli/options" "github.com/spf13/viper" ) type attest struct { KeyRef string `yaml:"key" json:"key" mapstructure:"key"` // same as --key, file path to the private key // IMPORTANT: do not show the password in any YAML/JSON output (sensitive information) - Password string `yaml:"-" json:"-" mapstructure:"password"` // password for the private key + Password string `yaml:"-" json:"-" mapstructure:"password"` // password for the private key + FulcioURL string `yaml:"fulcio_url" json:"fulcioUrl" mapstructure:"fulcio_url"` + InsecureSkipFulcioVerify bool `yaml:""` + 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"` + OIDCClientSecret string `yaml:"oidc_client_secret" json:"oidcClientSecret" mapstructure:"oidc_client_secret"` } func (cfg *attest) parseConfigValues() error { @@ -35,4 +42,8 @@ func (cfg *attest) parseConfigValues() error { func (cfg attest) loadDefaultValues(v *viper.Viper) { 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") } From 37ca6b20e69977ee4701966b9023d51542de4519 Mon Sep 17 00:00:00 2001 From: Christopher Phillips Date: Mon, 2 May 2022 12:36:43 -0400 Subject: [PATCH 16/42] update keyless options to new cmd format Signed-off-by: Christopher Phillips --- cmd/syft/cli/options/attest.go | 54 ++++++++++++++++++++++++++++++++-- cmd/syft/cli/options/fulcio.go | 48 ++++++++++++++++++++++++++++++ cmd/syft/cli/options/oidc.go | 48 ++++++++++++++++++++++++++++++ cmd/syft/cli/options/rekor.go | 32 ++++++++++++++++++++ 4 files changed, 180 insertions(+), 2 deletions(-) create mode 100644 cmd/syft/cli/options/fulcio.go create mode 100644 cmd/syft/cli/options/oidc.go create mode 100644 cmd/syft/cli/options/rekor.go diff --git a/cmd/syft/cli/options/attest.go b/cmd/syft/cli/options/attest.go index b36d1cb3f46..71afa192ef7 100644 --- a/cmd/syft/cli/options/attest.go +++ b/cmd/syft/cli/options/attest.go @@ -6,16 +6,46 @@ import ( "github.com/spf13/viper" ) +const defaultKeyFileName = "cosign.key" + type AttestOptions struct { - Key string + Key string + Cert string + NoUpload bool + Force bool + Recursive bool + Replace bool + + Rekor RekorOptions + Fulcio FulcioOptions + OIDC OIDCOptions } var _ Interface = (*AttestOptions)(nil) func (o *AttestOptions) AddFlags(cmd *cobra.Command, v *viper.Viper) error { - cmd.PersistentFlags().StringVarP(&o.Key, "key", "", "cosign.key", + o.Rekor.AddFlags(cmd, v) + o.Fulcio.AddFlags(cmd, v) + o.OIDC.AddFlags(cmd, v) + + 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") + + cmd.Flags().BoolVarP(&o.Replace, "replace", "", false, + "") + return bindAttestationConfigOptions(cmd.PersistentFlags(), v) } @@ -24,5 +54,25 @@ func bindAttestationConfigOptions(flags *pflag.FlagSet, v *viper.Viper) error { 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 + } + + if err := v.BindPFlag("attest.replace", flags.Lookup("replace")); err != nil { + return err + } + return nil } diff --git a/cmd/syft/cli/options/fulcio.go b/cmd/syft/cli/options/fulcio.go new file mode 100644 index 00000000000..2ccddaed34d --- /dev/null +++ b/cmd/syft/cli/options/fulcio.go @@ -0,0 +1,48 @@ +package options + +import ( + "github.com/spf13/cobra" + "github.com/spf13/pflag" + "github.com/spf13/viper" +) + +const DefaultFulcioURL = "https://fulcio.sigstore.dev" + +// FulcioOptions is the wrapper for Fulcio related options. +type FulcioOptions struct { + URL string + IdentityToken string + InsecureSkipFulcioVerify bool +} + +var _ Interface = (*FulcioOptions)(nil) + +// AddFlags implements Interface +func (o *FulcioOptions) AddFlags(cmd *cobra.Command, v *viper.Viper) error { + // TODO: change this back to api.SigstorePublicServerURL after the v1 migration is complete. + cmd.Flags().StringVar(&o.URL, "fulcio-url", DefaultFulcioURL, + "address of sigstore PKI server") + + cmd.Flags().StringVar(&o.IdentityToken, "identity-token", "", + "identity token to use for certificate from fulcio") + + cmd.Flags().BoolVar(&o.InsecureSkipFulcioVerify, "insecure-skip-verify", false, + "skip verifying fulcio published to the SCT (this should only be used for testing).") + return bindFulcioConfigOptions(cmd.Flags(), v) +} + +func bindFulcioConfigOptions(flags *pflag.FlagSet, v *viper.Viper) error { + if err := v.BindPFlag("attest.fulcio.fulcio-url", flags.Lookup("fulcio-url")); err != nil { + return err + } + + if err := v.BindPFlag("attest.fulcio.identity-token", flags.Lookup("identity-token")); err != nil { + return err + } + + if err := v.BindPFlag("attest.fulcio.insecure-skip-verify", flags.Lookup("insecure-skip-verify")); err != nil { + return err + } + + return nil +} diff --git a/cmd/syft/cli/options/oidc.go b/cmd/syft/cli/options/oidc.go new file mode 100644 index 00000000000..f88ec8c4600 --- /dev/null +++ b/cmd/syft/cli/options/oidc.go @@ -0,0 +1,48 @@ +package options + +import ( + "github.com/spf13/cobra" + "github.com/spf13/pflag" + "github.com/spf13/viper" +) + +const DefaultOIDCIssuerURL = "https://oauth2.sigstore.dev/auth" + +// OIDCOptions is the wrapper for OIDC related options. +type OIDCOptions struct { + Issuer string + ClientID string + RedirectURL string +} + +var _ Interface = (*OIDCOptions)(nil) + +// AddFlags implements Interface +func (o *OIDCOptions) AddFlags(cmd *cobra.Command, v *viper.Viper) error { + cmd.Flags().StringVar(&o.Issuer, "oidc-issuer", DefaultOIDCIssuerURL, + "OIDC provider to be used to issue ID token") + + cmd.Flags().StringVar(&o.ClientID, "oidc-client-id", "sigstore", + "OIDC client ID for application") + + cmd.Flags().StringVar(&o.RedirectURL, "oidc-redirect-url", "", + "OIDC redirect URL (Optional). The default oidc-redirect-url is 'http://localhost:0/auth/callback'.") + + return bindOIDCConfigOptions(cmd.Flags(), v) +} + +func bindOIDCConfigOptions(flags *pflag.FlagSet, v *viper.Viper) error { + if err := v.BindPFlag("attest.oidc.oidc-issuer", flags.Lookup("oidc-issuer")); err != nil { + return err + } + + if err := v.BindPFlag("attest.oidc.identity-token", flags.Lookup("identity-token")); err != nil { + return err + } + + if err := v.BindPFlag("attest.oidc.oidc-redirect-url", flags.Lookup("oidc-reirect-url")); err != nil { + return err + } + + return nil +} diff --git a/cmd/syft/cli/options/rekor.go b/cmd/syft/cli/options/rekor.go new file mode 100644 index 00000000000..4f11a8b7d5b --- /dev/null +++ b/cmd/syft/cli/options/rekor.go @@ -0,0 +1,32 @@ +package options + +import ( + "github.com/spf13/cobra" + "github.com/spf13/pflag" + "github.com/spf13/viper" +) + +const DefaultRekorURL = "https://rekor.sigstore.dev" + +// RekorOptions is the wrapper for Rekor related options. +type RekorOptions struct { + URL string +} + +var _ Interface = (*RekorOptions)(nil) + +// AddFlags implements Interface +func (o *RekorOptions) AddFlags(cmd *cobra.Command, v *viper.Viper) error { + cmd.Flags().StringVar(&o.URL, "rekor-url", DefaultRekorURL, + "address of rekor STL server") + return bindAttestationConfigOptions(cmd.Flags(), v) +} + +func bindRekorConfigOptions(flags *pflag.FlagSet, v *viper.Viper) error { + // TODO: config re-design + if err := v.BindPFlag("attest.rekor.rekor-url", flags.Lookup("rekor-url")); err != nil { + return err + } + + return nil +} From ebc96d321a3a7e545c6c7ec56bc1507acc9c92f8 Mon Sep 17 00:00:00 2001 From: Christopher Phillips Date: Mon, 2 May 2022 14:56:27 -0400 Subject: [PATCH 17/42] update ko with new injected values Signed-off-by: Christopher Phillips --- cmd/syft/cli/attest/attest.go | 74 +++++++++++++++++++++++++++++++--- cmd/syft/cli/options/attest.go | 4 +- cmd/syft/cli/options/fulcio.go | 6 +-- cmd/syft/cli/options/oidc.go | 6 +-- cmd/syft/cli/options/rekor.go | 2 +- internal/config/attest.go | 9 ++++- 6 files changed, 84 insertions(+), 17 deletions(-) diff --git a/cmd/syft/cli/attest/attest.go b/cmd/syft/cli/attest/attest.go index 6638e17fe3a..a03e698b89f 100644 --- a/cmd/syft/cli/attest/attest.go +++ b/cmd/syft/cli/attest/attest.go @@ -25,8 +25,16 @@ import ( "github.com/anchore/syft/syft/source" "github.com/in-toto/in-toto-golang/in_toto" "github.com/pkg/errors" + "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/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" "github.com/wagoodman/go-partybus" @@ -62,14 +70,23 @@ func Run(ctx context.Context, app *config.Application, args []string) error { return fmt.Errorf("could not produce attestation predicate for given format: %q. Available formats: %+v", options.FormatAliases(format.ID()), options.FormatAliases(allowedAttestFormats...)) } - passFunc, err := selectPassFunc(app.Attest.KeyRef, app.Attest.Password) - if err != nil { - return err + ko := sign.KeyOpts{ + KeyRef: app.Attest.KeyRef, + Slot: "signature", + FulcioURL: app.Attest.FulcioURL, + InsecureSkipFulcioVerify: app.Attest.InsecureSkipFulcioVerify, + RekorURL: app.Attest.RekorURL, + OIDCIssuer: app.Attest.OIDCIssuer, + OIDCClientID: app.Attest.OIDCClientID, } - ko := sign.KeyOpts{ - KeyRef: app.Attest.KeyRef, - PassFunc: passFunc, + if app.Attest.KeyRef != "" { + passFunc, err := selectPassFunc(app.Attest.KeyRef, app.Attest.Password) + if err != nil { + return err + } + + ko.PassFunc = passFunc } sv, err := sign.SignerFromKeyOpts(ctx, "", "", ko) @@ -178,11 +195,26 @@ func generateAttestation(predicate []byte, src *source.Source, sv *sign.SignerVe return err } + opts := []static.Option{static.WithLayerMediaType(types.DssePayloadType)} + if sv.Cert != nil { + opts = append(opts, static.WithCertChain(sv.Cert, sv.Chain)) + } + signedPayload, err := wrapped.SignMessage(bytes.NewReader(payload), signatureoptions.WithContext(context.Background())) if err != nil { return errors.Wrap(err, "unable to sign SBOM") } + ctx := context.Background() + + // TODO: inject rekor URL here + _, err = uploadToTlog(ctx, sv, "https://rekor.sigstore.dev", func(r *client.Rekor, b []byte) (*models.LogEntryAnon, error) { + return cosign.TLogUploadInTotoAttestation(ctx, r, signedPayload, b) + }) + if err != nil { + return err + } + bus.Publish(partybus.Event{ Type: event.Exit, Value: func() error { @@ -214,3 +246,33 @@ func formatPredicateType(format sbom.Format) string { return "" } } + +type tlogUploadFn func(*client.Rekor, []byte) (*models.LogEntryAnon, error) + +func uploadToTlog(ctx context.Context, sv *sign.SignerVerifier, rekorURL string, upload tlogUploadFn) (*cbundle.RekorBundle, error) { + var rekorBytes []byte + // Upload the cert or the public key, depending on what we have + if sv.Cert != nil { + rekorBytes = sv.Cert + } else { + pemBytes, err := sigs.PublicKeyPem(sv, signatureoptions.WithContext(ctx)) + if err != nil { + return nil, err + } + rekorBytes = pemBytes + } + + rekorClient, err := rekor.NewClient(rekorURL) + if err != nil { + return nil, err + } + entry, err := upload(rekorClient, rekorBytes) + if err != nil { + return nil, err + } + _, err = fmt.Fprintln(os.Stderr, "tlog entry created with index:", *entry.LogIndex) + if err != nil { + return nil, err + } + return cbundle.EntryToBundle(entry), nil +} diff --git a/cmd/syft/cli/options/attest.go b/cmd/syft/cli/options/attest.go index 71afa192ef7..78748446071 100644 --- a/cmd/syft/cli/options/attest.go +++ b/cmd/syft/cli/options/attest.go @@ -46,7 +46,7 @@ func (o *AttestOptions) AddFlags(cmd *cobra.Command, v *viper.Viper) error { cmd.Flags().BoolVarP(&o.Replace, "replace", "", false, "") - return bindAttestationConfigOptions(cmd.PersistentFlags(), v) + return bindAttestationConfigOptions(cmd.Flags(), v) } func bindAttestationConfigOptions(flags *pflag.FlagSet, v *viper.Viper) error { @@ -58,7 +58,7 @@ func bindAttestationConfigOptions(flags *pflag.FlagSet, v *viper.Viper) error { return err } - if err := v.BindPFlag("attest.no-upload", flags.Lookup("no-upload")); err != nil { + if err := v.BindPFlag("attest.no_upload", flags.Lookup("no-upload")); err != nil { return err } diff --git a/cmd/syft/cli/options/fulcio.go b/cmd/syft/cli/options/fulcio.go index 2ccddaed34d..002d8a68845 100644 --- a/cmd/syft/cli/options/fulcio.go +++ b/cmd/syft/cli/options/fulcio.go @@ -32,15 +32,15 @@ func (o *FulcioOptions) AddFlags(cmd *cobra.Command, v *viper.Viper) error { } func bindFulcioConfigOptions(flags *pflag.FlagSet, v *viper.Viper) error { - if err := v.BindPFlag("attest.fulcio.fulcio-url", flags.Lookup("fulcio-url")); err != nil { + if err := v.BindPFlag("attest.fulcio_url", flags.Lookup("fulcio-url")); err != nil { return err } - if err := v.BindPFlag("attest.fulcio.identity-token", flags.Lookup("identity-token")); err != nil { + if err := v.BindPFlag("attest.fulcio_identity_token", flags.Lookup("identity-token")); err != nil { return err } - if err := v.BindPFlag("attest.fulcio.insecure-skip-verify", flags.Lookup("insecure-skip-verify")); err != nil { + if err := v.BindPFlag("attest.insecure_skip_verify", flags.Lookup("insecure-skip-verify")); err != nil { return err } diff --git a/cmd/syft/cli/options/oidc.go b/cmd/syft/cli/options/oidc.go index f88ec8c4600..88b4ca087bc 100644 --- a/cmd/syft/cli/options/oidc.go +++ b/cmd/syft/cli/options/oidc.go @@ -32,15 +32,15 @@ func (o *OIDCOptions) AddFlags(cmd *cobra.Command, v *viper.Viper) error { } func bindOIDCConfigOptions(flags *pflag.FlagSet, v *viper.Viper) error { - if err := v.BindPFlag("attest.oidc.oidc-issuer", flags.Lookup("oidc-issuer")); err != nil { + if err := v.BindPFlag("attest.oidc_issuer", flags.Lookup("oidc-issuer")); err != nil { return err } - if err := v.BindPFlag("attest.oidc.identity-token", flags.Lookup("identity-token")); err != nil { + if err := v.BindPFlag("attest.oidc_client_id", flags.Lookup("oidc-client-id")); err != nil { return err } - if err := v.BindPFlag("attest.oidc.oidc-redirect-url", flags.Lookup("oidc-reirect-url")); err != nil { + if err := v.BindPFlag("attest.oidc_redirect_url", flags.Lookup("oidc-reirect-url")); err != nil { return err } diff --git a/cmd/syft/cli/options/rekor.go b/cmd/syft/cli/options/rekor.go index 4f11a8b7d5b..7588b1ddc7b 100644 --- a/cmd/syft/cli/options/rekor.go +++ b/cmd/syft/cli/options/rekor.go @@ -24,7 +24,7 @@ func (o *RekorOptions) AddFlags(cmd *cobra.Command, v *viper.Viper) error { func bindRekorConfigOptions(flags *pflag.FlagSet, v *viper.Viper) error { // TODO: config re-design - if err := v.BindPFlag("attest.rekor.rekor-url", flags.Lookup("rekor-url")); err != nil { + if err := v.BindPFlag("attest.rekor_url", flags.Lookup("rekor-url")); err != nil { return err } diff --git a/internal/config/attest.go b/internal/config/attest.go index 81f1880f4e2..7d7fb63601f 100644 --- a/internal/config/attest.go +++ b/internal/config/attest.go @@ -12,13 +12,18 @@ import ( type attest struct { KeyRef string `yaml:"key" json:"key" mapstructure:"key"` // same as --key, file path to the private key // IMPORTANT: do not show the password in any YAML/JSON output (sensitive information) + 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"` - InsecureSkipFulcioVerify bool `yaml:""` + 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"` - OIDCClientSecret string `yaml:"oidc_client_secret" json:"oidcClientSecret" mapstructure:"oidc_client_secret"` } func (cfg *attest) parseConfigValues() error { From b0b3907e25db667df913554a0c968d6b36de1e7a Mon Sep 17 00:00:00 2001 From: Christopher Phillips Date: Mon, 2 May 2022 15:40:10 -0400 Subject: [PATCH 18/42] update log to use syft logging package Signed-off-by: Christopher Phillips --- cmd/syft/cli/attest/attest.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/cmd/syft/cli/attest/attest.go b/cmd/syft/cli/attest/attest.go index a03e698b89f..755880d783c 100644 --- a/cmd/syft/cli/attest/attest.go +++ b/cmd/syft/cli/attest/attest.go @@ -18,6 +18,7 @@ import ( "github.com/anchore/syft/internal/formats/cyclonedxjson" "github.com/anchore/syft/internal/formats/spdx22json" "github.com/anchore/syft/internal/formats/syftjson" + "github.com/anchore/syft/internal/log" "github.com/anchore/syft/internal/ui" "github.com/anchore/syft/syft" "github.com/anchore/syft/syft/event" @@ -270,9 +271,7 @@ func uploadToTlog(ctx context.Context, sv *sign.SignerVerifier, rekorURL string, if err != nil { return nil, err } - _, err = fmt.Fprintln(os.Stderr, "tlog entry created with index:", *entry.LogIndex) - if err != nil { - return nil, err - } + + log.Infof("tlog entry created with index: %v", *entry.LogIndex) return cbundle.EntryToBundle(entry), nil } From dbf4f87960312ebf2f148711e43f99e466d23c55 Mon Sep 17 00:00:00 2001 From: Christopher Phillips Date: Mon, 2 May 2022 23:00:05 -0400 Subject: [PATCH 19/42] static touchups Signed-off-by: Christopher Phillips --- cmd/syft/cli/attest/attest.go | 7 ------- cmd/syft/cli/options/attest.go | 12 +++++++++--- cmd/syft/cli/options/rekor.go | 2 +- go.mod | 2 +- 4 files changed, 11 insertions(+), 12 deletions(-) diff --git a/cmd/syft/cli/attest/attest.go b/cmd/syft/cli/attest/attest.go index 755880d783c..a14271368ee 100644 --- a/cmd/syft/cli/attest/attest.go +++ b/cmd/syft/cli/attest/attest.go @@ -31,9 +31,7 @@ import ( "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/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" @@ -196,11 +194,6 @@ func generateAttestation(predicate []byte, src *source.Source, sv *sign.SignerVe return err } - opts := []static.Option{static.WithLayerMediaType(types.DssePayloadType)} - if sv.Cert != nil { - opts = append(opts, static.WithCertChain(sv.Cert, sv.Chain)) - } - signedPayload, err := wrapped.SignMessage(bytes.NewReader(payload), signatureoptions.WithContext(context.Background())) if err != nil { return errors.Wrap(err, "unable to sign SBOM") diff --git a/cmd/syft/cli/options/attest.go b/cmd/syft/cli/options/attest.go index 78748446071..6cf960e75fd 100644 --- a/cmd/syft/cli/options/attest.go +++ b/cmd/syft/cli/options/attest.go @@ -24,9 +24,15 @@ type AttestOptions struct { var _ Interface = (*AttestOptions)(nil) func (o *AttestOptions) AddFlags(cmd *cobra.Command, v *viper.Viper) error { - o.Rekor.AddFlags(cmd, v) - o.Fulcio.AddFlags(cmd, v) - o.OIDC.AddFlags(cmd, v) + 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") diff --git a/cmd/syft/cli/options/rekor.go b/cmd/syft/cli/options/rekor.go index 7588b1ddc7b..b2ed6eb6e42 100644 --- a/cmd/syft/cli/options/rekor.go +++ b/cmd/syft/cli/options/rekor.go @@ -19,7 +19,7 @@ var _ Interface = (*RekorOptions)(nil) func (o *RekorOptions) AddFlags(cmd *cobra.Command, v *viper.Viper) error { cmd.Flags().StringVar(&o.URL, "rekor-url", DefaultRekorURL, "address of rekor STL server") - return bindAttestationConfigOptions(cmd.Flags(), v) + return bindRekorConfigOptions(cmd.Flags(), v) } func bindRekorConfigOptions(flags *pflag.FlagSet, v *viper.Viper) error { diff --git a/go.mod b/go.mod index 89ae61bd194..4ef2ef3fe66 100644 --- a/go.mod +++ b/go.mod @@ -58,6 +58,7 @@ require ( github.com/docker/docker v20.10.12+incompatible github.com/in-toto/in-toto-golang v0.3.4-0.20211211042327-af1f9fb822bf github.com/sigstore/cosign v1.7.2 + github.com/sigstore/rekor v0.4.1-0.20220114213500-23f583409af3 github.com/sigstore/sigstore v1.2.1-0.20220401110139-0e610e39782f ) @@ -209,7 +210,6 @@ require ( github.com/segmentio/ksuid v1.0.4 // indirect github.com/shibumi/go-pathspec v1.3.0 // indirect github.com/sigstore/fulcio v0.1.2-0.20220114150912-86a2036f9bc7 // indirect - github.com/sigstore/rekor v0.4.1-0.20220114213500-23f583409af3 // indirect github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect github.com/soheilhy/cmux v0.1.5 // indirect github.com/spiffe/go-spiffe/v2 v2.0.0 // indirect From ebded6fc696889afc8de7ec0e20e1508969b04e1 Mon Sep 17 00:00:00 2001 From: Christopher Phillips Date: Tue, 3 May 2022 12:38:11 -0400 Subject: [PATCH 20/42] update injected options Signed-off-by: Christopher Phillips --- cmd/syft/cli/attest.go | 15 ++++++++++++++- cmd/syft/cli/attest/attest.go | 20 +++++++------------- cmd/syft/cli/options/attest.go | 1 + cmd/syft/cli/options/oidc.go | 2 +- internal/config/attest.go | 6 ++++-- 5 files changed, 27 insertions(+), 17 deletions(-) diff --git a/cmd/syft/cli/attest.go b/cmd/syft/cli/attest.go index 21bfe6b9362..1eaf9146728 100644 --- a/cmd/syft/cli/attest.go +++ b/cmd/syft/cli/attest.go @@ -8,6 +8,7 @@ import ( "github.com/anchore/syft/cmd/syft/cli/options" "github.com/anchore/syft/internal" "github.com/anchore/syft/internal/config" + "github.com/sigstore/cosign/cmd/cosign/cli/sign" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -53,7 +54,19 @@ func Attest(v *viper.Viper, app *config.Application, ro *options.RootOptions) *c checkForApplicationUpdate() } - return attest.Run(cmd.Context(), app, args) + // build cosign key options for attestation + ko := &sign.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) }, } diff --git a/cmd/syft/cli/attest/attest.go b/cmd/syft/cli/attest/attest.go index a14271368ee..ccbaf716c0b 100644 --- a/cmd/syft/cli/attest/attest.go +++ b/cmd/syft/cli/attest/attest.go @@ -50,7 +50,7 @@ var ( intotoJSONDsseType = `application/vnd.in-toto+json` ) -func Run(ctx context.Context, app *config.Application, args []string) error { +func Run(ctx context.Context, app *config.Application, ko *sign.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") @@ -66,17 +66,11 @@ func Run(ctx context.Context, app *config.Application, args []string) error { format := syft.FormatByName(app.Outputs[0]) 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...)) - } - - ko := sign.KeyOpts{ - KeyRef: app.Attest.KeyRef, - Slot: "signature", - FulcioURL: app.Attest.FulcioURL, - InsecureSkipFulcioVerify: app.Attest.InsecureSkipFulcioVerify, - RekorURL: app.Attest.RekorURL, - OIDCIssuer: app.Attest.OIDCIssuer, - OIDCClientID: app.Attest.OIDCClientID, + 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 != "" { @@ -88,7 +82,7 @@ func Run(ctx context.Context, app *config.Application, args []string) error { ko.PassFunc = passFunc } - sv, err := sign.SignerFromKeyOpts(ctx, "", "", ko) + sv, err := sign.SignerFromKeyOpts(ctx, "", "", *ko) if err != nil { return err } diff --git a/cmd/syft/cli/options/attest.go b/cmd/syft/cli/options/attest.go index 6cf960e75fd..6c00bf8334f 100644 --- a/cmd/syft/cli/options/attest.go +++ b/cmd/syft/cli/options/attest.go @@ -11,6 +11,7 @@ const defaultKeyFileName = "cosign.key" type AttestOptions struct { Key string Cert string + CertChain string NoUpload bool Force bool Recursive bool diff --git a/cmd/syft/cli/options/oidc.go b/cmd/syft/cli/options/oidc.go index 88b4ca087bc..a275df8ddcf 100644 --- a/cmd/syft/cli/options/oidc.go +++ b/cmd/syft/cli/options/oidc.go @@ -40,7 +40,7 @@ func bindOIDCConfigOptions(flags *pflag.FlagSet, v *viper.Viper) error { return err } - if err := v.BindPFlag("attest.oidc_redirect_url", flags.Lookup("oidc-reirect-url")); err != nil { + if err := v.BindPFlag("attest.oidc_redirect_url", flags.Lookup("oidc-redirect-url")); err != nil { return err } diff --git a/internal/config/attest.go b/internal/config/attest.go index 7d7fb63601f..c59661e55f2 100644 --- a/internal/config/attest.go +++ b/internal/config/attest.go @@ -9,9 +9,9 @@ import ( "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 - // IMPORTANT: do not show the password in any YAML/JSON output (sensitive information) + 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"` @@ -24,6 +24,7 @@ type attest struct { 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 { @@ -46,6 +47,7 @@ func (cfg *attest) parseConfigValues() error { } 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) From 9acebc1ea2a8c993759a145c3fe49275c11b267d Mon Sep 17 00:00:00 2001 From: Christopher Phillips Date: Tue, 3 May 2022 15:17:59 -0400 Subject: [PATCH 21/42] wip Signed-off-by: Christopher Phillips --- cmd/syft/cli/attest/attest.go | 10 ++++------ go.mod | 2 +- go.sum | 2 ++ 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/cmd/syft/cli/attest/attest.go b/cmd/syft/cli/attest/attest.go index ccbaf716c0b..301d163f331 100644 --- a/cmd/syft/cli/attest/attest.go +++ b/cmd/syft/cli/attest/attest.go @@ -93,7 +93,7 @@ func Run(ctx context.Context, app *config.Application, ko *sign.KeyOpts, args [] syft.SetBus(eventBus) return eventloop.EventLoop( - execWorker(app, *si, format, predicateType, sv), + execWorker(ctx, app, *si, format, predicateType, sv), eventloop.SetupSignals(), eventBus.Subscribe(), stereoscope.Cleanup, @@ -128,7 +128,7 @@ func parseImageSource(userInput string, app *config.Application) (s *source.Inpu return si, nil } -func execWorker(app *config.Application, sourceInput source.Input, format sbom.Format, predicateType string, sv *sign.SignerVerifier) <-chan error { +func execWorker(ctx context.Context, app *config.Application, sourceInput source.Input, format sbom.Format, predicateType string, sv *sign.SignerVerifier) <-chan error { errs := make(chan error) go func() { defer close(errs) @@ -154,7 +154,7 @@ func execWorker(app *config.Application, sourceInput source.Input, format sbom.F return } - err = generateAttestation(sbomBytes, src, sv, predicateType) + err = generateAttestation(ctx, app, sbomBytes, src, sv, predicateType) if err != nil { errs <- err return @@ -163,7 +163,7 @@ func execWorker(app *config.Application, sourceInput source.Input, format sbom.F return errs } -func generateAttestation(predicate []byte, src *source.Source, sv *sign.SignerVerifier, predicateType string) error { +func generateAttestation(ctx context.Context, app *config.Application, predicate []byte, src *source.Source, sv *sign.SignerVerifier, predicateType string) error { switch len(src.Image.Metadata.RepoDigests) { case 0: return fmt.Errorf("cannot generate attestation since no repo digests were found; make sure you're passing an OCI registry source for the attest command") @@ -193,8 +193,6 @@ func generateAttestation(predicate []byte, src *source.Source, sv *sign.SignerVe return errors.Wrap(err, "unable to sign SBOM") } - ctx := context.Background() - // TODO: inject rekor URL here _, err = uploadToTlog(ctx, sv, "https://rekor.sigstore.dev", func(r *client.Rekor, b []byte) (*models.LogEntryAnon, error) { return cosign.TLogUploadInTotoAttestation(ctx, r, signedPayload, b) diff --git a/go.mod b/go.mod index 4ef2ef3fe66..e0802316a6a 100644 --- a/go.mod +++ b/go.mod @@ -319,7 +319,7 @@ require ( github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect - golang.org/x/crypto v0.0.0-20220214200702-86341886e292 // indirect + golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f // indirect golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a // indirect golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886 // indirect diff --git a/go.sum b/go.sum index 462981be2c3..73dc363e82b 100644 --- a/go.sum +++ b/go.sum @@ -2417,6 +2417,8 @@ golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220214200702-86341886e292 h1:f+lwQ+GtmgoY+A2YaQxlSOnDjXcQ7ZRLWOHbC6HtRqE= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f h1:OeJjE6G4dgCY4PIXvIRQbE8+RX+uXZyGhUy/ksMGJoc= +golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= From e5996367acac1050500ec74135d4aecc07fcef5d Mon Sep 17 00:00:00 2001 From: Christopher Phillips Date: Thu, 5 May 2022 10:00:21 -0400 Subject: [PATCH 22/42] update to upload attestation Signed-off-by: Christopher Phillips --- cmd/syft/cli/attest/attest.go | 63 ++++++++++++++++++++++++++++------- go.mod | 2 +- go.sum | 2 -- 3 files changed, 52 insertions(+), 15 deletions(-) diff --git a/cmd/syft/cli/attest/attest.go b/cmd/syft/cli/attest/attest.go index 301d163f331..de20132de66 100644 --- a/cmd/syft/cli/attest/attest.go +++ b/cmd/syft/cli/attest/attest.go @@ -6,7 +6,6 @@ import ( "encoding/json" "fmt" "os" - "strings" "github.com/anchore/stereoscope" "github.com/anchore/stereoscope/pkg/image" @@ -24,6 +23,8 @@ import ( "github.com/anchore/syft/syft/event" "github.com/anchore/syft/syft/sbom" "github.com/anchore/syft/syft/source" + "github.com/google/go-containerregistry/pkg/name" + v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/in-toto/in-toto-golang/in_toto" "github.com/pkg/errors" "github.com/sigstore/cosign/cmd/cosign/cli/rekor" @@ -31,6 +32,9 @@ import ( "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/rekor/pkg/generated/client" "github.com/sigstore/rekor/pkg/generated/models" @@ -173,11 +177,22 @@ func generateAttestation(ctx context.Context, app *config.Application, predicate } wrapped := dsse.WrapSigner(sv, intotoJSONDsseType) + ref, err := name.ParseReference(src.Metadata.ImageMetadata.UserInput) + if err != nil { + return err + } + + digest, err := ociremote.ResolveDigest(ref) + if err != nil { + return err + } + + h, _ := v1.NewHash(digest.Identifier()) sh, err := attestation.GenerateStatement(attestation.GenerateOpts{ Predicate: bytes.NewBuffer(predicate), Type: predicateType, - Digest: findValidDigest(src.Image.Metadata.RepoDigests), + Digest: h.Hex, }) if err != nil { return err @@ -193,31 +208,55 @@ func generateAttestation(ctx context.Context, app *config.Application, predicate return errors.Wrap(err, "unable to sign SBOM") } - // TODO: inject rekor URL here - _, err = uploadToTlog(ctx, sv, "https://rekor.sigstore.dev", func(r *client.Rekor, b []byte) (*models.LogEntryAnon, error) { + _, err = uploadToTlog(ctx, sv, app.Attest.RekorURL, func(r *client.Rekor, b []byte) (*models.LogEntryAnon, error) { return cosign.TLogUploadInTotoAttestation(ctx, r, signedPayload, b) }) if err != nil { return err } - bus.Publish(partybus.Event{ - Type: event.Exit, - Value: func() error { - _, err := os.Stdout.Write(signedPayload) - return err - }, - }) + if app.Attest.NoUpload { + bus.Publish(partybus.Event{ + Type: event.Exit, + Value: func() error { + _, err := os.Stdout.Write(signedPayload) + return err + }, + }) + } + + return uploadAttestation(signedPayload, digest) +} + +func uploadAttestation(signedPayload []byte, digest name.Digest) error { + sig, err := static.NewAttestation(signedPayload) + if err != nil { + return err + } + + se, err := ociremote.SignedEntity(digest) + if err != nil { + return err + } + + // Attach the attestation to the entity. + newSE, err := mutate.AttachAttestationToEntity(se, sig) + if err != nil { + return err + } - return nil + // Publish the attestations associated with this entity + return ociremote.WriteAttestations(digest.Repository, newSE) } +/* func findValidDigest(digests []string) string { // since we are only using the OCI repo provider for this source we are safe that this is only 1 value // see https://github.com/anchore/stereoscope/blob/25ebd49a842b5ac0a20c2e2b4b81335b64ad248c/pkg/image/oci/registry_provider.go#L57-L63 split := strings.Split(digests[0], "sha256:") return split[1] } +*/ func formatPredicateType(format sbom.Format) string { switch format.ID() { diff --git a/go.mod b/go.mod index e0802316a6a..42e9fc109c8 100644 --- a/go.mod +++ b/go.mod @@ -292,7 +292,7 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/golang/snappy v0.0.4 // indirect - github.com/google/go-containerregistry v0.8.1-0.20220209165246-a44adc326839 // indirect + github.com/google/go-containerregistry v0.8.1-0.20220209165246-a44adc326839 github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 // indirect diff --git a/go.sum b/go.sum index 73dc363e82b..0fce1755655 100644 --- a/go.sum +++ b/go.sum @@ -2415,8 +2415,6 @@ golang.org/x/crypto v0.0.0-20211202192323-5770296d904e/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220214200702-86341886e292 h1:f+lwQ+GtmgoY+A2YaQxlSOnDjXcQ7ZRLWOHbC6HtRqE= -golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f h1:OeJjE6G4dgCY4PIXvIRQbE8+RX+uXZyGhUy/ksMGJoc= golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= From 6fa29e723b1e9dca59f60e46e48d91f9460d693e Mon Sep 17 00:00:00 2001 From: Christopher Phillips Date: Fri, 6 May 2022 10:36:28 -0400 Subject: [PATCH 23/42] update options to include annotations Signed-off-by: Christopher Phillips --- cmd/syft/cli/attest/attest.go | 40 +++++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/cmd/syft/cli/attest/attest.go b/cmd/syft/cli/attest/attest.go index de20132de66..c3d69bb0929 100644 --- a/cmd/syft/cli/attest/attest.go +++ b/cmd/syft/cli/attest/attest.go @@ -36,6 +36,7 @@ import ( 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" @@ -208,13 +209,6 @@ func generateAttestation(ctx context.Context, app *config.Application, predicate return errors.Wrap(err, "unable to sign SBOM") } - _, err = uploadToTlog(ctx, sv, app.Attest.RekorURL, func(r *client.Rekor, b []byte) (*models.LogEntryAnon, error) { - return cosign.TLogUploadInTotoAttestation(ctx, r, signedPayload, b) - }) - if err != nil { - return err - } - if app.Attest.NoUpload { bus.Publish(partybus.Event{ Type: event.Exit, @@ -225,11 +219,23 @@ func generateAttestation(ctx context.Context, app *config.Application, predicate }) } - return uploadAttestation(signedPayload, digest) + return uploadAttestation(app, signedPayload, digest, sv) } -func uploadAttestation(signedPayload []byte, digest name.Digest) error { - sig, err := static.NewAttestation(signedPayload) +func uploadAttestation(app *config.Application, signedPayload []byte, digest name.Digest, sv *sign.SignerVerifier) error { + opts := []static.Option{static.WithLayerMediaType(types.DssePayloadType)} + if sv.Cert != nil { + opts = append(opts, static.WithCertChain(sv.Cert, sv.Chain)) + } + + 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 + } + opts = append(opts, static.WithBundle(bundle)) + sig, err := static.NewAttestation(signedPayload, opts...) if err != nil { return err } @@ -246,7 +252,19 @@ func uploadAttestation(signedPayload []byte, digest name.Digest) error { } // Publish the attestations associated with this entity - return ociremote.WriteAttestations(digest.Repository, newSE) + err = ociremote.WriteAttestations(digest.Repository, newSE) + if err != nil { + return err + } + + bus.Publish(partybus.Event{ + Type: event.Exit, + Value: func() error { + _, err := os.Stdout.Write(signedPayload) + return err + }, + }) + return nil } /* From 211e4607b8618c163626aaf3ea00e19475588391 Mon Sep 17 00:00:00 2001 From: Christopher Phillips Date: Fri, 6 May 2022 10:58:21 -0400 Subject: [PATCH 24/42] remove old cli test Signed-off-by: Christopher Phillips --- test/cli/cosign_test.go | 249 ---------------------------------------- 1 file changed, 249 deletions(-) delete mode 100644 test/cli/cosign_test.go diff --git a/test/cli/cosign_test.go b/test/cli/cosign_test.go deleted file mode 100644 index ffd19856731..00000000000 --- a/test/cli/cosign_test.go +++ /dev/null @@ -1,249 +0,0 @@ -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 hard pki", - 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) - }, - }, - { - name: "cosign verify syft attest keyless", - 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{ - 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", "attestation") - 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", "attestation") - 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, " ")) - } -} From b440344e47c2e24925487d7420852b7f02243c4e Mon Sep 17 00:00:00 2001 From: Christopher Phillips Date: Fri, 6 May 2022 11:09:40 -0400 Subject: [PATCH 25/42] remove unused var Signed-off-by: Christopher Phillips --- cmd/syft/cli/attest/attest.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cmd/syft/cli/attest/attest.go b/cmd/syft/cli/attest/attest.go index c3d69bb0929..320ef55a8e1 100644 --- a/cmd/syft/cli/attest/attest.go +++ b/cmd/syft/cli/attest/attest.go @@ -98,7 +98,7 @@ func Run(ctx context.Context, app *config.Application, ko *sign.KeyOpts, args [] syft.SetBus(eventBus) return eventloop.EventLoop( - execWorker(ctx, app, *si, format, predicateType, sv), + execWorker(app, *si, format, predicateType, sv), eventloop.SetupSignals(), eventBus.Subscribe(), stereoscope.Cleanup, @@ -133,7 +133,7 @@ func parseImageSource(userInput string, app *config.Application) (s *source.Inpu return si, nil } -func execWorker(ctx context.Context, app *config.Application, sourceInput source.Input, format sbom.Format, predicateType string, sv *sign.SignerVerifier) <-chan error { +func execWorker(app *config.Application, sourceInput source.Input, format sbom.Format, predicateType string, sv *sign.SignerVerifier) <-chan error { errs := make(chan error) go func() { defer close(errs) @@ -159,7 +159,7 @@ func execWorker(ctx context.Context, app *config.Application, sourceInput source return } - err = generateAttestation(ctx, app, sbomBytes, src, sv, predicateType) + err = generateAttestation(app, sbomBytes, src, sv, predicateType) if err != nil { errs <- err return @@ -168,7 +168,7 @@ func execWorker(ctx context.Context, app *config.Application, sourceInput source return errs } -func generateAttestation(ctx context.Context, app *config.Application, predicate []byte, src *source.Source, sv *sign.SignerVerifier, predicateType string) error { +func generateAttestation(app *config.Application, predicate []byte, src *source.Source, sv *sign.SignerVerifier, predicateType string) error { switch len(src.Image.Metadata.RepoDigests) { case 0: return fmt.Errorf("cannot generate attestation since no repo digests were found; make sure you're passing an OCI registry source for the attest command") From 52a9affef76da7392fe990c09f1e576a85e7f9dd Mon Sep 17 00:00:00 2001 From: Christopher Phillips Date: Fri, 6 May 2022 11:29:48 -0400 Subject: [PATCH 26/42] pass by value for ko Signed-off-by: Christopher Phillips --- cmd/syft/cli/attest.go | 2 +- cmd/syft/cli/attest/attest.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/syft/cli/attest.go b/cmd/syft/cli/attest.go index 1eaf9146728..1f12916d88d 100644 --- a/cmd/syft/cli/attest.go +++ b/cmd/syft/cli/attest.go @@ -55,7 +55,7 @@ func Attest(v *viper.Viper, app *config.Application, ro *options.RootOptions) *c } // build cosign key options for attestation - ko := &sign.KeyOpts{ + ko := sign.KeyOpts{ KeyRef: app.Attest.KeyRef, FulcioURL: app.Attest.FulcioURL, IDToken: app.Attest.FulcioIdentityToken, diff --git a/cmd/syft/cli/attest/attest.go b/cmd/syft/cli/attest/attest.go index 320ef55a8e1..216b5541eb0 100644 --- a/cmd/syft/cli/attest/attest.go +++ b/cmd/syft/cli/attest/attest.go @@ -55,7 +55,7 @@ var ( intotoJSONDsseType = `application/vnd.in-toto+json` ) -func Run(ctx context.Context, app *config.Application, ko *sign.KeyOpts, args []string) error { +func Run(ctx context.Context, app *config.Application, ko sign.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") @@ -87,7 +87,7 @@ func Run(ctx context.Context, app *config.Application, ko *sign.KeyOpts, args [] ko.PassFunc = passFunc } - sv, err := sign.SignerFromKeyOpts(ctx, "", "", *ko) + sv, err := sign.SignerFromKeyOpts(ctx, "", "", ko) if err != nil { return err } From f595e3a2ce95b6f71e1b3566a575801106551f03 Mon Sep 17 00:00:00 2001 From: Christopher Phillips Date: Fri, 6 May 2022 12:20:58 -0400 Subject: [PATCH 27/42] update nil check for tlog entry; privatize defaults Signed-off-by: Christopher Phillips --- cmd/syft/cli/attest/attest.go | 5 ++++- cmd/syft/cli/options/fulcio.go | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/cmd/syft/cli/attest/attest.go b/cmd/syft/cli/attest/attest.go index 216b5541eb0..bb336ea39f1 100644 --- a/cmd/syft/cli/attest/attest.go +++ b/cmd/syft/cli/attest/attest.go @@ -217,6 +217,7 @@ func generateAttestation(app *config.Application, predicate []byte, src *source. return err }, }) + return nil } return uploadAttestation(app, signedPayload, digest, sv) @@ -314,6 +315,8 @@ func uploadToTlog(ctx context.Context, sv *sign.SignerVerifier, rekorURL string, return nil, err } - log.Infof("tlog entry created with index: %v", *entry.LogIndex) + if entry.LogIndex != nil { + log.Debugf("transparency log entry created with index: %v", *entry.LogIndex) + } return cbundle.EntryToBundle(entry), nil } diff --git a/cmd/syft/cli/options/fulcio.go b/cmd/syft/cli/options/fulcio.go index 002d8a68845..ed94ee3964e 100644 --- a/cmd/syft/cli/options/fulcio.go +++ b/cmd/syft/cli/options/fulcio.go @@ -6,7 +6,7 @@ import ( "github.com/spf13/viper" ) -const DefaultFulcioURL = "https://fulcio.sigstore.dev" +const defaultFulcioURL = "https://fulcio.sigstore.dev" // FulcioOptions is the wrapper for Fulcio related options. type FulcioOptions struct { @@ -20,7 +20,7 @@ var _ Interface = (*FulcioOptions)(nil) // AddFlags implements Interface func (o *FulcioOptions) AddFlags(cmd *cobra.Command, v *viper.Viper) error { // TODO: change this back to api.SigstorePublicServerURL after the v1 migration is complete. - cmd.Flags().StringVar(&o.URL, "fulcio-url", DefaultFulcioURL, + cmd.Flags().StringVar(&o.URL, "fulcio-url", defaultFulcioURL, "address of sigstore PKI server") cmd.Flags().StringVar(&o.IdentityToken, "identity-token", "", From 48234e1f0707481a4c64abb8007b829859b13159 Mon Sep 17 00:00:00 2001 From: Christopher Phillips Date: Fri, 6 May 2022 12:28:36 -0400 Subject: [PATCH 28/42] remove dead code Signed-off-by: Christopher Phillips --- cmd/syft/cli/attest/attest.go | 9 --------- 1 file changed, 9 deletions(-) diff --git a/cmd/syft/cli/attest/attest.go b/cmd/syft/cli/attest/attest.go index bb336ea39f1..fd45b0f04ea 100644 --- a/cmd/syft/cli/attest/attest.go +++ b/cmd/syft/cli/attest/attest.go @@ -268,15 +268,6 @@ func uploadAttestation(app *config.Application, signedPayload []byte, digest nam return nil } -/* -func findValidDigest(digests []string) string { - // since we are only using the OCI repo provider for this source we are safe that this is only 1 value - // see https://github.com/anchore/stereoscope/blob/25ebd49a842b5ac0a20c2e2b4b81335b64ad248c/pkg/image/oci/registry_provider.go#L57-L63 - split := strings.Split(digests[0], "sha256:") - return split[1] -} -*/ - func formatPredicateType(format sbom.Format) string { switch format.ID() { case spdx22json.ID: From e58f5f588d786a915d948c8a4e489315885a8059 Mon Sep 17 00:00:00 2001 From: Christopher Phillips Date: Fri, 6 May 2022 12:40:46 -0400 Subject: [PATCH 29/42] remove default docs and prescribed default since optional flag Signed-off-by: Christopher Phillips --- cmd/syft/cli/options/oidc.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/syft/cli/options/oidc.go b/cmd/syft/cli/options/oidc.go index a275df8ddcf..2b8188d27a1 100644 --- a/cmd/syft/cli/options/oidc.go +++ b/cmd/syft/cli/options/oidc.go @@ -26,7 +26,7 @@ func (o *OIDCOptions) AddFlags(cmd *cobra.Command, v *viper.Viper) error { "OIDC client ID for application") cmd.Flags().StringVar(&o.RedirectURL, "oidc-redirect-url", "", - "OIDC redirect URL (Optional). The default oidc-redirect-url is 'http://localhost:0/auth/callback'.") + "OIDC redirect URL (Optional)") return bindOIDCConfigOptions(cmd.Flags(), v) } From a1732befe67d9c6e3a3827ae92773128e3c13450 Mon Sep 17 00:00:00 2001 From: Christopher Phillips Date: Fri, 6 May 2022 12:53:49 -0400 Subject: [PATCH 30/42] remove replace flag add initial comments Signed-off-by: Christopher Phillips --- cmd/syft/cli/attest/attest.go | 5 +++++ cmd/syft/cli/options/attest.go | 4 ---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/cmd/syft/cli/attest/attest.go b/cmd/syft/cli/attest/attest.go index fd45b0f04ea..d378cdf2b74 100644 --- a/cmd/syft/cli/attest/attest.go +++ b/cmd/syft/cli/attest/attest.go @@ -224,17 +224,22 @@ func generateAttestation(app *config.Application, predicate []byte, src *source. } 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)) } + // uploads payload to Rekor transparency log and returns bundle for attesation annotations + // the entry plus bundle are used during the verify attestation comamand 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 } + + // add bundle OCI attestation that is uploaded to opts = append(opts, static.WithBundle(bundle)) sig, err := static.NewAttestation(signedPayload, opts...) if err != nil { diff --git a/cmd/syft/cli/options/attest.go b/cmd/syft/cli/options/attest.go index 6c00bf8334f..083dc60071f 100644 --- a/cmd/syft/cli/options/attest.go +++ b/cmd/syft/cli/options/attest.go @@ -15,7 +15,6 @@ type AttestOptions struct { NoUpload bool Force bool Recursive bool - Replace bool Rekor RekorOptions Fulcio FulcioOptions @@ -50,9 +49,6 @@ func (o *AttestOptions) AddFlags(cmd *cobra.Command, v *viper.Viper) error { cmd.Flags().BoolVarP(&o.Recursive, "recursive", "", false, "if a multi-arch image is specified, additionally sign each discrete image") - cmd.Flags().BoolVarP(&o.Replace, "replace", "", false, - "") - return bindAttestationConfigOptions(cmd.Flags(), v) } From 47c8ff08b2a939a216809cd5c9a5e61fad775a64 Mon Sep 17 00:00:00 2001 From: Christopher Phillips Date: Fri, 6 May 2022 12:55:10 -0400 Subject: [PATCH 31/42] update flag description Signed-off-by: Christopher Phillips --- cmd/syft/cli/options/fulcio.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/syft/cli/options/fulcio.go b/cmd/syft/cli/options/fulcio.go index ed94ee3964e..ca2af6952ac 100644 --- a/cmd/syft/cli/options/fulcio.go +++ b/cmd/syft/cli/options/fulcio.go @@ -27,7 +27,7 @@ func (o *FulcioOptions) AddFlags(cmd *cobra.Command, v *viper.Viper) error { "identity token to use for certificate from fulcio") cmd.Flags().BoolVar(&o.InsecureSkipFulcioVerify, "insecure-skip-verify", false, - "skip verifying fulcio published to the SCT (this should only be used for testing).") + "skip verifying fulcio certificat and the SCT (Signed Certificate Timestamp) (this should only be used for testing).") return bindFulcioConfigOptions(cmd.Flags(), v) } From 5cc17042b0f5562037c19a7bbe43473caecfeaac Mon Sep 17 00:00:00 2001 From: Christopher Phillips Date: Fri, 6 May 2022 12:58:47 -0400 Subject: [PATCH 32/42] s/_/-/g <- for all flag options Signed-off-by: Christopher Phillips --- cmd/syft/cli/options/attest.go | 2 +- cmd/syft/cli/options/fulcio.go | 6 +++--- cmd/syft/cli/options/oidc.go | 6 +++--- cmd/syft/cli/options/rekor.go | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/cmd/syft/cli/options/attest.go b/cmd/syft/cli/options/attest.go index 083dc60071f..2ff5b280e40 100644 --- a/cmd/syft/cli/options/attest.go +++ b/cmd/syft/cli/options/attest.go @@ -61,7 +61,7 @@ func bindAttestationConfigOptions(flags *pflag.FlagSet, v *viper.Viper) error { return err } - if err := v.BindPFlag("attest.no_upload", flags.Lookup("no-upload")); err != nil { + if err := v.BindPFlag("attest.no-upload", flags.Lookup("no-upload")); err != nil { return err } diff --git a/cmd/syft/cli/options/fulcio.go b/cmd/syft/cli/options/fulcio.go index ca2af6952ac..28a91891045 100644 --- a/cmd/syft/cli/options/fulcio.go +++ b/cmd/syft/cli/options/fulcio.go @@ -32,15 +32,15 @@ func (o *FulcioOptions) AddFlags(cmd *cobra.Command, v *viper.Viper) error { } func bindFulcioConfigOptions(flags *pflag.FlagSet, v *viper.Viper) error { - if err := v.BindPFlag("attest.fulcio_url", flags.Lookup("fulcio-url")); err != nil { + if err := v.BindPFlag("attest.fulcio-url", flags.Lookup("fulcio-url")); err != nil { return err } - if err := v.BindPFlag("attest.fulcio_identity_token", flags.Lookup("identity-token")); err != nil { + if err := v.BindPFlag("attest.fulcio-identity-token", flags.Lookup("identity-token")); err != nil { return err } - if err := v.BindPFlag("attest.insecure_skip_verify", flags.Lookup("insecure-skip-verify")); err != nil { + if err := v.BindPFlag("attest.insecure-skip-verify", flags.Lookup("insecure-skip-verify")); err != nil { return err } diff --git a/cmd/syft/cli/options/oidc.go b/cmd/syft/cli/options/oidc.go index 2b8188d27a1..ce1ea4817d9 100644 --- a/cmd/syft/cli/options/oidc.go +++ b/cmd/syft/cli/options/oidc.go @@ -32,15 +32,15 @@ func (o *OIDCOptions) AddFlags(cmd *cobra.Command, v *viper.Viper) error { } func bindOIDCConfigOptions(flags *pflag.FlagSet, v *viper.Viper) error { - if err := v.BindPFlag("attest.oidc_issuer", flags.Lookup("oidc-issuer")); err != nil { + if err := v.BindPFlag("attest.oidc-issuer", flags.Lookup("oidc-issuer")); err != nil { return err } - if err := v.BindPFlag("attest.oidc_client_id", flags.Lookup("oidc-client-id")); err != nil { + if err := v.BindPFlag("attest.oidc-client-id", flags.Lookup("oidc-client-id")); err != nil { return err } - if err := v.BindPFlag("attest.oidc_redirect_url", flags.Lookup("oidc-redirect-url")); err != nil { + if err := v.BindPFlag("attest.oidc-redirect-url", flags.Lookup("oidc-redirect-url")); err != nil { return err } diff --git a/cmd/syft/cli/options/rekor.go b/cmd/syft/cli/options/rekor.go index b2ed6eb6e42..668c06e4b18 100644 --- a/cmd/syft/cli/options/rekor.go +++ b/cmd/syft/cli/options/rekor.go @@ -24,7 +24,7 @@ func (o *RekorOptions) AddFlags(cmd *cobra.Command, v *viper.Viper) error { func bindRekorConfigOptions(flags *pflag.FlagSet, v *viper.Viper) error { // TODO: config re-design - if err := v.BindPFlag("attest.rekor_url", flags.Lookup("rekor-url")); err != nil { + if err := v.BindPFlag("attest.rekor-url", flags.Lookup("rekor-url")); err != nil { return err } From c624f6cea3b30db3b52ee0d2a7f2c1d6c3991c84 Mon Sep 17 00:00:00 2001 From: Christopher Phillips Date: Fri, 6 May 2022 13:03:51 -0400 Subject: [PATCH 33/42] refacor cli test Signed-off-by: Christopher Phillips --- test/cli/cosign_test.go | 177 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 test/cli/cosign_test.go diff --git a/test/cli/cosign_test.go b/test/cli/cosign_test.go new file mode 100644 index 00000000000..283d36b6e89 --- /dev/null +++ b/test/cli/cosign_test.go @@ -0,0 +1,177 @@ +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", + "--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, " ")) + } +} From 667b977b53bd35fa87438374e8f95e20a9a153d0 Mon Sep 17 00:00:00 2001 From: Christopher Phillips Date: Fri, 6 May 2022 13:12:48 -0400 Subject: [PATCH 34/42] remove replace from binding Signed-off-by: Christopher Phillips --- cmd/syft/cli/options/attest.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/cmd/syft/cli/options/attest.go b/cmd/syft/cli/options/attest.go index 2ff5b280e40..17f86526413 100644 --- a/cmd/syft/cli/options/attest.go +++ b/cmd/syft/cli/options/attest.go @@ -73,9 +73,5 @@ func bindAttestationConfigOptions(flags *pflag.FlagSet, v *viper.Viper) error { return err } - if err := v.BindPFlag("attest.replace", flags.Lookup("replace")); err != nil { - return err - } - return nil } From 5585a8284fbd725b70c38759c2cdd4a03c458116 Mon Sep 17 00:00:00 2001 From: Christopher Phillips Date: Fri, 6 May 2022 16:04:44 -0400 Subject: [PATCH 35/42] added ETUI entries for keyless path Signed-off-by: Christopher Phillips --- cmd/syft/cli/attest/attest.go | 9 ++++- syft/event/event.go | 6 ++++ ui/event_handlers.go | 64 +++++++++++++++++++++++++++++++++++ ui/handler.go | 18 +++++++++- 4 files changed, 95 insertions(+), 2 deletions(-) diff --git a/cmd/syft/cli/attest/attest.go b/cmd/syft/cli/attest/attest.go index d378cdf2b74..d28ed1d5ea8 100644 --- a/cmd/syft/cli/attest/attest.go +++ b/cmd/syft/cli/attest/attest.go @@ -230,6 +230,10 @@ func uploadAttestation(app *config.Application, signedPayload []byte, digest nam opts = append(opts, static.WithCertChain(sv.Cert, sv.Chain)) } + bus.Publish(partybus.Event{ + Type: event.UploadTransparencyLog, + }) + // uploads payload to Rekor transparency log and returns bundle for attesation annotations // the entry plus bundle are used during the verify attestation comamand bundle, err := uploadToTlog(context.TODO(), sv, app.Attest.RekorURL, func(r *client.Rekor, b []byte) (*models.LogEntryAnon, error) { @@ -251,7 +255,10 @@ func uploadAttestation(app *config.Application, signedPayload []byte, digest nam return err } - // Attach the attestation to the entity. + bus.Publish(partybus.Event{ + Type: event.UploadOCIAttestation, + }) + newSE, err := mutate.AttachAttestationToEntity(se, sig) if err != nil { return err diff --git a/syft/event/event.go b/syft/event/event.go index c5ff81d314e..451bea1954c 100644 --- a/syft/event/event.go +++ b/syft/event/event.go @@ -31,4 +31,10 @@ const ( // ImportStarted is a partybus event that occurs when an SBOM upload process has begun ImportStarted partybus.EventType = "syft-import-started-event" + + // UploadTransparencyLog is a party bus event that occurs when syft uploads a keyless record to the transparency log + UploadTransparencyLog partybus.EventType = "syft-upload-transparency-log" + + // UploadOCIAttestation is a party bus event that occurs when syft uploads an attestion to and OCI registry + UploadOCIAttestation partybus.EventType = "syft-upload-oic-attestation" ) diff --git a/ui/event_handlers.go b/ui/event_handlers.go index ba543a366b3..d3aebe6a924 100644 --- a/ui/event_handlers.go +++ b/ui/event_handlers.go @@ -226,6 +226,70 @@ func FetchImageHandler(ctx context.Context, fr *frame.Frame, event partybus.Even return err } +func UploadTransparencyLogHandler(ctx context.Context, fr *frame.Frame, event partybus.Event, wg *sync.WaitGroup) error { + line, err := fr.Append() + if err != nil { + return err + } + + wg.Add(1) + + _, spinner := startProcess() + title := tileFormat.Sprint("Uploading to Rekor transparency log") + + go func() { + defer wg.Done() + loop: + for { + spin := color.Magenta.Sprint(spinner.Next()) + _, _ = io.WriteString(line, fmt.Sprintf(statusTitleTemplate, spin, title)) + select { + case <-ctx.Done(): + break loop + case <-time.After(30 * time.Millisecond): + break loop + } + } + spin := color.Green.Sprint(completedStatus) + title = tileFormat.Sprint("Uploaded to Rekor transparency log") + _, _ = io.WriteString(line, fmt.Sprintf(statusTitleTemplate, spin, title)) + }() + return nil +} + +func UploadAttestationHandler(ctx context.Context, fr *frame.Frame, event partybus.Event, wg *sync.WaitGroup) error { + line, err := fr.Append() + if err != nil { + return err + } + + wg.Add(1) + + _, spinner := startProcess() + title := tileFormat.Sprint("Uploading attestation to OCI registry") + + go func() { + defer wg.Done() + loop: + for { + spin := color.Magenta.Sprint(spinner.Next()) + _, _ = io.WriteString(line, fmt.Sprintf(statusTitleTemplate, spin, title)) + select { + case <-ctx.Done(): + break loop + case <-time.After(30 * time.Millisecond): + break loop + } + } + spin := color.Green.Sprint(completedStatus) + title = tileFormat.Sprint("Uploaded attestation to OCI registry") + _, _ = io.WriteString(line, fmt.Sprintf(statusTitleTemplate, spin, title)) + + }() + + return nil +} + // 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 { _, prog, err := stereoEventParsers.ParseReadImage(event) diff --git a/ui/handler.go b/ui/handler.go index bd11733eb37..92e11bacdb2 100644 --- a/ui/handler.go +++ b/ui/handler.go @@ -27,7 +27,17 @@ func NewHandler() *Handler { // RespondsTo indicates if the handler is capable of handling the given event. func (r *Handler) RespondsTo(event partybus.Event) bool { switch event.Type { - case stereoscopeEvent.PullDockerImage, stereoscopeEvent.ReadImage, stereoscopeEvent.FetchImage, syftEvent.PackageCatalogerStarted, syftEvent.SecretsCatalogerStarted, syftEvent.FileDigestsCatalogerStarted, syftEvent.FileMetadataCatalogerStarted, syftEvent.FileIndexingStarted, syftEvent.ImportStarted: + case stereoscopeEvent.PullDockerImage, + stereoscopeEvent.ReadImage, + stereoscopeEvent.FetchImage, + syftEvent.UploadTransparencyLog, + syftEvent.UploadOCIAttestation, + syftEvent.PackageCatalogerStarted, + syftEvent.SecretsCatalogerStarted, + syftEvent.FileDigestsCatalogerStarted, + syftEvent.FileMetadataCatalogerStarted, + syftEvent.FileIndexingStarted, + syftEvent.ImportStarted: return true default: return false @@ -46,6 +56,12 @@ func (r *Handler) Handle(ctx context.Context, fr *frame.Frame, event partybus.Ev case stereoscopeEvent.FetchImage: return FetchImageHandler(ctx, fr, event, wg) + case syftEvent.UploadTransparencyLog: + return UploadTransparencyLogHandler(ctx, fr, event, wg) + + case syftEvent.UploadOCIAttestation: + return UploadAttestationHandler(ctx, fr, event, wg) + case syftEvent.PackageCatalogerStarted: return PackageCatalogerStartedHandler(ctx, fr, event, wg) From d1a5d68b22359cd4528dfe42e4ea86235428717a Mon Sep 17 00:00:00 2001 From: Christopher Phillips Date: Fri, 6 May 2022 16:16:08 -0400 Subject: [PATCH 36/42] don't upload keyed workflow Signed-off-by: Christopher Phillips --- cmd/syft/cli/attest/attest.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cmd/syft/cli/attest/attest.go b/cmd/syft/cli/attest/attest.go index d28ed1d5ea8..9504c701eda 100644 --- a/cmd/syft/cli/attest/attest.go +++ b/cmd/syft/cli/attest/attest.go @@ -209,7 +209,9 @@ func generateAttestation(app *config.Application, predicate []byte, src *source. return errors.Wrap(err, "unable to sign SBOM") } - if app.Attest.NoUpload { + // We want to give the option to not upload the generated attestation + // if passed or if the user is using local PKI + if app.Attest.NoUpload || app.Attest.KeyRef != "" { bus.Publish(partybus.Event{ Type: event.Exit, Value: func() error { From 659685d56d3dec4d4c81bb6211f199838468c010 Mon Sep 17 00:00:00 2001 From: Christopher Phillips Date: Fri, 6 May 2022 16:29:21 -0400 Subject: [PATCH 37/42] update linter Signed-off-by: Christopher Phillips --- ui/event_handlers.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ui/event_handlers.go b/ui/event_handlers.go index d3aebe6a924..1b2a8ba2119 100644 --- a/ui/event_handlers.go +++ b/ui/event_handlers.go @@ -226,6 +226,7 @@ func FetchImageHandler(ctx context.Context, fr *frame.Frame, event partybus.Even return err } +//nolint:dupl func UploadTransparencyLogHandler(ctx context.Context, fr *frame.Frame, event partybus.Event, wg *sync.WaitGroup) error { line, err := fr.Append() if err != nil { @@ -246,7 +247,7 @@ func UploadTransparencyLogHandler(ctx context.Context, fr *frame.Frame, event pa select { case <-ctx.Done(): break loop - case <-time.After(30 * time.Millisecond): + case <-time.After(interval): break loop } } @@ -257,6 +258,7 @@ func UploadTransparencyLogHandler(ctx context.Context, fr *frame.Frame, event pa return nil } +//nolint:dupl func UploadAttestationHandler(ctx context.Context, fr *frame.Frame, event partybus.Event, wg *sync.WaitGroup) error { line, err := fr.Append() if err != nil { @@ -277,14 +279,13 @@ func UploadAttestationHandler(ctx context.Context, fr *frame.Frame, event partyb select { case <-ctx.Done(): break loop - case <-time.After(30 * time.Millisecond): + case <-time.After(interval): break loop } } spin := color.Green.Sprint(completedStatus) title = tileFormat.Sprint("Uploaded attestation to OCI registry") _, _ = io.WriteString(line, fmt.Sprintf(statusTitleTemplate, spin, title)) - }() return nil From f68493aa5f67b92f0139a82717b0a035cd6b70b7 Mon Sep 17 00:00:00 2001 From: Christopher Phillips Date: Fri, 6 May 2022 16:36:25 -0400 Subject: [PATCH 38/42] add specific flag now no longer default Signed-off-by: Christopher Phillips --- test/cli/attest_cmd_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/cli/attest_cmd_test.go b/test/cli/attest_cmd_test.go index fa385a17679..43a1d040450 100644 --- a/test/cli/attest_cmd_test.go +++ b/test/cli/attest_cmd_test.go @@ -26,7 +26,7 @@ func TestAttestCmd(t *testing.T) { }, { name: "can encode syft.json as the predicate given a password", - args: []string{"attest", "-o", "json", img}, + args: []string{"attest", "-o", "json", "--key", "cosign.key", img}, assertions: []traitAssertion{ assertSuccessfulReturnCode, }, @@ -34,7 +34,7 @@ func TestAttestCmd(t *testing.T) { }, { name: "can encode syft.json as the predicate given a blank password", - args: []string{"attest", "-o", "json", img}, + args: []string{"attest", "-o", "json", "--key", "cosign.key", img}, assertions: []traitAssertion{ assertSuccessfulReturnCode, }, From 02196dbfb90f2deb159a5e74d6c0634fb28fb68a Mon Sep 17 00:00:00 2001 From: Christopher Phillips Date: Fri, 6 May 2022 17:06:10 -0400 Subject: [PATCH 39/42] update comment with inputs and outputs Signed-off-by: Christopher Phillips --- cmd/syft/cli/attest/attest.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/cmd/syft/cli/attest/attest.go b/cmd/syft/cli/attest/attest.go index 9504c701eda..ff52df4fe30 100644 --- a/cmd/syft/cli/attest/attest.go +++ b/cmd/syft/cli/attest/attest.go @@ -225,6 +225,11 @@ func generateAttestation(app *config.Application, predicate []byte, src *source. return uploadAttestation(app, signedPayload, digest, sv) } +// uploads signed SBOM payload to Rekor transparency log along with key information; +// returns a 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 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)} @@ -236,8 +241,11 @@ func uploadAttestation(app *config.Application, signedPayload []byte, digest nam Type: event.UploadTransparencyLog, }) - // uploads payload to Rekor transparency log and returns bundle for attesation annotations - // the entry plus bundle are used during the verify attestation comamand + // 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) }) From e5bb6dfbb48f831fae929cbfbb22d4d51e6ea79b Mon Sep 17 00:00:00 2001 From: Christopher Phillips Date: Fri, 6 May 2022 17:14:09 -0400 Subject: [PATCH 40/42] remove output on upload Signed-off-by: Christopher Phillips --- cmd/syft/cli/attest/attest.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cmd/syft/cli/attest/attest.go b/cmd/syft/cli/attest/attest.go index ff52df4fe30..b2a637c4ba0 100644 --- a/cmd/syft/cli/attest/attest.go +++ b/cmd/syft/cli/attest/attest.go @@ -283,8 +283,7 @@ func uploadAttestation(app *config.Application, signedPayload []byte, digest nam bus.Publish(partybus.Event{ Type: event.Exit, Value: func() error { - _, err := os.Stdout.Write(signedPayload) - return err + return nil }, }) return nil From a24372eff51f3d9f31e2447bb7bcab5a3d9904c1 Mon Sep 17 00:00:00 2001 From: Alex Goodman Date: Fri, 6 May 2022 17:17:28 -0400 Subject: [PATCH 41/42] update attestation upload etui Signed-off-by: Alex Goodman --- cmd/syft/cli/attest/attest.go | 38 +++++++++++++++---- syft/event/event.go | 7 +--- syft/event/parsers/parsers.go | 13 +++++++ ui/event_handlers.go | 70 ++++++++++++----------------------- ui/handler.go | 8 +--- 5 files changed, 71 insertions(+), 65 deletions(-) diff --git a/cmd/syft/cli/attest/attest.go b/cmd/syft/cli/attest/attest.go index b2a637c4ba0..00081fa2f51 100644 --- a/cmd/syft/cli/attest/attest.go +++ b/cmd/syft/cli/attest/attest.go @@ -5,6 +5,7 @@ import ( "context" "encoding/json" "fmt" + "github.com/wagoodman/go-progress" "os" "github.com/anchore/stereoscope" @@ -225,8 +226,26 @@ func generateAttestation(app *config.Application, predicate []byte, src *source. 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 attesation annotations +// 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 @@ -237,9 +256,11 @@ func uploadAttestation(app *config.Application, signedPayload []byte, digest nam opts = append(opts, static.WithCertChain(sv.Cert, sv.Chain)) } - bus.Publish(partybus.Event{ - Type: event.UploadTransparencyLog, - }) + 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 @@ -253,6 +274,9 @@ func uploadAttestation(app *config.Application, signedPayload []byte, digest nam 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...) @@ -265,10 +289,6 @@ func uploadAttestation(app *config.Application, signedPayload []byte, digest nam return err } - bus.Publish(partybus.Event{ - Type: event.UploadOCIAttestation, - }) - newSE, err := mutate.AttachAttestationToEntity(se, sig) if err != nil { return err @@ -280,6 +300,8 @@ func uploadAttestation(app *config.Application, signedPayload []byte, digest nam return err } + prog.SetCompleted() + bus.Publish(partybus.Event{ Type: event.Exit, Value: func() error { diff --git a/syft/event/event.go b/syft/event/event.go index 451bea1954c..8adcdd29790 100644 --- a/syft/event/event.go +++ b/syft/event/event.go @@ -32,9 +32,6 @@ const ( // ImportStarted is a partybus event that occurs when an SBOM upload process has begun ImportStarted partybus.EventType = "syft-import-started-event" - // UploadTransparencyLog is a party bus event that occurs when syft uploads a keyless record to the transparency log - UploadTransparencyLog partybus.EventType = "syft-upload-transparency-log" - - // UploadOCIAttestation is a party bus event that occurs when syft uploads an attestion to and OCI registry - UploadOCIAttestation partybus.EventType = "syft-upload-oic-attestation" + // UploadAttestation is a partybus event that occurs when syft uploads an attestation to an OCI registry (+ any transparency log) + UploadAttestation partybus.EventType = "syft-upload-attestation" ) diff --git a/syft/event/parsers/parsers.go b/syft/event/parsers/parsers.go index 6abcd28caae..f384044cd52 100644 --- a/syft/event/parsers/parsers.go +++ b/syft/event/parsers/parsers.go @@ -151,3 +151,16 @@ func ParseImportStarted(e partybus.Event) (string, progress.StagedProgressable, return host, prog, nil } + +func ParseUploadAttestation(e partybus.Event) (progress.StagedProgressable, error) { + if err := checkEventType(e.Type, event.UploadAttestation); err != nil { + return nil, err + } + + prog, ok := e.Value.(progress.StagedProgressable) + if !ok { + return nil, newPayloadErr(e.Type, "Value", e.Value) + } + + return prog, nil +} diff --git a/ui/event_handlers.go b/ui/event_handlers.go index 1b2a8ba2119..03620ef7276 100644 --- a/ui/event_handlers.go +++ b/ui/event_handlers.go @@ -227,68 +227,46 @@ func FetchImageHandler(ctx context.Context, fr *frame.Frame, event partybus.Even } //nolint:dupl -func UploadTransparencyLogHandler(ctx context.Context, fr *frame.Frame, event partybus.Event, wg *sync.WaitGroup) error { - line, err := fr.Append() +func UploadAttestationHandler(ctx context.Context, fr *frame.Frame, event partybus.Event, wg *sync.WaitGroup) error { + prog, err := syftEventParsers.ParseUploadAttestation(event) if err != nil { - return err + return fmt.Errorf("bad %s event: %w", event.Type, err) } - wg.Add(1) - - _, spinner := startProcess() - title := tileFormat.Sprint("Uploading to Rekor transparency log") - - go func() { - defer wg.Done() - loop: - for { - spin := color.Magenta.Sprint(spinner.Next()) - _, _ = io.WriteString(line, fmt.Sprintf(statusTitleTemplate, spin, title)) - select { - case <-ctx.Done(): - break loop - case <-time.After(interval): - break loop - } - } - spin := color.Green.Sprint(completedStatus) - title = tileFormat.Sprint("Uploaded to Rekor transparency log") - _, _ = io.WriteString(line, fmt.Sprintf(statusTitleTemplate, spin, title)) - }() - return nil -} - -//nolint:dupl -func UploadAttestationHandler(ctx context.Context, fr *frame.Frame, event partybus.Event, wg *sync.WaitGroup) error { line, err := fr.Append() if err != nil { return err } - wg.Add(1) - _, spinner := startProcess() - title := tileFormat.Sprint("Uploading attestation to OCI registry") + 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() - loop: - for { - spin := color.Magenta.Sprint(spinner.Next()) - _, _ = io.WriteString(line, fmt.Sprintf(statusTitleTemplate, spin, title)) - select { - case <-ctx.Done(): - break loop - case <-time.After(interval): - break loop - } + + formatFn(progress.Progress{}) + for p := range stream { + formatFn(p) } + spin := color.Green.Sprint(completedStatus) - title = tileFormat.Sprint("Uploaded attestation to OCI registry") + title = tileFormat.Sprint("Uploaded attestation") _, _ = io.WriteString(line, fmt.Sprintf(statusTitleTemplate, spin, title)) }() - - return nil + return err } // ReadImageHandler periodically writes a the image read/parse/build-tree status in the form of a progress bar. diff --git a/ui/handler.go b/ui/handler.go index 92e11bacdb2..bf9eb81d364 100644 --- a/ui/handler.go +++ b/ui/handler.go @@ -30,8 +30,7 @@ func (r *Handler) RespondsTo(event partybus.Event) bool { case stereoscopeEvent.PullDockerImage, stereoscopeEvent.ReadImage, stereoscopeEvent.FetchImage, - syftEvent.UploadTransparencyLog, - syftEvent.UploadOCIAttestation, + syftEvent.UploadAttestation, syftEvent.PackageCatalogerStarted, syftEvent.SecretsCatalogerStarted, syftEvent.FileDigestsCatalogerStarted, @@ -56,10 +55,7 @@ func (r *Handler) Handle(ctx context.Context, fr *frame.Frame, event partybus.Ev case stereoscopeEvent.FetchImage: return FetchImageHandler(ctx, fr, event, wg) - case syftEvent.UploadTransparencyLog: - return UploadTransparencyLogHandler(ctx, fr, event, wg) - - case syftEvent.UploadOCIAttestation: + case syftEvent.UploadAttestation: return UploadAttestationHandler(ctx, fr, event, wg) case syftEvent.PackageCatalogerStarted: From 5d58aa3c6a330746010d61c8d6c8f563188d4b29 Mon Sep 17 00:00:00 2001 From: Christopher Phillips Date: Fri, 6 May 2022 17:39:12 -0400 Subject: [PATCH 42/42] commit new changes for static analysis Signed-off-by: Christopher Phillips --- cmd/syft/cli/attest/attest.go | 3 ++- ui/event_handlers.go | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/syft/cli/attest/attest.go b/cmd/syft/cli/attest/attest.go index 00081fa2f51..c0e202d1afd 100644 --- a/cmd/syft/cli/attest/attest.go +++ b/cmd/syft/cli/attest/attest.go @@ -5,9 +5,10 @@ import ( "context" "encoding/json" "fmt" - "github.com/wagoodman/go-progress" "os" + "github.com/wagoodman/go-progress" + "github.com/anchore/stereoscope" "github.com/anchore/stereoscope/pkg/image" "github.com/anchore/syft/cmd/syft/cli/eventloop" diff --git a/ui/event_handlers.go b/ui/event_handlers.go index 03620ef7276..0c42984412c 100644 --- a/ui/event_handlers.go +++ b/ui/event_handlers.go @@ -226,7 +226,6 @@ func FetchImageHandler(ctx context.Context, fr *frame.Frame, event partybus.Even return err } -//nolint:dupl func UploadAttestationHandler(ctx context.Context, fr *frame.Frame, event partybus.Event, wg *sync.WaitGroup) error { prog, err := syftEventParsers.ParseUploadAttestation(event) if err != nil { @@ -398,8 +397,8 @@ func SecretsCatalogerStartedHandler(ctx context.Context, fr *frame.Frame, event return err } +//nolint:dupl // FileMetadataCatalogerStartedHandler shows the intermittent secrets searching progress. -// nolint:dupl func FileMetadataCatalogerStartedHandler(ctx context.Context, fr *frame.Frame, event partybus.Event, wg *sync.WaitGroup) error { prog, err := syftEventParsers.ParseFileMetadataCatalogingStarted(event) if err != nil {