diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md new file mode 100644 index 00000000..1efbee32 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -0,0 +1,26 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '[Bug]: ' +labels: ['bug', triage'] +assignees: '' +--- + +**What steps did you take and what happened:** + +[A clear and concise description of what the bug is.] + +**What did you expect to happen:** + +[Expected outcome listed here.] + +**Anything else you would like to add:** + +[Miscellaneous information that will assist in solving the issue.] + +**Environment:** + +- Witness version: +- Architecture: +- Attestors used: +- Archivista version: diff --git a/.github/ISSUE_TEMPLATE/feature-request.md b/.github/ISSUE_TEMPLATE/feature-request.md new file mode 100644 index 00000000..a8540f67 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request.md @@ -0,0 +1,36 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '[Feat]: ' +labels: ['feature', 'triage'] +assignees: '' + +--- + +**Describe the solution you'd like:** + +[A clear and concise description of what you want to happen.] + +**User value:** + +[Why will this feature be valuable to you? Why will this be valuable to others?] + +**Expected behavior:** + +[What would you like to see happen] + +**Proposed solution:** + +[If you're able, describe possible solution workflow] + +**Anything else you would like to add:** + +[Miscellaneous information that will assist in solving the issue.] + +**Testing changes required:** + +[List possible testing changes required, if none please explain, if unsure assignee will assist] + +**Documentation changes required:** + +[List possible documentation changes required, if none please explain, if unsure assignee will assist] diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..0d0d900f --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,19 @@ +## What this PR does / why we need it + +Description + +## Which issue(s) this PR fixes (optional) + +(optional, using `fixes #(, fixes #, ...)` format, will close the issue(s) when the PR gets merged)* + +Fixes # + +## Acceptance Criteria Met + +- [ ] Docs changes if needed +- [ ] Testing changes if needed +- [ ] All workflow checks passing (automatically enforced) +- [ ] All review conversations resolved (automatically enforced) +- [ ] [DCO Sign-off](https://github.com/apps/dco) + +**Special notes for your reviewer**: diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 290a8cb0..6c10102c 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -50,7 +50,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@012739e5082ff0c22ca6d6ab32e07c36df03c4a4 # v3.22.12 + uses: github/codeql-action/init@e5f05b81d5b6ff8cfa111c80c22c5fd02a384118 # v3.23.0 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -60,7 +60,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@012739e5082ff0c22ca6d6ab32e07c36df03c4a4 # v3.22.12 + uses: github/codeql-action/autobuild@e5f05b81d5b6ff8cfa111c80c22c5fd02a384118 # v3.23.0 # ℹī¸ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun @@ -73,6 +73,6 @@ jobs: # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@012739e5082ff0c22ca6d6ab32e07c36df03c4a4 # v3.22.12 + uses: github/codeql-action/analyze@e5f05b81d5b6ff8cfa111c80c22c5fd02a384118 # v3.23.0 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1e09db9a..d858616d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -76,7 +76,7 @@ jobs: uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 with: go-version: 1.21.x - - uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3.3.2 + - uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4.0.0 with: path: | ~/go/pkg/mod diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index d0311f66..34618bdc 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -67,7 +67,7 @@ jobs: # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - name: "Upload artifact" - uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # tag=v4.0.0 + uses: actions/upload-artifact@1eb3cb2b3e0f29609092a73eb033bb759a334595 # tag=v4.1.0 with: name: SARIF file path: results.sarif @@ -75,6 +75,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@012739e5082ff0c22ca6d6ab32e07c36df03c4a4 # tag=v3.22.12 + uses: github/codeql-action/upload-sarif@e5f05b81d5b6ff8cfa111c80c22c5fd02a384118 # tag=v3.23.0 with: sarif_file: results.sarif diff --git a/.github/workflows/witness.yml b/.github/workflows/witness.yml index 2e353ed2..94145228 100644 --- a/.github/workflows/witness.yml +++ b/.github/workflows/witness.yml @@ -56,7 +56,7 @@ jobs: go-version: 1.21.x - if: ${{ inputs.artifact-download != '' }} - uses: actions/download-artifact@f44cd7b40bfd40b6aa1cc1b9b5b7bf03d3c67110 # v4.1.0 + uses: actions/download-artifact@6b208ae046db98c579e8a3aa621ab581ff575935 # v4.1.1 with: name: ${{ inputs.artifact-download }} path: /tmp @@ -80,7 +80,7 @@ jobs: run: ${{ inputs.command }} - if: ${{ inputs.artifact-upload-path != '' && inputs.artifact-upload-name != ''}} - uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4.0.0 + uses: actions/upload-artifact@1eb3cb2b3e0f29609092a73eb033bb759a334595 # v4.1.0 with: name: ${{ inputs.artifact-upload-name }} path: ${{ inputs.artifact-upload-path }} diff --git a/cmd/run.go b/cmd/run.go index ad19a30c..2efbf4b1 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -85,12 +85,26 @@ func runRun(ctx context.Context, ro options.RunOptions, args []string, signers . attestors = append(attestors, commandrun.New(commandrun.WithCommand(args), commandrun.WithTracing(ro.Tracing))) } - addtlAttestors, err := attestation.Attestors(ro.Attestations) - if err != nil { - return fmt.Errorf("failed to create attestors := %w", err) + for _, a := range ro.Attestations { + duplicate := false + for _, att := range attestors { + if a != att.Name() { + } else { + log.Warnf("Attestator %s already declared, skipping", a) + duplicate = true + break + } + } + + if !duplicate { + attestor, err := attestation.GetAttestor(a) + if err != nil { + return fmt.Errorf("failed to create attestor: %w", err) + } + attestors = append(attestors, attestor) + } } - attestors = append(attestors, addtlAttestors...) for _, attestor := range attestors { setters, ok := ro.AttestorOptSetters[attestor.Name()] if !ok { @@ -120,7 +134,6 @@ func runRun(ctx context.Context, ro options.RunOptions, args []string, signers . witness.RunWithAttestationOpts(attestation.WithWorkingDir(ro.WorkingDir), attestation.WithHashes(roHashes)), witness.RunWithTimestampers(timestampers...), ) - if err != nil { return err } diff --git a/cmd/run_test.go b/cmd/run_test.go index 03cd2db3..4871c9d2 100644 --- a/cmd/run_test.go +++ b/cmd/run_test.go @@ -20,15 +20,20 @@ import ( "crypto/rand" "crypto/rsa" "encoding/json" + "fmt" "os" "path/filepath" + "strings" "testing" "github.com/in-toto/go-witness/cryptoutil" "github.com/in-toto/go-witness/dsse" + "github.com/in-toto/go-witness/log" "github.com/in-toto/go-witness/signer" "github.com/in-toto/go-witness/signer/file" "github.com/in-toto/witness/options" + "github.com/sirupsen/logrus" + "github.com/sirupsen/logrus/hooks/test" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -171,3 +176,73 @@ func TestRunHashesOptions(t *testing.T) { }) } } + +func TestRunDuplicateAttestors(t *testing.T) { + tests := []struct { + name string + attestors []string + expectWarn int + }{ + { + name: "No duplicate attestors", + attestors: []string{"environment"}, + expectWarn: 0, + }, + { + name: "duplicate attestors", + attestors: []string{"environment", "environment"}, + expectWarn: 1, + }, + { + name: "duplicate attestor due to default", + attestors: []string{"product"}, + expectWarn: 1, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fmt.Println(tt.name) + testLogger, hook := test.NewNullLogger() + log.SetLogger(testLogger) + + privatekey, err := rsa.GenerateKey(rand.Reader, keybits) + require.NoError(t, err) + signer := cryptoutil.NewRSASigner(privatekey, crypto.SHA256) + + workingDir := t.TempDir() + attestationPath := filepath.Join(workingDir, "outfile.txt") + runOptions := options.RunOptions{ + WorkingDir: workingDir, + Attestations: tt.attestors, + OutFilePath: attestationPath, + StepName: "teststep", + Tracing: false, + } + + args := []string{ + "bash", + "-c", + "echo 'test' > test.txt", + } + + err = runRun(context.Background(), runOptions, args, signer) + if tt.expectWarn > 0 { + c := 0 + for _, entry := range hook.AllEntries() { + fmt.Println(tt.name, "log:", entry.Message) + if entry.Level == logrus.WarnLevel && strings.Contains(entry.Message, "already declared, skipping") { + c++ + } + } + assert.Equal(t, tt.expectWarn, c) + } else { + require.NoError(t, err) + attestationBytes, err := os.ReadFile(attestationPath) + require.NoError(t, err) + env := dsse.Envelope{} + require.NoError(t, json.Unmarshal(attestationBytes, &env)) + } + }) + } +} diff --git a/docs/witness_run.md b/docs/witness_run.md index 6c5851b7..831832d2 100644 --- a/docs/witness_run.md +++ b/docs/witness_run.md @@ -10,7 +10,7 @@ witness run [cmd] [flags] ``` --archivista-server string URL of the Archivista server to store or retrieve attestations (default "https://archivista.testifysec.io") - -a, --attestations strings Attestations to record (default [environment,git]) + -a, --attestations strings Attestations to record ('product' and 'material' are always recorded) (default [environment,git]) --attestor-product-exclude-glob string Pattern to use when recording products. Files that match this pattern will be excluded as subjects on the attestation. --attestor-product-include-glob string Pattern to use when recording products. Files that match this pattern will be included as subjects on the attestation. (default "*") --enable-archivista Use Archivista to store or retrieve attestations @@ -22,6 +22,7 @@ witness run [cmd] [flags] -k, --signer-file-key-path string Path to the file containing the private key --signer-fulcio-oidc-client-id string OIDC client ID to use for authentication --signer-fulcio-oidc-issuer string OIDC issuer to use for authentication + --signer-fulcio-oidc-redirect-url string OIDC redirect URL (Optional). The default oidc-redirect-url is 'http://localhost:0/auth/callback'. --signer-fulcio-token string Raw token string to use for authentication to fulcio (cannot be used in conjunction with --fulcio-token-path) --signer-fulcio-token-path string Path to the file containing a raw token to use for authentication to fulcio (cannot be used in conjunction with --fulcio-token) --signer-fulcio-url string Fulcio address to sign with diff --git a/docs/witness_sign.md b/docs/witness_sign.md index c20b5fd3..d91a689a 100644 --- a/docs/witness_sign.md +++ b/docs/witness_sign.md @@ -22,6 +22,7 @@ witness sign [file] [flags] -k, --signer-file-key-path string Path to the file containing the private key --signer-fulcio-oidc-client-id string OIDC client ID to use for authentication --signer-fulcio-oidc-issuer string OIDC issuer to use for authentication + --signer-fulcio-oidc-redirect-url string OIDC redirect URL (Optional). The default oidc-redirect-url is 'http://localhost:0/auth/callback'. --signer-fulcio-token string Raw token string to use for authentication to fulcio (cannot be used in conjunction with --fulcio-token-path) --signer-fulcio-token-path string Path to the file containing a raw token to use for authentication to fulcio (cannot be used in conjunction with --fulcio-token) --signer-fulcio-url string Fulcio address to sign with diff --git a/go.mod b/go.mod index abc0b988..fa1478c2 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.19 replace github.com/in-toto/go-witness => ../go-witness require ( - github.com/in-toto/go-witness v0.2.0 + github.com/in-toto/go-witness v0.2.1 github.com/sirupsen/logrus v1.9.3 github.com/spf13/cobra v1.8.0 github.com/spf13/pflag v1.0.5 diff --git a/go.sum b/go.sum index d09a1fd3..9bfc6685 100644 --- a/go.sum +++ b/go.sum @@ -220,6 +220,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1: github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/in-toto/archivista v0.2.0 h1:FViuHMVVETborvOqlmSYdROY8RmX3CO0V0MOhU/Rl20= github.com/in-toto/archivista v0.2.0/go.mod h1:qt9uN4TkHWUgR5A2wxRqQIBizSl32P2nI2AjESskkr0= +github.com/in-toto/go-witness v0.2.1 h1:eAxMBWUPbz3oPU3lsfEYi/Kdj6weej2umm59bOXPJSU= +github.com/in-toto/go-witness v0.2.1/go.mod h1:xURJVj4QRD3xnzOJps7gT0pMCFPpAHcPqDC3EyuLuUE= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= diff --git a/options/run.go b/options/run.go index 3cccb3f7..0e7ab343 100644 --- a/options/run.go +++ b/options/run.go @@ -37,7 +37,7 @@ func (ro *RunOptions) AddFlags(cmd *cobra.Command) { ro.SignerOptions.AddFlags(cmd) ro.ArchivistaOptions.AddFlags(cmd) cmd.Flags().StringVarP(&ro.WorkingDir, "workingdir", "d", "", "Directory from which commands will run") - cmd.Flags().StringSliceVarP(&ro.Attestations, "attestations", "a", []string{"environment", "git"}, "Attestations to record") + cmd.Flags().StringSliceVarP(&ro.Attestations, "attestations", "a", []string{"environment", "git"}, "Attestations to record ('product' and 'material' are always recorded)") cmd.Flags().StringSliceVar(&ro.Hashes, "hashes", []string{"sha256"}, "Hashes selected for digest calculation. Defaults to SHA256") cmd.Flags().StringVarP(&ro.OutFilePath, "outfile", "o", "", "File to which to write signed data. Defaults to stdout") cmd.Flags().StringVarP(&ro.StepName, "step", "s", "", "Name of the step being run")