From 57aa030b317aad95c049de105f0e0a792517eae7 Mon Sep 17 00:00:00 2001 From: Luiz Carvalho Date: Wed, 12 Jun 2024 13:18:55 -0400 Subject: [PATCH 1/2] Add sigstore initialize subcommand It is now possible to initialize the TUF root directly with the ec-cli. No need to use another binary, e.g. cosign, to perform this operation. Ref: EC-584 Signed-off-by: Luiz Carvalho --- cmd/root.go | 2 + cmd/sigstore/initialize.go | 77 +++++++++++++++++ cmd/sigstore/initialize_test.go | 83 +++++++++++++++++++ cmd/sigstore/sigstore.go | 39 +++++++++ docs/modules/ROOT/pages/ec_sigstore.adoc | 19 +++++ .../ROOT/pages/ec_sigstore_initialize.adoc | 55 ++++++++++++ docs/modules/ROOT/partials/cli_nav.adoc | 2 + go.mod | 2 +- go.sum | 4 +- 9 files changed, 280 insertions(+), 3 deletions(-) create mode 100644 cmd/sigstore/initialize.go create mode 100644 cmd/sigstore/initialize_test.go create mode 100644 cmd/sigstore/sigstore.go create mode 100644 docs/modules/ROOT/pages/ec_sigstore.adoc create mode 100644 docs/modules/ROOT/pages/ec_sigstore_initialize.adoc diff --git a/cmd/root.go b/cmd/root.go index e6bdd3e00..bb4556bbc 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -25,6 +25,7 @@ import ( "github.com/enterprise-contract/ec-cli/cmd/inspect" "github.com/enterprise-contract/ec-cli/cmd/opa" "github.com/enterprise-contract/ec-cli/cmd/root" + "github.com/enterprise-contract/ec-cli/cmd/sigstore" "github.com/enterprise-contract/ec-cli/cmd/test" "github.com/enterprise-contract/ec-cli/cmd/track" "github.com/enterprise-contract/ec-cli/cmd/validate" @@ -53,6 +54,7 @@ func init() { RootCmd.AddCommand(validate.ValidateCmd) RootCmd.AddCommand(version.VersionCmd) RootCmd.AddCommand(opa.OPACmd) + RootCmd.AddCommand(sigstore.SigstoreCmd) if utils.Experimental() { RootCmd.AddCommand(test.TestCmd) } diff --git a/cmd/sigstore/initialize.go b/cmd/sigstore/initialize.go new file mode 100644 index 000000000..75f02aea5 --- /dev/null +++ b/cmd/sigstore/initialize.go @@ -0,0 +1,77 @@ +// Copyright The Enterprise Contract Contributors +// +// 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. +// +// SPDX-License-Identifier: Apache-2.0 + +package sigstore + +import ( + "context" + + hd "github.com/MakeNowJust/heredoc" + "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" + "github.com/spf13/cobra" +) + +type sigstoreInitializeFunc func(ctx context.Context, root, mirror string) error + +func sigstoreInitializeCmd(f sigstoreInitializeFunc) *cobra.Command { + + opts := &options.InitializeOptions{} + + cmd := &cobra.Command{ + Use: "initialize", + Short: "Initializes Sigstore root to retrieve trusted certificate and key targets for verification", + + Long: hd.Doc(` + Initializes Sigstore root to retrieve trusted certificate and key targets for verification. + + The following options are used by default: + - The current trusted Sigstore TUF root is embedded inside ec at the time of release. + - Sigstore remote TUF repository is pulled from the CDN mirror at tuf-repo-cdn.sigstore.dev. + + To provide an out-of-band trusted initial root.json, use the --root flag with a file or + URL reference. This will enable you to point ec to a separate TUF root. + + Any updated TUF repository will be written to $HOME/.sigstore/root/. + + Trusted keys and certificate used in ec verification (e.g. verifying Fulcio issued certificates + with Fulcio root CA) are pulled form the trusted metadata. + + This command is mostly a wrapper around "cosign initialize". + `), + + Example: hd.Doc(` + ec initialize -mirror -out + + Initialize root with distributed root keys, default mirror, and default out path. + ec initialize + + Initialize with an out-of-band root key file, using the default mirror. + ec initialize -root + + Initialize with an out-of-band root key file and custom repository mirror. + ec initialize -mirror -root + `), + + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, _ []string) error { + return f(cmd.Context(), opts.Root, opts.Mirror) + }, + } + + opts.AddFlags(cmd) + + return cmd +} diff --git a/cmd/sigstore/initialize_test.go b/cmd/sigstore/initialize_test.go new file mode 100644 index 000000000..033fe917d --- /dev/null +++ b/cmd/sigstore/initialize_test.go @@ -0,0 +1,83 @@ +// Copyright The Enterprise Contract Contributors +// +// 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. +// +// SPDX-License-Identifier: Apache-2.0 + +//go:build unit + +package sigstore + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/enterprise-contract/ec-cli/cmd/root" +) + +func TestInitializeCmd(t *testing.T) { + cases := []struct { + name string + args []string + expectedRoot string + expectedMirror string + }{ + { + name: "no args", + expectedMirror: "https://tuf-repo-cdn.sigstore.dev", + }, + { + name: "with root", + args: []string{"--root", "/some/path/root.json"}, + expectedRoot: "/some/path/root.json", + expectedMirror: "https://tuf-repo-cdn.sigstore.dev", + }, + { + name: "with mirror", + args: []string{"--mirror", "https://tuf.local"}, + expectedMirror: "https://tuf.local", + }, + { + name: "with root and mirror", + args: []string{"--root", "/some/path/root.json", "--mirror", "https://tuf.local"}, + expectedRoot: "/some/path/root.json", + expectedMirror: "https://tuf.local", + }, + } + + for _, tt := range cases { + t.Run(tt.name, func(t *testing.T) { + initF := func(ctx context.Context, root, mirror string) error { + require.Equal(t, tt.expectedRoot, root) + require.Equal(t, tt.expectedMirror, mirror) + return nil + } + + sigInitCmd := sigstoreInitializeCmd(initF) + + sigCmd := NewSigstoreCmd() + sigCmd.AddCommand(sigInitCmd) + + rootCmd := root.NewRootCmd() + rootCmd.AddCommand(sigCmd) + + rootCmd.SetContext(context.Background()) + rootCmd.SetArgs(append([]string{"sigstore", "initialize"}, tt.args...)) + + err := rootCmd.Execute() + require.NoError(t, err) + }) + } +} diff --git a/cmd/sigstore/sigstore.go b/cmd/sigstore/sigstore.go new file mode 100644 index 000000000..07b0fd6dd --- /dev/null +++ b/cmd/sigstore/sigstore.go @@ -0,0 +1,39 @@ +// Copyright The Enterprise Contract Contributors +// +// 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. +// +// SPDX-License-Identifier: Apache-2.0 + +package sigstore + +import ( + "github.com/sigstore/cosign/v2/cmd/cosign/cli/initialize" + "github.com/spf13/cobra" + + _ "github.com/enterprise-contract/ec-cli/internal/rego" +) + +var SigstoreCmd *cobra.Command + +func init() { + SigstoreCmd = NewSigstoreCmd() + SigstoreCmd.AddCommand(sigstoreInitializeCmd(initialize.DoInitialize)) +} + +func NewSigstoreCmd() *cobra.Command { + sigstoreCmd := &cobra.Command{ + Use: "sigstore", + Short: "Perform certain sigstore operations", + } + return sigstoreCmd +} diff --git a/docs/modules/ROOT/pages/ec_sigstore.adoc b/docs/modules/ROOT/pages/ec_sigstore.adoc new file mode 100644 index 000000000..c8c91f8ca --- /dev/null +++ b/docs/modules/ROOT/pages/ec_sigstore.adoc @@ -0,0 +1,19 @@ += ec sigstore + +Perform certain sigstore operations +== Options + +-h, --help:: help for sigstore (Default: false) + +== Options inherited from parent commands + +--debug:: same as verbose but also show function names and line numbers (Default: false) +--kubeconfig:: path to the Kubernetes config file to use +--quiet:: less verbose output (Default: false) +--timeout:: max overall execution duration (Default: 5m0s) +--trace:: enable trace logging (Default: false) +--verbose:: more verbose output (Default: false) + +== See also + + * xref:ec.adoc[ec - Enterprise Contract CLI] diff --git a/docs/modules/ROOT/pages/ec_sigstore_initialize.adoc b/docs/modules/ROOT/pages/ec_sigstore_initialize.adoc new file mode 100644 index 000000000..860686096 --- /dev/null +++ b/docs/modules/ROOT/pages/ec_sigstore_initialize.adoc @@ -0,0 +1,55 @@ += ec sigstore initialize + +Initializes Sigstore root to retrieve trusted certificate and key targets for verification== Synopsis + +Initializes Sigstore root to retrieve trusted certificate and key targets for verification. + +The following options are used by default: +- The current trusted Sigstore TUF root is embedded inside ec at the time of release. +- Sigstore remote TUF repository is pulled from the CDN mirror at tuf-repo-cdn.sigstore.dev. + +To provide an out-of-band trusted initial root.json, use the --root flag with a file or +URL reference. This will enable you to point ec to a separate TUF root. + +Any updated TUF repository will be written to $HOME/.sigstore/root/. + +Trusted keys and certificate used in ec verification (e.g. verifying Fulcio issued certificates +with Fulcio root CA) are pulled form the trusted metadata. + +This command is mostly a wrapper around "cosign initialize". + +[source,shell] +---- +ec sigstore initialize [flags] +---- + +== Examples +ec initialize -mirror -out + +Initialize root with distributed root keys, default mirror, and default out path. +ec initialize + +Initialize with an out-of-band root key file, using the default mirror. +ec initialize -root + +Initialize with an out-of-band root key file and custom repository mirror. +ec initialize -mirror -root + +== Options + +-h, --help:: help for initialize (Default: false) +--mirror:: GCS bucket to a SigStore TUF repository, or HTTP(S) base URL, or file:/// for local filestore remote (air-gap) (Default: https://tuf-repo-cdn.sigstore.dev) +--root:: path to trusted initial root. defaults to embedded root + +== Options inherited from parent commands + +--debug:: same as verbose but also show function names and line numbers (Default: false) +--kubeconfig:: path to the Kubernetes config file to use +--quiet:: less verbose output (Default: false) +--timeout:: max overall execution duration (Default: 5m0s) +--trace:: enable trace logging (Default: false) +--verbose:: more verbose output (Default: false) + +== See also + + * xref:ec_sigstore.adoc[ec sigstore - Perform certain sigstore operations] diff --git a/docs/modules/ROOT/partials/cli_nav.adoc b/docs/modules/ROOT/partials/cli_nav.adoc index 014cb4847..11b98849e 100644 --- a/docs/modules/ROOT/partials/cli_nav.adoc +++ b/docs/modules/ROOT/partials/cli_nav.adoc @@ -22,6 +22,8 @@ ** xref:ec_opa_sign.adoc[ec opa sign] ** xref:ec_opa_test.adoc[ec opa test] ** xref:ec_opa_version.adoc[ec opa version] +** xref:ec_sigstore.adoc[ec sigstore] +** xref:ec_sigstore_initialize.adoc[ec sigstore initialize] ** xref:ec_test.adoc[ec test] ** xref:ec_track.adoc[ec track] ** xref:ec_track_bundle.adoc[ec track bundle] diff --git a/go.mod b/go.mod index 39adc008e..801c39588 100644 --- a/go.mod +++ b/go.mod @@ -298,7 +298,7 @@ require ( go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.22.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/sdk v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect diff --git a/go.sum b/go.sum index 8e6b4aaea..144b59142 100644 --- a/go.sum +++ b/go.sum @@ -1326,8 +1326,8 @@ go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0 h1:9M3+rhx7kZCIQQhQRYaZCdNu1V73tm4TvXs2ntl98C4= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0/go.mod h1:noq80iT8rrHP1SfybmPiRGc9dc5M8RPmGvtwo7Oo7tc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0 h1:tIqheXEFWAZ7O8A7m+J0aPTmpJN3YQ7qetUAdkkkKpk= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0/go.mod h1:nUeKExfxAQVbiVFn32YXpXZZHZ61Cc3s3Rn1pDBGAb0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.22.0 h1:H2JFgRcGiyHg7H7bwcwaQJYrNFqCqrbTQ8K4p1OvDu8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.22.0/go.mod h1:WfCWp1bGoYK8MeULtI15MmQVczfR+bFkk0DF3h06QmQ= go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw= From 65d0feaeaed3d4a92183315e81d06bf1dc7ac30e Mon Sep 17 00:00:00 2001 From: Luiz Carvalho Date: Wed, 12 Jun 2024 13:19:58 -0400 Subject: [PATCH 2/2] Use ec instead of cosign in Task Modify the verify-enterprise-contract Task to no longer rely on the cosign binary to initialize the TUF root. Instead, use the newly added `ec sigstore initialize` command. As a consequence, the cosign binary is also removed from the ec-cli container image. Ref: EC-584 Signed-off-by: Luiz Carvalho --- Dockerfile | 25 ----------------- Dockerfile.dist | 27 ++----------------- .../__snapshots__/task_validate_image.snap | 1 - .../0.1/verify-enterprise-contract.yaml | 2 +- 4 files changed, 3 insertions(+), 52 deletions(-) diff --git a/Dockerfile b/Dockerfile index 6b6766827..0da733121 100644 --- a/Dockerfile +++ b/Dockerfile @@ -32,36 +32,11 @@ COPY . . RUN /build/build.sh "${TARGETOS}_${TARGETARCH}" -# Extract this so we can download the matching cosign version below -RUN go list --mod=readonly -f '{{.Version}}' -m github.com/sigstore/cosign/v2 | tee cosign_version.txt - -## Downloads - -FROM registry.access.redhat.com/ubi9/ubi-minimal:9.4@sha256:ef6fb6b3b38ef6c85daebeabebc7ff3151b9dd1500056e6abc9c3295e4b78a51 AS download - -ARG TARGETOS -ARG TARGETARCH - -WORKDIR /download - -COPY --from=build /build/cosign_version.txt /download/ - -# Download the matching version of cosign -RUN COSIGN_VERSION=$(cat /download/cosign_version.txt) && \ - curl -sLO https://github.com/sigstore/cosign/releases/download/${COSIGN_VERSION}/cosign-${TARGETOS}-${TARGETARCH} && \ - curl -sLO https://github.com/sigstore/cosign/releases/download/${COSIGN_VERSION}/cosign_checksums.txt && \ - sha256sum --check <(grep -w "cosign-${TARGETOS}-${TARGETARCH}" < cosign_checksums.txt) && \ - mv "cosign-${TARGETOS}-${TARGETARCH}" cosign && \ - chmod +x cosign - FROM registry.access.redhat.com/ubi9/ubi-minimal:9.4@sha256:ef6fb6b3b38ef6c85daebeabebc7ff3151b9dd1500056e6abc9c3295e4b78a51 ARG TARGETOS ARG TARGETARCH -COPY --from=download /download/cosign /usr/local/bin/cosign -RUN cosign version - RUN microdnf upgrade --assumeyes --nodocs --setopt=keepcache=0 --refresh && microdnf -y --nodocs --setopt=keepcache=0 install git-core jq # Copy the one ec binary that can run in this container diff --git a/Dockerfile.dist b/Dockerfile.dist index 5cd3a1bcf..d51be3a77 100644 --- a/Dockerfile.dist +++ b/Dockerfile.dist @@ -40,28 +40,6 @@ COPY . . RUN /build/build.sh "${BUILD_LIST}" "${BUILD_SUFFIX}" -# Extract this so we can download the matching cosign version below -RUN go list --mod=readonly -f '{{.Version}}' -m github.com/sigstore/cosign/v2 | tee cosign_version.txt - -## Downloads - -FROM registry.access.redhat.com/ubi9/ubi-minimal:9.4@sha256:ef6fb6b3b38ef6c85daebeabebc7ff3151b9dd1500056e6abc9c3295e4b78a51 AS download - -ARG TARGETOS -ARG TARGETARCH - -WORKDIR /download - -COPY --from=build /build/cosign_version.txt /download/ - -# Download the matching version of cosign -RUN COSIGN_VERSION=$(cat /download/cosign_version.txt) && \ - curl -sLO https://github.com/sigstore/cosign/releases/download/${COSIGN_VERSION}/cosign-${TARGETOS}-${TARGETARCH} && \ - curl -sLO https://github.com/sigstore/cosign/releases/download/${COSIGN_VERSION}/cosign_checksums.txt && \ - sha256sum --check <(grep -w "cosign-${TARGETOS}-${TARGETARCH}" < cosign_checksums.txt) && \ - mv "cosign-${TARGETOS}-${TARGETARCH}" cosign && \ - chmod +x cosign - ## Final image FROM registry.access.redhat.com/ubi9/ubi-minimal:9.4@sha256:ef6fb6b3b38ef6c85daebeabebc7ff3151b9dd1500056e6abc9c3295e4b78a51 @@ -78,9 +56,8 @@ LABEL \ io.openshift.tags="rhtas rhtap trusted-artifact-signer trusted-application-pipeline enterprise-contract ec opa cosign sigstore" \ com.redhat.component="ec-cli" -# Install cosign and other tools we want to use in the Tekton task +# Install tools we want to use in the Tekton task RUN microdnf upgrade --assumeyes --nodocs --setopt=keepcache=0 --refresh && microdnf -y --nodocs --setopt=keepcache=0 install git-core jq -COPY --from=download /download/cosign /usr/local/bin/cosign # Copy all the binaries so they're available to extract and download # (Beware if you're testing this locally it will copy everything from @@ -101,6 +78,6 @@ COPY --from=build /build/LICENSE /licenses/LICENSE USER 1001 # Show some version numbers for troubleshooting purposes -RUN git version && jq --version && cosign version && ec version && ls -l /usr/local/bin +RUN git version && jq --version && ec version && ls -l /usr/local/bin ENTRYPOINT ["/usr/local/bin/ec"] diff --git a/features/__snapshots__/task_validate_image.snap b/features/__snapshots__/task_validate_image.snap index fec1f2c88..8791dc98d 100755 --- a/features/__snapshots__/task_validate_image.snap +++ b/features/__snapshots__/task_validate_image.snap @@ -376,7 +376,6 @@ ${TIMESTAMP} Skipping step because a previous step failed [Initialize TUF fails:initialize-tuf - 1] Initializing TUF root... Error: Get "http://tuf.invalid/root.json": dial tcp: lookup tuf.invalid on 10.96.0.10:53: no such host -main.go:74: error during command execution: Get "http://tuf.invalid/root.json": dial tcp: lookup tuf.invalid on 10.96.0.10:53: no such host --- diff --git a/tasks/verify-enterprise-contract/0.1/verify-enterprise-contract.yaml b/tasks/verify-enterprise-contract/0.1/verify-enterprise-contract.yaml index ddbb0b59d..065d00e10 100644 --- a/tasks/verify-enterprise-contract/0.1/verify-enterprise-contract.yaml +++ b/tasks/verify-enterprise-contract/0.1/verify-enterprise-contract.yaml @@ -147,7 +147,7 @@ spec: fi echo 'Initializing TUF root...' - cosign initialize --mirror "${TUF_MIRROR}" --root "${TUF_MIRROR}/root.json" + ec sigstore initialize --mirror "${TUF_MIRROR}" --root "${TUF_MIRROR}/root.json" echo 'Done!' env: - name: TUF_MIRROR