From 39d504159039b4451f2b8ac0d55173a434f38ba2 Mon Sep 17 00:00:00 2001 From: Matthias Glastra Date: Mon, 29 Apr 2024 21:16:57 +0200 Subject: [PATCH 1/7] chore: Adding dirhas-glob argument. Signed-off-by: Matthias Glastra --- cmd/run.go | 15 ++++++++++++++- options/run.go | 2 ++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/cmd/run.go b/cmd/run.go index 9f2091ed..88c7bec5 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -18,7 +18,9 @@ import ( "context" "encoding/json" "fmt" + "strings" + "github.com/gobwas/glob" witness "github.com/in-toto/go-witness" "github.com/in-toto/go-witness/archivista" "github.com/in-toto/go-witness/attestation" @@ -127,11 +129,22 @@ func runRun(ctx context.Context, ro options.RunOptions, args []string, signers . roHashes = append(roHashes, cryptoutil.DigestValue{Hash: hash, GitOID: false}) } + for _, dirHashGlobItem := range ro.DirHashGlobs { + if strings.Contains(dirHashGlobItem, "**") { + return fmt.Errorf("failed to parse dirhash-glob: can not contain deep (**) glob matches") + } + + _, err := glob.Compile(dirHashGlobItem) + if err != nil { + return fmt.Errorf("failed to compile glob: %v", err) + } + } + results, err := witness.RunWithExports( ro.StepName, witness.RunWithSigners(signers...), witness.RunWithAttestors(attestors), - witness.RunWithAttestationOpts(attestation.WithWorkingDir(ro.WorkingDir), attestation.WithHashes(roHashes)), + witness.RunWithAttestationOpts(attestation.WithWorkingDir(ro.WorkingDir), attestation.WithHashes(roHashes), attestation.WithDirHashGlob(ro.DirHashGlobs)), witness.RunWithTimestampers(timestampers...), ) if err != nil { diff --git a/options/run.go b/options/run.go index 05e087b9..a2eea1a6 100644 --- a/options/run.go +++ b/options/run.go @@ -28,6 +28,7 @@ type RunOptions struct { ArchivistaOptions ArchivistaOptions WorkingDir string Attestations []string + DirHashGlobs []string Hashes []string OutFilePath string StepName string @@ -51,6 +52,7 @@ func (ro *RunOptions) AddFlags(cmd *cobra.Command) { ro.ArchivistaOptions.AddFlags(cmd) cmd.Flags().StringVarP(&ro.WorkingDir, "workingdir", "d", "", "Directory from which commands will run") cmd.Flags().StringSliceVarP(&ro.Attestations, "attestations", "a", DefaultAttestors, "Attestations to record ('product' and 'material' are always recorded)") + cmd.Flags().StringSliceVar(&ro.DirHashGlobs, "dirhash-glob", []string{}, "Dirhash glob can be used to collapse material and product hashes on matching directory matches.") 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 write signed data to") cmd.Flags().StringVarP(&ro.StepName, "step", "s", "", "Name of the step being run") From bf8b7dfd05e1ea1fd05ba2f96ffc07ad5fdbeebd Mon Sep 17 00:00:00 2001 From: Matthias Glastra Date: Wed, 1 May 2024 17:58:23 +0200 Subject: [PATCH 2/7] docs: Update command docs Signed-off-by: Matthias Glastra --- docs/commands.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/commands.md b/docs/commands.md index 2de90877..fa6b88d5 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -50,6 +50,7 @@ witness run [cmd] [flags] --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 "*") --attestor-sbom-export Export the SBOM predicate in its own attestation --attestor-slsa-export Export the SLSA provenance predicate in its own attestation + --dirhash-glob strings Dirhash glob can be used to collapse material and product hashes on matching directory matches. --enable-archivista Use Archivista to store or retrieve attestations --hashes strings Hashes selected for digest calculation. Defaults to SHA256 (default [sha256]) -h, --help help for run From d424680544b1015577a6ea1c1e7c0b519f66ba4a Mon Sep 17 00:00:00 2001 From: Matthias Glastra Date: Wed, 1 May 2024 18:07:40 +0200 Subject: [PATCH 3/7] chore: Remove check on ** glob. Signed-off-by: Matthias Glastra --- cmd/run.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/cmd/run.go b/cmd/run.go index 88c7bec5..f86c95ff 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -18,7 +18,6 @@ import ( "context" "encoding/json" "fmt" - "strings" "github.com/gobwas/glob" witness "github.com/in-toto/go-witness" @@ -130,10 +129,6 @@ func runRun(ctx context.Context, ro options.RunOptions, args []string, signers . } for _, dirHashGlobItem := range ro.DirHashGlobs { - if strings.Contains(dirHashGlobItem, "**") { - return fmt.Errorf("failed to parse dirhash-glob: can not contain deep (**) glob matches") - } - _, err := glob.Compile(dirHashGlobItem) if err != nil { return fmt.Errorf("failed to compile glob: %v", err) From dd0b5947b5356d4e607344605a4c468531cd5955 Mon Sep 17 00:00:00 2001 From: Matthias Glastra Date: Mon, 13 May 2024 17:27:13 +0200 Subject: [PATCH 4/7] chore: Add argument for dir subject. Signed-off-by: Matthias Glastra --- cmd/verify.go | 9 +++++++++ options/verify.go | 4 +++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/cmd/verify.go b/cmd/verify.go index b12d3fc6..e59f130e 100644 --- a/cmd/verify.go +++ b/cmd/verify.go @@ -162,6 +162,15 @@ func runVerify(ctx context.Context, vo options.VerifyOptions, verifiers ...crypt } subjects := []cryptoutil.DigestSet{} + if len(vo.ArtifactDirectoryPath) > 0 { + artifactDigestSet, err := cryptoutil.CalculateDigestSetFromDir(vo.ArtifactDirectoryPath, []cryptoutil.DigestValue{{Hash: crypto.SHA256, GitOID: false}}) + if err != nil { + return fmt.Errorf("failed to calculate dir digest: %w", err) + } + + subjects = append(subjects, artifactDigestSet) + } + if len(vo.ArtifactFilePath) > 0 { artifactDigestSet, err := cryptoutil.CalculateDigestSetFromFile(vo.ArtifactFilePath, []cryptoutil.DigestValue{{Hash: crypto.SHA256, GitOID: false}}) if err != nil { diff --git a/options/verify.go b/options/verify.go index 4f202864..0fd7c675 100644 --- a/options/verify.go +++ b/options/verify.go @@ -27,6 +27,7 @@ type VerifyOptions struct { AttestationFilePaths []string PolicyFilePath string ArtifactFilePath string + ArtifactDirectoryPath string AdditionalSubjects []string PolicyFulcioCertExtensions certificate.Extensions PolicyCARootPaths []string @@ -63,7 +64,8 @@ func (vo *VerifyOptions) AddFlags(cmd *cobra.Command) { cmd.Flags().StringVarP(&vo.KeyPath, "publickey", "k", "", "Path to the policy signer's public key") cmd.Flags().StringSliceVarP(&vo.AttestationFilePaths, "attestations", "a", []string{}, "Attestation files to test against the policy") cmd.Flags().StringVarP(&vo.PolicyFilePath, "policy", "p", "", "Path to the policy to verify") - cmd.Flags().StringVarP(&vo.ArtifactFilePath, "artifactfile", "f", "", "Path to the artifact to verify") + cmd.Flags().StringVarP(&vo.ArtifactFilePath, "artifactfile", "f", "", "Path to the artifact subject to verify") + cmd.Flags().StringVarP(&vo.ArtifactDirectoryPath, "directory-path", "", "", "Path to the directory subject to verify") cmd.Flags().StringSliceVarP(&vo.AdditionalSubjects, "subjects", "s", []string{}, "Additional subjects to lookup attestations") cmd.Flags().StringSliceVarP(&vo.PolicyCARootPaths, "policy-ca-roots", "", []string{}, "Paths to CA root certificates to use for verifying a policy signed with x.509") cmd.Flags().StringSliceVarP(&vo.PolicyCAIntermediatePaths, "policy-ca-intermediates", "", []string{}, "Paths to CA intermediate certificates to use for verifying a policy signed with x.509") From ca72b8fc8c72ed5adf0a5ab8a6e089d05bcd0ae7 Mon Sep 17 00:00:00 2001 From: Matthias Glastra Date: Tue, 14 May 2024 09:10:26 +0200 Subject: [PATCH 5/7] Add logger to verify_test Signed-off-by: Matthias Glastra --- cmd/verify_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cmd/verify_test.go b/cmd/verify_test.go index d11f251c..091f4df6 100644 --- a/cmd/verify_test.go +++ b/cmd/verify_test.go @@ -32,6 +32,7 @@ import ( "github.com/in-toto/go-witness/attestation/commandrun" "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/policy" "github.com/in-toto/go-witness/signer" "github.com/in-toto/go-witness/signer/file" @@ -208,6 +209,9 @@ func TestRunVerifyCA(t *testing.T) { } func TestRunVerifyKeyPair(t *testing.T) { + logger := newLogger() + log.SetLogger(logger) + policy, funcPriv := makepolicyRSAPub(t) signedPolicy, pub := signPolicyRSA(t, policy) workingDir := t.TempDir() From c14545abcd23e26605bd5b50b583970bdecc71d9 Mon Sep 17 00:00:00 2001 From: Matthias Glastra Date: Mon, 20 May 2024 12:52:17 +0200 Subject: [PATCH 6/7] chore: Update command docs Signed-off-by: Matthias Glastra --- docs/commands.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/commands.md b/docs/commands.md index fa6b88d5..96b8f7e8 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -179,8 +179,9 @@ witness verify [flags] ``` --archivista-server string URL of the Archivista server to store or retrieve attestations (default "https://archivista.testifysec.io") - -f, --artifactfile string Path to the artifact to verify + -f, --artifactfile string Path to the artifact subject to verify -a, --attestations strings Attestation files to test against the policy + --directory-path string Path to the directory subject to verify --enable-archivista Use Archivista to store or retrieve attestations -h, --help help for verify -p, --policy string Path to the policy to verify From 091a03153c4156be29a82c36b6c7aabbad38e38c Mon Sep 17 00:00:00 2001 From: Matthias Glastra Date: Mon, 20 May 2024 12:58:27 +0200 Subject: [PATCH 7/7] chore: Add dirhash examples to tutorial Signed-off-by: Matthias Glastra --- docs/tutorials/getting-started.md | 36 ++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/docs/tutorials/getting-started.md b/docs/tutorials/getting-started.md index 8bc111a8..ca0c7ac0 100644 --- a/docs/tutorials/getting-started.md +++ b/docs/tutorials/getting-started.md @@ -1,6 +1,7 @@ # Getting Started ## Intro + This quick tutorial will walk you through a simple example of how Witness can be used. To complete it successfully, you will need the following: @@ -16,12 +17,14 @@ You will also of course need to have witness installed, which can be achieved by ### 1. Create a Keypair >💡 Tip: Witness supports keyless signing with [SPIRE](https://spiffe.io/)! -``` + +```shell openssl genpkey -algorithm ed25519 -outform PEM -out testkey.pem openssl pkey -in testkey.pem -pubout > testpub.pem ``` ### 2. Create a Witness Configuration + >💡 Tip: Witness supports creating attestations for a wide variety of services, > including Github Actions @@ -30,7 +33,7 @@ openssl pkey -in testkey.pem -pubout > testpub.pem - `witness help` will show all configuration options - command-line arguments overrides configuration file values. -``` +```yaml ## .witness.yaml run: @@ -44,17 +47,21 @@ verify: ``` ### 3. Record attestations for a build step + >💡 Tip: You can upload the recorded attestations to an [Archivista](https://github.com/in-toto/archivista) server by using the `--enable-archivista` flag! + - The `-a {attestor}` flag allows you to define which attestors run - ex. `-a maven -a gcp -a gitlab` would be used for a maven build running on a GitLab runner on GCP. - Witness has a set of attestors that are always run. You can see them in the output of the `witness attestors list` command. - Defining step names is important, these will be used in the policy. - This should happen as a part of a CI step -``` +```shell witness run --step build -o test-att.json -a slsa --attestor-slsa-export -- go build -o=testapp . ``` +>💡 Tip: When you run a step with many files as the product of that step, like node_modules, it could be beneficial to collapse the result into a hash of the directory content. You can use `--dirhash-glob ` to match the directory or use it multiple times to use different glob patterns. E.g. `--dirhash-glob node_modules/*` + >💡 Tip: The `-a slsa` option allows to generate the [SLSA Provenace](https://slsa.dev/spec/v1.0/provenance) predicate in the attestation. The `--attestor-slsa-export` option allows to write the Provenance in a dedicated file. This is a mandatory requirement for SLSA Level 1 ### 4. View the attestation data in the signed DSSE Envelope @@ -62,7 +69,7 @@ witness run --step build -o test-att.json -a slsa --attestor-slsa-export -- go b - This data can be stored and retrieved from Archivista - This is the data that is evaluated against the Rego policy -``` +```shell cat test-att.json | jq -r .payload | base64 -d | jq ``` @@ -76,7 +83,7 @@ Look [here](/docs/concepts/policy.md) for full documentation on Witness Policies > - Witness will require all attestations to succeed > - Witness will evaluate the rego policy against the JSON object in the corresponding attestor -``` +```json ## policy.json { @@ -116,7 +123,7 @@ Look [here](/docs/concepts/policy.md) for full documentation on Witness Policies ### 6. Replace the variables in the policy -``` +```shell id=`sha256sum testpub.pem | awk '{print $1}'` && sed -i "s/{{PUBLIC_KEY_ID}}/$id/g" policy.json pubb64=`cat testpub.pem | base64 -w 0` && sed -i "s/{{B64_PUBLIC_KEY}}/$pubb64/g" policy.json ``` @@ -125,19 +132,28 @@ pubb64=`cat testpub.pem | base64 -w 0` && sed -i "s/{{B64_PUBLIC_KEY}}/$pubb64/g Keep this key safe, its owner will control the policy gates. -``` +```shell witness sign -f policy.json --signer-file-key-path testkey.pem --outfile policy-signed.json ``` ### 8. Verify the Binary Meets Policy Requirements -This process works across air-gap as long as you have the signed policy file, correct binary, and public key or certificate authority corresponding to the private -key that signed the policy. -``` +This process works across air-gap as long as you have the signed policy file, correct binary, and public key or certificate authority corresponding to the private key that signed the policy. + +```shell witness verify -f testapp -a test-att.json -p policy-signed.json -k testpub.pem ``` + +If you want to verify a directory as a subject you can use the following. + +```shell +witness verify --directory-path node_modules/example -a test-att.json -p policy-signed.json -k testpub.pem +``` + ### 9. Profit + `witness verify` will return a `non-zero` exit and reason in the case of failure, but hopefully you should have gotten sweet sweet silence with a `0` exit status, victory! If not, try again and if that fails please [file an issue](https://github.com/in-toto/witness/issues/new/choose)! ## What's Next? + If you enjoyed this intro to Witness, you might benefit from taking things a step further by learning about [Witness Policies](./artifact-policy.md).