From a3422b5331bbcc340094a904ebf839bdab93e138 Mon Sep 17 00:00:00 2001 From: mrjoelkamp Date: Mon, 22 Apr 2024 12:22:15 -0500 Subject: [PATCH 1/5] feat: add policy, oci, attestation --- go.mod | 35 +- go.sum | 34 +- internal/oci/http.go | 27 ++ internal/oci/oci.go | 395 ++++++++++++++++++ internal/oci/oci_test.go | 49 +++ internal/oci/resolver.go | 46 ++ internal/oci/types.go | 8 + internal/test/test.go | 64 +++ internal/util/strings.go | 10 + pkg/attestation/sign.go | 62 +++ pkg/attestation/sign_test.go | 147 +++++++ pkg/attestation/types.go | 35 ++ pkg/attestation/verify.go | 134 ++++++ pkg/attestation/verify_test.go | 46 ++ pkg/mirror/metadata_test.go | 6 +- pkg/mirror/targets_test.go | 6 +- pkg/policy/evaluator.go | 41 ++ pkg/policy/policy.go | 203 +++++++++ pkg/policy/policy_test.go | 110 +++++ pkg/policy/rego.go | 225 ++++++++++ .../testdata/mock-tuf-allow/doi/data.yaml | 1 + .../testdata/mock-tuf-allow/doi/policy.rego | 5 + .../testdata/mock-tuf-allow/mapping.yaml | 10 + .../testdata/mock-tuf-deny/doi/data.yaml | 1 + .../testdata/mock-tuf-deny/doi/policy.rego | 5 + .../testdata/mock-tuf-deny/mapping.yaml | 10 + .../mock-tuf-verify-sig/doi/data.yaml | 1 + .../mock-tuf-verify-sig/doi/policy.rego | 15 + .../testdata/mock-tuf-verify-sig/mapping.yaml | 10 + .../testdata/mock-tuf-wrong-key/doi/data.yaml | 1 + .../mock-tuf-wrong-key/doi/policy.rego | 19 + .../testdata/mock-tuf-wrong-key/mapping.yaml | 10 + pkg/tuf/registry_test.go | 5 +- pkg/tuf/tuf_test.go | 26 +- test/testdata/example_attestation.json | 48 +++ test/testdata/local-policy/doi/data.yaml | 58 +++ test/testdata/local-policy/doi/policy.rego | 49 +++ .../local-policy/doi/policy_test.rego | 25 ++ test/testdata/local-policy/mapping.yaml | 16 + ...7724db0e4c4ba47b649930cec22a3e7e6b6077ba38 | 0 ...71ff9e58becca652fb1ce4a0e6ea55a01c4ec41950 | 0 ...927e38beae430d7e6d2bd6207054179429ea9b6763 | 0 ...92b07fbf630d2fe2d80a6f1a9dc09e1321cbd338a7 | 0 ...2df6978ed0287bb38342f6ddb7bf934a456f1d6f87 | 0 ...b5e3f5508dbaf6a76768a9d23741828172bab1dc97 | 0 ...cd8d09eeb59578fcb72ae394763cf7ef492175b1ee | 0 ...8f92b7e348733f9c1281b5b7e6b6d669a8a74230a7 | 0 ...dccda9f7f066595b1b2c6e37e52dbfd250f5287260 | 0 .../tuf}/test-repo-oci/metadata/index.json | 0 .../tuf}/test-repo-oci/metadata/oci-layout | 0 ...90399c5cf4a45e4fe2ddebb9066c55aa2bcf0a73d3 | 0 ...8bb41cf85dad23ec9cdc7d7d2e10bc37b86ebffff5 | 0 ...49ec6a6ea2fad3e7c939c32a8219aaa4726792457c | 0 .../metadata/test-role/index.json | 0 .../metadata/test-role/oci-layout | 0 ...95e20794f5a8d5bce3d7ecc264681bb7334ca2e24b | 0 ...fb6b010dcf0c2369f7a59a45ff29c693c844163ca7 | 0 ...fa9f094809e6cd9bd7364942c7f067c747bc535f94 | 0 .../index.json | 0 .../oci-layout | 0 ...ab4feaf052425fbf4ded864bc55c18cfefec8be6e2 | 0 ...f1fb6ce51537baa243fb9fccca03f2ff3c15fb52f8 | 0 ...fca1651604a6c0323011023145a140b38f02105b04 | 0 ...88b5e72f9a5942ebac20588aa0c4bf78ba621e1ee2 | 0 ...c1bc7cbf9c71a5d8035b6e27be0d9e5d9087599055 | 0 ...26125414f8957fb29c08350528d50a162c620f36b1 | 0 .../targets/test-role/index.json | 0 .../targets/test-role/oci-layout | 0 .../tuf}/test-repo/metadata/1.root.json | 0 .../tuf}/test-repo/metadata/2.root.json | 0 .../tuf}/test-repo/metadata/3.test-role.json | 0 .../tuf}/test-repo/metadata/5.targets.json | 0 .../tuf}/test-repo/metadata/6.snapshot.json | 0 .../tuf}/test-repo/metadata/timestamp.json | 0 ...5a8d5bce3d7ecc264681bb7334ca2e24b.test.txt | 0 ...a5942ebac20588aa0c4bf78ba621e1ee2.test.txt | 0 ...57fb29c08350528d50a162c620f36b1.myfile.txt | 0 test/testdata/vsa.json | 48 +++ 78 files changed, 2021 insertions(+), 25 deletions(-) create mode 100644 internal/oci/http.go create mode 100644 internal/oci/oci.go create mode 100644 internal/oci/oci_test.go create mode 100644 internal/oci/resolver.go create mode 100644 internal/oci/types.go create mode 100644 internal/util/strings.go create mode 100644 pkg/attestation/sign.go create mode 100644 pkg/attestation/sign_test.go create mode 100644 pkg/attestation/types.go create mode 100644 pkg/attestation/verify.go create mode 100644 pkg/attestation/verify_test.go create mode 100644 pkg/policy/evaluator.go create mode 100644 pkg/policy/policy.go create mode 100644 pkg/policy/policy_test.go create mode 100644 pkg/policy/rego.go create mode 100644 pkg/policy/testdata/mock-tuf-allow/doi/data.yaml create mode 100644 pkg/policy/testdata/mock-tuf-allow/doi/policy.rego create mode 100644 pkg/policy/testdata/mock-tuf-allow/mapping.yaml create mode 100644 pkg/policy/testdata/mock-tuf-deny/doi/data.yaml create mode 100644 pkg/policy/testdata/mock-tuf-deny/doi/policy.rego create mode 100644 pkg/policy/testdata/mock-tuf-deny/mapping.yaml create mode 100644 pkg/policy/testdata/mock-tuf-verify-sig/doi/data.yaml create mode 100644 pkg/policy/testdata/mock-tuf-verify-sig/doi/policy.rego create mode 100644 pkg/policy/testdata/mock-tuf-verify-sig/mapping.yaml create mode 100644 pkg/policy/testdata/mock-tuf-wrong-key/doi/data.yaml create mode 100644 pkg/policy/testdata/mock-tuf-wrong-key/doi/policy.rego create mode 100644 pkg/policy/testdata/mock-tuf-wrong-key/mapping.yaml create mode 100644 test/testdata/example_attestation.json create mode 100644 test/testdata/local-policy/doi/data.yaml create mode 100644 test/testdata/local-policy/doi/policy.rego create mode 100644 test/testdata/local-policy/doi/policy_test.rego create mode 100644 test/testdata/local-policy/mapping.yaml rename {internal/test/testdata => test/testdata/tuf}/test-repo-oci/metadata/blobs/sha256/1fd0d9781f02486718fcbd7724db0e4c4ba47b649930cec22a3e7e6b6077ba38 (100%) rename {internal/test/testdata => test/testdata/tuf}/test-repo-oci/metadata/blobs/sha256/4c1054844dba3241525cbd71ff9e58becca652fb1ce4a0e6ea55a01c4ec41950 (100%) rename {internal/test/testdata => test/testdata/tuf}/test-repo-oci/metadata/blobs/sha256/61a98e1e86ae279e59415d927e38beae430d7e6d2bd6207054179429ea9b6763 (100%) rename {internal/test/testdata => test/testdata/tuf}/test-repo-oci/metadata/blobs/sha256/a152e59e7b58a3a1ec718192b07fbf630d2fe2d80a6f1a9dc09e1321cbd338a7 (100%) rename {internal/test/testdata => test/testdata/tuf}/test-repo-oci/metadata/blobs/sha256/a2e026ce65c198ee68a7ed2df6978ed0287bb38342f6ddb7bf934a456f1d6f87 (100%) rename {internal/test/testdata => test/testdata/tuf}/test-repo-oci/metadata/blobs/sha256/a744a2f1e62ae4ce410822b5e3f5508dbaf6a76768a9d23741828172bab1dc97 (100%) rename {internal/test/testdata => test/testdata/tuf}/test-repo-oci/metadata/blobs/sha256/ad4cacc170229608305ffccd8d09eeb59578fcb72ae394763cf7ef492175b1ee (100%) rename {internal/test/testdata => test/testdata/tuf}/test-repo-oci/metadata/blobs/sha256/c927b30f17fa8c64e3c20b8f92b7e348733f9c1281b5b7e6b6d669a8a74230a7 (100%) rename {internal/test/testdata => test/testdata/tuf}/test-repo-oci/metadata/blobs/sha256/ea7713eb649ca1a33d79ebdccda9f7f066595b1b2c6e37e52dbfd250f5287260 (100%) rename {internal/test/testdata => test/testdata/tuf}/test-repo-oci/metadata/index.json (100%) rename {internal/test/testdata => test/testdata/tuf}/test-repo-oci/metadata/oci-layout (100%) rename {internal/test/testdata => test/testdata/tuf}/test-repo-oci/metadata/test-role/blobs/sha256/2b2d4fba192ec164e05e6d90399c5cf4a45e4fe2ddebb9066c55aa2bcf0a73d3 (100%) rename {internal/test/testdata => test/testdata/tuf}/test-repo-oci/metadata/test-role/blobs/sha256/7c8d8f5dfca62068e3a4b18bb41cf85dad23ec9cdc7d7d2e10bc37b86ebffff5 (100%) rename {internal/test/testdata => test/testdata/tuf}/test-repo-oci/metadata/test-role/blobs/sha256/9edf24c022c2cd6796e87f49ec6a6ea2fad3e7c939c32a8219aaa4726792457c (100%) rename {internal/test/testdata => test/testdata/tuf}/test-repo-oci/metadata/test-role/index.json (100%) rename {internal/test/testdata => test/testdata/tuf}/test-repo-oci/metadata/test-role/oci-layout (100%) rename {internal/test/testdata => test/testdata/tuf}/test-repo-oci/targets/02119a076ec3878c736c3a95e20794f5a8d5bce3d7ecc264681bb7334ca2e24b.test.txt/blobs/sha256/02119a076ec3878c736c3a95e20794f5a8d5bce3d7ecc264681bb7334ca2e24b (100%) rename {internal/test/testdata => test/testdata/tuf}/test-repo-oci/targets/02119a076ec3878c736c3a95e20794f5a8d5bce3d7ecc264681bb7334ca2e24b.test.txt/blobs/sha256/cf0c754e6415fab25e2f59fb6b010dcf0c2369f7a59a45ff29c693c844163ca7 (100%) rename {internal/test/testdata => test/testdata/tuf}/test-repo-oci/targets/02119a076ec3878c736c3a95e20794f5a8d5bce3d7ecc264681bb7334ca2e24b.test.txt/blobs/sha256/cf70a3b91fd7dfaa30952dfa9f094809e6cd9bd7364942c7f067c747bc535f94 (100%) rename {internal/test/testdata => test/testdata/tuf}/test-repo-oci/targets/02119a076ec3878c736c3a95e20794f5a8d5bce3d7ecc264681bb7334ca2e24b.test.txt/index.json (100%) rename {internal/test/testdata => test/testdata/tuf}/test-repo-oci/targets/02119a076ec3878c736c3a95e20794f5a8d5bce3d7ecc264681bb7334ca2e24b.test.txt/oci-layout (100%) rename {internal/test/testdata => test/testdata/tuf}/test-repo-oci/targets/test-role/blobs/sha256/0a4afcdad291941327b070ab4feaf052425fbf4ded864bc55c18cfefec8be6e2 (100%) rename {internal/test/testdata => test/testdata/tuf}/test-repo-oci/targets/test-role/blobs/sha256/0b6b8fdb10421310b9aca2f1fb6ce51537baa243fb9fccca03f2ff3c15fb52f8 (100%) rename {internal/test/testdata => test/testdata/tuf}/test-repo-oci/targets/test-role/blobs/sha256/8d320e9d3f3663613df6e4fca1651604a6c0323011023145a140b38f02105b04 (100%) rename {internal/test/testdata => test/testdata/tuf}/test-repo-oci/targets/test-role/blobs/sha256/d1bb6181284970ae43fbbc88b5e72f9a5942ebac20588aa0c4bf78ba621e1ee2 (100%) rename {internal/test/testdata => test/testdata/tuf}/test-repo-oci/targets/test-role/blobs/sha256/d9941355ca037d7e878e04c1bc7cbf9c71a5d8035b6e27be0d9e5d9087599055 (100%) rename {internal/test/testdata => test/testdata/tuf}/test-repo-oci/targets/test-role/blobs/sha256/ea230621c53e0bb858ea5526125414f8957fb29c08350528d50a162c620f36b1 (100%) rename {internal/test/testdata => test/testdata/tuf}/test-repo-oci/targets/test-role/index.json (100%) rename {internal/test/testdata => test/testdata/tuf}/test-repo-oci/targets/test-role/oci-layout (100%) rename {internal/test/testdata => test/testdata/tuf}/test-repo/metadata/1.root.json (100%) rename {internal/test/testdata => test/testdata/tuf}/test-repo/metadata/2.root.json (100%) rename {internal/test/testdata => test/testdata/tuf}/test-repo/metadata/3.test-role.json (100%) rename {internal/test/testdata => test/testdata/tuf}/test-repo/metadata/5.targets.json (100%) rename {internal/test/testdata => test/testdata/tuf}/test-repo/metadata/6.snapshot.json (100%) rename {internal/test/testdata => test/testdata/tuf}/test-repo/metadata/timestamp.json (100%) rename {internal/test/testdata => test/testdata/tuf}/test-repo/targets/02119a076ec3878c736c3a95e20794f5a8d5bce3d7ecc264681bb7334ca2e24b.test.txt (100%) rename {internal/test/testdata => test/testdata/tuf}/test-repo/targets/test-role/d1bb6181284970ae43fbbc88b5e72f9a5942ebac20588aa0c4bf78ba621e1ee2.test.txt (100%) rename {internal/test/testdata => test/testdata/tuf}/test-repo/targets/test-role/dir1/dir2/dir3/ea230621c53e0bb858ea5526125414f8957fb29c08350528d50a162c620f36b1.myfile.txt (100%) create mode 100644 test/testdata/vsa.json diff --git a/go.mod b/go.mod index da70722..d6525b3 100644 --- a/go.mod +++ b/go.mod @@ -4,9 +4,17 @@ go 1.22.1 require ( github.com/aws/aws-sdk-go-v2/config v1.27.11 + github.com/containerd/containerd v1.7.14 + github.com/distribution/reference v0.5.0 github.com/go-openapi/runtime v0.28.0 github.com/go-openapi/strfmt v0.23.0 github.com/google/go-containerregistry v0.19.1 + github.com/hashicorp/go-cleanhttp v0.5.2 + github.com/in-toto/in-toto-golang v0.9.0 + github.com/open-policy-agent/opa v0.63.0 + github.com/opencontainers/image-spec v1.1.0 + github.com/package-url/packageurl-go v0.1.2 + github.com/pkg/errors v0.9.1 github.com/secure-systems-lab/go-securesystemslib v0.8.0 github.com/sigstore/cosign/v2 v2.2.4 github.com/sigstore/rekor v1.3.6 @@ -15,6 +23,8 @@ require ( github.com/testcontainers/testcontainers-go v0.30.0 github.com/testcontainers/testcontainers-go/modules/registry v0.30.0 github.com/theupdateframework/go-tuf/v2 v2.0.0-20240402164131-b2e024ad4752 + gopkg.in/yaml.v3 v3.0.1 + sigs.k8s.io/yaml v1.4.0 ) replace github.com/theupdateframework/go-tuf/v2 => github.com/mrjoelkamp/go-tuf/v2 v2.0.1 // for https://github.com/theupdateframework/go-tuf/pull/632 @@ -24,6 +34,8 @@ require ( github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect github.com/Microsoft/hcsshim v0.11.4 // indirect + github.com/OneOfOne/xxhash v1.2.8 // indirect + github.com/agnivade/levenshtein v1.1.1 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/aws/aws-sdk-go-v2 v1.26.1 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.17.11 // indirect @@ -38,9 +50,10 @@ require ( github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.4 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.28.6 // indirect github.com/aws/smithy-go v1.20.2 // indirect + github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver v3.5.1+incompatible // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect - github.com/containerd/containerd v1.7.12 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/containerd/log v0.1.0 // indirect github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect github.com/cpuguy83/dockercfg v0.3.1 // indirect @@ -48,7 +61,6 @@ require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/digitorus/pkcs7 v0.0.0-20230818184609-3a137a874352 // indirect github.com/digitorus/timestamp v0.0.0-20231217203849-220c5c2851b7 // indirect - github.com/distribution/reference v0.5.0 // indirect github.com/docker/cli v24.0.7+incompatible // indirect github.com/docker/distribution v2.8.3+incompatible // indirect github.com/docker/docker v25.0.5+incompatible // indirect @@ -59,6 +71,7 @@ require ( github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/go-chi/chi v4.1.2+incompatible // indirect + github.com/go-ini/ini v1.67.0 // indirect github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.2.6 // indirect @@ -70,15 +83,15 @@ require ( github.com/go-openapi/spec v0.21.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect github.com/go-openapi/validate v0.24.0 // indirect + github.com/gobwas/glob v0.2.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/certificate-transparency-go v1.1.8 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/gorilla/mux v1.8.1 // indirect github.com/hashicorp/go-retryablehttp v0.7.5 // indirect github.com/hashicorp/hcl v1.0.1-vault-5 // indirect - github.com/in-toto/in-toto-golang v0.9.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267 // indirect github.com/jellydator/ttlcache/v3 v3.2.0 // indirect @@ -98,12 +111,15 @@ require ( github.com/nozzle/throttler v0.0.0-20180817012639-2ea982251481 // indirect github.com/oklog/ulid v1.3.1 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/image-spec v1.1.0 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/pelletier/go-toml/v2 v2.1.0 // indirect - github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect + github.com/prometheus/client_golang v1.19.0 // indirect + github.com/prometheus/client_model v0.6.0 // indirect + github.com/prometheus/common v0.51.1 // indirect + github.com/prometheus/procfs v0.12.0 // indirect + github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sassoftware/relic v7.2.1+incompatible // indirect @@ -121,17 +137,22 @@ require ( github.com/spf13/viper v1.18.2 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect + github.com/tchap/go-patricia/v2 v2.3.1 // indirect github.com/theupdateframework/go-tuf v0.7.0 // indirect github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect github.com/transparency-dev/merkle v0.0.2 // indirect github.com/vbatts/tar-split v0.11.5 // indirect + github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect + github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect + github.com/yashtewari/glob-intersection v0.2.0 // indirect github.com/yusufpapurcu/wmi v1.2.3 // indirect go.mongodb.org/mongo-driver v1.14.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect go.opentelemetry.io/otel v1.24.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 go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect @@ -148,6 +169,6 @@ require ( google.golang.org/protobuf v1.33.0 // indirect gopkg.in/go-jose/go-jose.v2 v2.6.3 // indirect gopkg.in/ini.v1 v1.67.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect k8s.io/klog/v2 v2.120.1 // indirect ) diff --git a/go.sum b/go.sum index 6353072..176ff9e 100644 --- a/go.sum +++ b/go.sum @@ -89,6 +89,8 @@ github.com/alibabacloud-go/tea-xml v1.1.3 h1:7LYnm+JbOq2B+T/B0fHC4Ies4/FofC4zHzY github.com/alibabacloud-go/tea-xml v1.1.3/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8= github.com/aliyun/credentials-go v1.3.1 h1:uq/0v7kWrxmoLGpqjx7vtQ/s03f0zR//0br/xWDTE28= github.com/aliyun/credentials-go v1.3.1/go.mod h1:8jKYhQuDawt8x2+fusqa1Y6mPxemTsBEN04dgcAcYz0= +github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q= +github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/aws/aws-sdk-go v1.51.6 h1:Ld36dn9r7P9IjU8WZSaswQ8Y/XUCRpewim5980DwYiU= @@ -137,10 +139,14 @@ github.com/buildkite/go-pipeline v0.3.2 h1:SW4EaXNwfjow7xDRPGgX0Rcx+dPj5C1kV9LKC github.com/buildkite/go-pipeline v0.3.2/go.mod h1:iY5jzs3Afc8yHg6KDUcu3EJVkfaUkd9x/v/OH98qyUA= github.com/buildkite/interpolate v0.0.0-20200526001904-07f35b4ae251 h1:k6UDF1uPYOs0iy1HPeotNa155qXRWrzKnqAaGXHLZCE= github.com/buildkite/interpolate v0.0.0-20200526001904-07f35b4ae251/go.mod h1:gbPR1gPu9dB96mucYIR7T3B7p/78hRVSOuzIWLHK2Y4= +github.com/bytecodealliance/wasmtime-go/v3 v3.0.2 h1:3uZCA/BLTIu+DqCfguByNMJa2HVHpXvjfy0Dy7g6fuA= +github.com/bytecodealliance/wasmtime-go/v3 v3.0.2/go.mod h1:RnUjnIXxEJcL6BgCvNyzCCRzZcxCgsZCi+RNlvYor5Q= github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M= github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chrismellard/docker-credential-acr-env v0.0.0-20230304212654-82a0ddb27589 h1:krfRl01rzPzxSxyLyrChD+U+MzsBXbm0OwYYB67uF+4= @@ -158,8 +164,8 @@ github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUo github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4= github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be h1:J5BL2kskAlV9ckgEsNQXscjIaLiOYiZ75d4e94E6dcQ= github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be/go.mod h1:mk5IQ+Y0ZeO87b858TlA645sVcEcbiX6YqP98kt+7+w= -github.com/containerd/containerd v1.7.12 h1:+KQsnv4VnzyxWcfO9mlxxELaoztsDEjOuCMPAuPqgU0= -github.com/containerd/containerd v1.7.12/go.mod h1:/5OMpE1p0ylxtEUGY8kuCYkDRzJm9NO1TFMWjUpdevk= +github.com/containerd/containerd v1.7.14 h1:H/XLzbnGuenZEGK+v0RkwTdv2u1QFAruMe5N0GNPJwA= +github.com/containerd/containerd v1.7.14/go.mod h1:YMC9Qt5yzNqXx/fO4j/5yYVIHXSRrlB3H7sxkUTvspg= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/containerd/stargz-snapshotter/estargz v0.14.3 h1:OqlDCK3ZVUO6C3B/5FSkDwbkEETK84kQgEeFwDC+62k= @@ -179,6 +185,12 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgraph-io/badger/v3 v3.2103.5 h1:ylPa6qzbjYRQMU6jokoj4wzcaweHylt//CH0AKt0akg= +github.com/dgraph-io/badger/v3 v3.2103.5/go.mod h1:4MPiseMeDQ3FNCYwRbbcBOGJLf5jsE0PPFzRiKjtcdw= +github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8= +github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA= +github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g= +github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= github.com/digitorus/pkcs7 v0.0.0-20230713084857-e76b763bdc49/go.mod h1:SKVExuS+vpu2l9IoOc0RwqE7NYnb0JlcFHFnEJkVDzc= github.com/digitorus/pkcs7 v0.0.0-20230818184609-3a137a874352 h1:ge14PCmCvPjpMQMIAH7uKg0lrtNSOdpYsRXlwk3QbaE= github.com/digitorus/pkcs7 v0.0.0-20230818184609-3a137a874352/go.mod h1:SKVExuS+vpu2l9IoOc0RwqE7NYnb0JlcFHFnEJkVDzc= @@ -210,6 +222,10 @@ github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= +github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +github.com/foxcpp/go-mockdns v1.1.0 h1:jI0rD8M0wuYAxL7r/ynTrCQQq0BVqfB99Vgk7DlmewI= +github.com/foxcpp/go-mockdns v1.1.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= @@ -267,6 +283,8 @@ github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOW github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang/glog v1.2.0 h1:uCdmnmatrKCgMBlM4rMuJZWOkPDqdbZPnrMXDY4gI68= +github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -284,6 +302,8 @@ github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/certificate-transparency-go v1.1.8 h1:LGYKkgZF7satzgTak9R4yzfJXEeYVAjV6/EAEJOf1to= github.com/google/certificate-transparency-go v1.1.8/go.mod h1:bV/o8r0TBKRf1X//iiiSgWrvII4d7/8OiA+3vG26gI8= +github.com/google/flatbuffers v2.0.8+incompatible h1:ivUb1cGomAB101ZM1T0nOiWz9pSrTMoa9+EiY7igmkM= +github.com/google/flatbuffers v2.0.8+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 h1:0VpGH+cDhbDtdcweoyCVsF3fhN8kejK6rFe/2FFX2nU= github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49/go.mod h1:BkkQ4L1KS1xMt2aWSPStnn55ChGC0DPOn2FQYj+f25M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -387,6 +407,8 @@ github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxec github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM= +github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk= github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU= github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= @@ -443,6 +465,8 @@ github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQ github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= +github.com/package-url/packageurl-go v0.1.2 h1:0H2DQt6DHd/NeRlVwW4EZ4oEI6Bn40XlNPRqegcxuo4= +github.com/package-url/packageurl-go v0.1.2/go.mod h1:uQd4a7Rh3ZsVg5j0lNyAfyxIeGde9yrlhjF78GzeW0c= github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw= github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= @@ -594,8 +618,10 @@ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= 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.20.0 h1:DeFD0VgTZ+Cj6hxravYYZE2W4GlneVH81iAOPjZkzk8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.20.0/go.mod h1:GijYcYmNpX1KazD5JmWGsi4P7dDTTTnfv1UbGn84MnU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 h1:cl5P5/GIfFh4t6xyruOgJP5QiA1pw4fYYdv6nc6CBWw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0/go.mod h1:zgBdWWAu7oEEMC06MMKc5NLbA/1YDXV1sMpSqEeLQLg= +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/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU= go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= diff --git a/internal/oci/http.go b/internal/oci/http.go new file mode 100644 index 0000000..0c1b302 --- /dev/null +++ b/internal/oci/http.go @@ -0,0 +1,27 @@ +package oci + +import ( + "net/http" + + "github.com/hashicorp/go-cleanhttp" +) + +type userAgentTransporter struct { + ua string + rt http.RoundTripper +} + +type Option = func(*http.Client) + +func (u *userAgentTransporter) RoundTrip(req *http.Request) (*http.Response, error) { + req.Header.Set("User-Agent", u.ua) + + return u.rt.RoundTrip(req) +} + +func HttpTransport() http.RoundTripper { + return &userAgentTransporter{ + ua: "Docker-Client", + rt: cleanhttp.DefaultTransport(), + } +} diff --git a/internal/oci/oci.go b/internal/oci/oci.go new file mode 100644 index 0000000..89af78c --- /dev/null +++ b/internal/oci/oci.go @@ -0,0 +1,395 @@ +package oci + +import ( + "context" + "encoding/json" + "fmt" + "strings" + + "github.com/containerd/containerd/platforms" + "github.com/distribution/reference" + att "github.com/docker/attest/pkg/attestation" + "github.com/google/go-containerregistry/pkg/authn" + "github.com/google/go-containerregistry/pkg/name" + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/go-containerregistry/pkg/v1/layout" + "github.com/google/go-containerregistry/pkg/v1/remote" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/package-url/packageurl-go" + "github.com/pkg/errors" +) + +// parsePlatform parses the provided platform string or attempts to obtain +// the platform of the current host system +func parsePlatform(platformStr string) (*v1.Platform, error) { + if platformStr == "" { + cdp := platforms.Normalize(platforms.DefaultSpec()) + if cdp.OS != "windows" { + cdp.OS = "linux" + } + return &v1.Platform{ + OS: cdp.OS, + Architecture: cdp.Architecture, + Variant: cdp.Variant, + }, nil + } else { + return v1.ParsePlatform(platformStr) + } +} + +func attestationManifestFromOCILayout(path string, platformStr string) (*AttestationManifest, error) { + idx, err := layout.ImageIndexFromPath(path) + if err != nil { + return nil, fmt.Errorf("failed to load image index: %w", err) + } + + idxm, err := idx.IndexManifest() + if err != nil { + return nil, fmt.Errorf("failed to get digest: %w", err) + } + + idxDescriptor := idxm.Manifests[0] + name := idxDescriptor.Annotations["org.opencontainers.image.ref.name"] + idxDigest := idxDescriptor.Digest + + mfs, err := idx.ImageIndex(idxDigest) + if err != nil { + return nil, fmt.Errorf("failed to extract ImageIndex for digest %s: %w", idxDigest.String(), err) + } + mfs2, err := mfs.IndexManifest() + if err != nil { + return nil, fmt.Errorf("failed to extract IndexManifest from ImageIndex: %w", err) + } + platform, err := parsePlatform(platformStr) + if err != nil { + return nil, fmt.Errorf("failed to parse platform: %w", err) + } + var imageDigest string + for _, mf := range mfs2.Manifests { + if mf.Platform.Equals(*platform) { + imageDigest = mf.Digest.String() + } + } + for _, mf := range mfs2.Manifests { + if mf.Annotations[DockerReferenceType] != AttestationManifestType { + continue + } + + if mf.Annotations[DockerReferenceDigest] != imageDigest { + continue + } + + attestationImage, err := mfs.Image(mf.Digest) + if err != nil { + return nil, fmt.Errorf("failed to extract attestation image with digest %s: %w", mf.Digest.String(), err) + } + manifest, err := attestationImage.Manifest() + if err != nil { + return nil, fmt.Errorf("failed to get manifest: %w", err) + } + attest := &AttestationManifest{ + Name: name, + Image: attestationImage, + Manifest: manifest, + Descriptor: &mf, + Digest: imageDigest, + Platform: platform, + } + return attest, nil + } + return nil, errors.New("attestation manifest not found") + +} + +// implementation of AttestationResolver that closes over attestations from an oci layout +type OCILayoutResolver struct { + Path string + Platform string + *AttestationManifest +} + +func (r *OCILayoutResolver) ImagePlatformStr() string { + return r.Platform +} +func (r *OCILayoutResolver) fetchAttestationManifest() (*AttestationManifest, error) { + if r.AttestationManifest == nil { + m, err := attestationManifestFromOCILayout(r.Path, r.Platform) + if err != nil { + return nil, fmt.Errorf("failed to get attestation manifest: %w", err) + } + r.AttestationManifest = m + } + return r.AttestationManifest, nil +} + +func (r *OCILayoutResolver) Attestations(ctx context.Context, predicateType string) ([]*att.Envelope, error) { + if r.AttestationManifest == nil { + _, err := r.fetchAttestationManifest() + if err != nil { + return nil, fmt.Errorf("failed to get attestation manifest: %w", err) + } + } + attestationImage := r.AttestationManifest.Image + layers, err := attestationImage.Layers() + if err != nil { + return nil, fmt.Errorf("failed to extract layers from attestation image: %w", err) + } + var envs []*att.Envelope + manifest := r.AttestationManifest.Manifest + for i, l := range manifest.Layers { + if l.Annotations[InTotoPredicateType] != predicateType { + continue + } + layer := layers[i] + mt, err := layer.MediaType() + if err != nil { + return nil, fmt.Errorf("failed to get layer media type: %w", err) + } + mts := string(mt) + if !strings.HasSuffix(mts, "+dsse") { + continue + } + var env = new(att.Envelope) + // parse layer blob as json + r, err := layer.Uncompressed() + + if err != nil { + return nil, fmt.Errorf("failed to get layer contents: %w", err) + } + defer r.Close() + err = json.NewDecoder(r).Decode(env) + if err != nil { + return nil, fmt.Errorf("failed to decode envelope: %w", err) + } + envs = append(envs, env) + } + return envs, nil +} + +func (r *OCILayoutResolver) ImageName(ctx context.Context) (string, error) { + if r.AttestationManifest == nil { + _, err := r.fetchAttestationManifest() + if err != nil { + return "", fmt.Errorf("failed to get attestation manifest: %w", err) + } + } + + return r.Name, nil +} + +func (r *OCILayoutResolver) ImageDigest(ctx context.Context) (string, error) { + if r.AttestationManifest == nil { + _, err := r.fetchAttestationManifest() + if err != nil { + return "", fmt.Errorf("failed to get attestation manifest: %w", err) + } + } + return r.Digest, nil +} + +type RegistryResolver struct { + Image string + Platform string + *AttestationManifest +} + +func (r *RegistryResolver) ImageName(ctx context.Context) (string, error) { + return r.Image, nil +} + +func (r *RegistryResolver) ImagePlatformStr() string { + return r.Platform +} + +func (r *RegistryResolver) ImageDigest(ctx context.Context) (string, error) { + if r.AttestationManifest == nil { + attest, err := FetchAttestationManifest(ctx, r.Image, r.Platform) + if err != nil { + return "", fmt.Errorf("failed to get attestation manifest: %w", err) + } + r.AttestationManifest = attest + } + return r.Digest, nil +} + +func (r *RegistryResolver) Attestations(ctx context.Context, predicateType string) ([]*att.Envelope, error) { + if r.AttestationManifest == nil { + attest, err := FetchAttestationManifest(ctx, r.Image, r.Platform) + if err != nil { + return nil, fmt.Errorf("failed to get attestation manifest: %w", err) + } + r.AttestationManifest = attest + } + return ExtractEnvelopes(r.AttestationManifest, predicateType) +} + +func FetchAttestationManifest(ctx context.Context, image, platformStr string) (*AttestationManifest, error) { + platform, err := parsePlatform(platformStr) + if err != nil { + return nil, fmt.Errorf("failed to parse platform %s: %w", platform, err) + } + + // we want to get to the image index, so ignoring platform for now + options := withOptions(ctx, nil) + ref, err := name.ParseReference(image) + if err != nil { + return nil, fmt.Errorf("failed to parse reference: %w", err) + } + + desc, err := remote.Index(ref, options...) + if err != nil { + return nil, fmt.Errorf("failed to obtain index manifest: %w", err) + } + ix, err := desc.IndexManifest() + if err != nil { + return nil, fmt.Errorf("failed to obtain index manifest: %w", err) + } + digest, err := imageDigestForPlatform(ix, platform) + if err != nil { + return nil, fmt.Errorf("failed to obtain image for platform: %w", err) + } + ref, err = name.ParseReference(fmt.Sprintf("%s@%s", ref.Context().Name(), digest)) + if err != nil { + return nil, fmt.Errorf("failed to parse attestation reference: %w", err) + } + + attestationDigest, err := attestationDigestForDigest(ix, digest, "attestation-manifest") + if err != nil { + return nil, fmt.Errorf("failed to obtain attestation for image: %w", err) + } + ref, err = name.ParseReference(fmt.Sprintf("%s@%s", ref.Context().Name(), attestationDigest)) + if err != nil { + return nil, fmt.Errorf("failed to parse attestation reference: %w", err) + } + remoteDescriptor, err := remote.Get(ref, options...) + if err != nil { + return nil, fmt.Errorf("failed to get attestation: %w", err) + } + manifest := new(v1.Manifest) + err = json.Unmarshal(remoteDescriptor.Manifest, manifest) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal attestation: %w", err) + } + attestationImage, err := remoteDescriptor.Image() + if err != nil { + return nil, fmt.Errorf("failed to get attestation image: %w", err) + } + attest := &AttestationManifest{ + Name: image, + Image: attestationImage, + Manifest: manifest, + Descriptor: &remoteDescriptor.Descriptor, + Digest: digest, + Platform: platform, + } + return attest, nil +} + +func withOptions(ctx context.Context, platform *v1.Platform) []remote.Option { + // prepare options + options := []remote.Option{remote.WithAuthFromKeychain(authn.DefaultKeychain), remote.WithTransport(HttpTransport()), remote.WithContext(ctx)} + + // add in platform into remote Get operation; this might conflict with an explicit digest, but we are trying anyway + if platform != nil { + options = append(options, remote.WithPlatform(*platform)) + } + return options +} + +func ExtractEnvelopes(ia *AttestationManifest, predicateType string) ([]*att.Envelope, error) { + manifest := ia.Manifest + im := ia.Image + + var envs []*att.Envelope + + ls, err := im.Layers() + if err != nil { + return nil, fmt.Errorf("failed to get layers: %w", err) + } + for i, l := range manifest.Layers { + if (strings.HasPrefix(string(l.MediaType), "application/vnd.in-toto.")) && + strings.HasSuffix(string(l.MediaType), "+dsse") && + l.Annotations[InTotoPredicateType] == predicateType { + reader, err := ls[i].Uncompressed() + if err != nil { + return nil, fmt.Errorf("failed to get layer contents: %w", err) + } + defer reader.Close() + var env = new(att.Envelope) + err = json.NewDecoder(reader).Decode(&env) + if err != nil { + return nil, fmt.Errorf("failed to decode envelope: %w", err) + } + envs = append(envs, env) + } + } + + return envs, nil +} + +func imageDigestForPlatform(ix *v1.IndexManifest, platform *v1.Platform) (string, error) { + for _, m := range ix.Manifests { + if m.MediaType == ocispec.MediaTypeImageManifest || m.MediaType == "application/vnd.docker.distribution.manifest.v2+json" && m.Platform.Equals(*platform) { + return m.Digest.String(), nil + } + } + return "", errors.New(fmt.Sprintf("no image found for platform %v", platform)) +} + +func attestationDigestForDigest(ix *v1.IndexManifest, imageDigest string, attestType string) (string, error) { + for _, m := range ix.Manifests { + if v, ok := m.Annotations["vnd.docker.reference.type"]; ok && v == attestType { + if d, ok := m.Annotations["vnd.docker.reference.digest"]; ok && d == imageDigest { + return m.Digest.String(), nil + } + } + } + return "", errors.New(fmt.Sprintf("no attestation found for image %s", imageDigest)) +} + +func RefToPURL(ref string, platform string) (string, bool, error) { + var isCanonical bool + named, err := reference.ParseNormalizedNamed(ref) + if err != nil { + return "", false, fmt.Errorf("failed to parse ref %q: %w", ref, err) + } + var qualifiers []packageurl.Qualifier + + if canonical, ok := named.(reference.Canonical); ok { + qualifiers = append(qualifiers, packageurl.Qualifier{ + Key: "digest", + Value: canonical.Digest().String(), + }) + isCanonical = true + } else { + named = reference.TagNameOnly(named) + } + + version := "" + if tagged, ok := named.(reference.Tagged); ok { + version = tagged.Tag() + } + + name := reference.FamiliarName(named) + + ns := "" + parts := strings.Split(name, "/") + if len(parts) > 1 { + ns = strings.Join(parts[:len(parts)-1], "/") + } + name = parts[len(parts)-1] + + pf, err := parsePlatform(platform) + if err != nil { + return "", false, fmt.Errorf("failed to parse platform %q: %w", platform, err) + } + if pf != nil { + qualifiers = append(qualifiers, packageurl.Qualifier{ + Key: "platform", + Value: pf.String(), + }) + } + + p := packageurl.NewPackageURL("docker", ns, name, version, qualifiers, "") + return p.ToString(), isCanonical, nil +} diff --git a/internal/oci/oci_test.go b/internal/oci/oci_test.go new file mode 100644 index 0000000..7392af2 --- /dev/null +++ b/internal/oci/oci_test.go @@ -0,0 +1,49 @@ +package oci + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestRefToPurl(t *testing.T) { + purl, canonical, err := RefToPURL("alpine", "arm64/linux") + assert.NoError(t, err) + assert.Equal(t, "pkg:docker/alpine@latest?platform=arm64%2Flinux", purl) + assert.False(t, canonical) + + purl, canonical, err = RefToPURL("alpine:123", "arm64/linux") + assert.NoError(t, err) + assert.Equal(t, "pkg:docker/alpine@123?platform=arm64%2Flinux", purl) + assert.False(t, canonical) + + purl, canonical, err = RefToPURL("google/alpine:123", "arm64/linux") + assert.NoError(t, err) + assert.Equal(t, "pkg:docker/google/alpine@123?platform=arm64%2Flinux", purl) + assert.False(t, canonical) + + purl, canonical, err = RefToPURL("library/alpine:123", "arm64/linux") + assert.NoError(t, err) + assert.Equal(t, "pkg:docker/alpine@123?platform=arm64%2Flinux", purl) + assert.False(t, canonical) + + purl, canonical, err = RefToPURL("docker.io/library/alpine:123", "arm64/linux") + assert.NoError(t, err) + assert.Equal(t, "pkg:docker/alpine@123?platform=arm64%2Flinux", purl) + assert.False(t, canonical) + + purl, canonical, err = RefToPURL("localhost:5001/library/alpine:123", "arm64/linux") + assert.NoError(t, err) + assert.Equal(t, "pkg:docker/localhost%3A5001/library/alpine@123?platform=arm64%2Flinux", purl) + assert.False(t, canonical) + + purl, canonical, err = RefToPURL("localhost:5001/alpine:123", "arm64/linux") + assert.NoError(t, err) + assert.Equal(t, "pkg:docker/localhost%3A5001/alpine@123?platform=arm64%2Flinux", purl) + assert.False(t, canonical) + + purl, canonical, err = RefToPURL("localhost:5001/alpine@sha256:c5b1261d6d3e43071626931fc004f70149baeba2c8ec672bd4f27761f8e1ad6b", "arm64/linux") + assert.NoError(t, err) + assert.Equal(t, "pkg:docker/localhost%3A5001/alpine?digest=sha256%3Ac5b1261d6d3e43071626931fc004f70149baeba2c8ec672bd4f27761f8e1ad6b&platform=arm64%2Flinux", purl) + assert.True(t, canonical) +} diff --git a/internal/oci/resolver.go b/internal/oci/resolver.go new file mode 100644 index 0000000..8dfd7d9 --- /dev/null +++ b/internal/oci/resolver.go @@ -0,0 +1,46 @@ +package oci + +import ( + "context" + + att "github.com/docker/attest/pkg/attestation" + v1 "github.com/google/go-containerregistry/pkg/v1" +) + +type AttestationManifest struct { + // attestation image details + Image v1.Image + Manifest *v1.Manifest + Descriptor *v1.Descriptor + // details of subect image + Name string + Digest string + Platform *v1.Platform +} + +type AttestationResolver interface { + ImageName(ctx context.Context) (string, error) + ImagePlatformStr() string + ImageDigest(ctx context.Context) (string, error) + Attestations(ctx context.Context, mediaType string) ([]*att.Envelope, error) +} + +type MockResolver struct { + Envs []*att.Envelope +} + +func (r MockResolver) Attestations(ctx context.Context, mediaType string) ([]*att.Envelope, error) { + return r.Envs, nil +} + +func (r MockResolver) ImageName(ctx context.Context) (string, error) { + return "library/alpine:latest", nil +} + +func (r MockResolver) ImageDigest(ctx context.Context) (string, error) { + return "sha256:test-digest", nil +} + +func (r MockResolver) ImagePlatformStr() string { + return "linux/amd64" +} diff --git a/internal/oci/types.go b/internal/oci/types.go new file mode 100644 index 0000000..5af15eb --- /dev/null +++ b/internal/oci/types.go @@ -0,0 +1,8 @@ +package oci + +const ( + DockerReferenceType = "vnd.docker.reference.type" + DockerReferenceDigest = "vnd.docker.reference.digest" + AttestationManifestType = "attestation-manifest" + InTotoPredicateType = "in-toto.io/predicate-type" +) diff --git a/internal/test/test.go b/internal/test/test.go index dadd5b8..b149693 100644 --- a/internal/test/test.go +++ b/internal/test/test.go @@ -1,8 +1,24 @@ package test import ( + "context" "os" "testing" + + "github.com/docker/attest/internal/oci" + "github.com/docker/attest/pkg/policy" + "github.com/docker/attest/pkg/signerverifier" + "github.com/docker/attest/pkg/tlog" + "github.com/secure-systems-lab/go-securesystemslib/dsse" +) + +const ( + USE_MOCK_TL = true + USE_MOCK_KMS = true + USE_MOCK_POLICY = true + + AwsRegion = "us-east-1" + AwsKmsKeyArn = "arn:aws:kms:us-east-1:175142243308:alias/doi-signing" // sandbox ) func CreateTempDir(t *testing.T, dir, pattern string) string { @@ -20,3 +36,51 @@ func CreateTempDir(t *testing.T, dir, pattern string) string { }) return tempDir } + +func Setup(t *testing.T) (context.Context, dsse.SignerVerifier) { + var tl tlog.TL + if USE_MOCK_TL { + tl = tlog.GetMockTL() + } else { + tl = &tlog.RekorTL{} + } + + ctx := tlog.WithTL(context.Background(), tl) + + var policyEvaluator policy.PolicyEvaluator + if USE_MOCK_POLICY { + policyEvaluator = GetMockPolicy() + } else { + policyEvaluator = policy.NewRegoEvaluator(true) + } + + ctx = policy.WithPolicyEvaluator(ctx, policyEvaluator) + + var signer dsse.SignerVerifier + var err error + if USE_MOCK_KMS { + signer, err = GetMockSigner(ctx) + if err != nil { + t.Fatal(err) + } + } else { + signer, err = signerverifier.GetAWSSigner(ctx, AwsKmsKeyArn, AwsRegion) + if err != nil { + t.Fatal(err) + } + } + + return ctx, signer +} + +func GetMockSigner(ctx context.Context) (dsse.SignerVerifier, error) { + return signerverifier.GenKeyPair() +} + +func GetMockPolicy() policy.PolicyEvaluator { + return &policy.MockPolicyEvaluator{ + EvaluateFunc: func(ctx context.Context, resolver oci.AttestationResolver, policy []*policy.PolicyFile, input *policy.PolicyInput) error { + return nil + }, + } +} diff --git a/internal/util/strings.go b/internal/util/strings.go new file mode 100644 index 0000000..b0320b9 --- /dev/null +++ b/internal/util/strings.go @@ -0,0 +1,10 @@ +package util + +func StringInSlice(str string, list []string) bool { + for _, v := range list { + if v == str { + return true + } + } + return false +} diff --git a/pkg/attestation/sign.go b/pkg/attestation/sign.go new file mode 100644 index 0000000..1af1a9d --- /dev/null +++ b/pkg/attestation/sign.go @@ -0,0 +1,62 @@ +package attestation + +import ( + "context" + "fmt" + + "github.com/docker/attest/internal/util" + "github.com/docker/attest/pkg/tlog" + "github.com/secure-systems-lab/go-securesystemslib/dsse" +) + +// SignDSSE signs a payload with a given signer and uploads the signature to the transparency log +func SignDSSE(ctx context.Context, payload []byte, payloadType string, signer dsse.SignerVerifier) (*Envelope, error) { + t := tlog.GetTL(ctx) + + env := new(Envelope) + env.Payload = base64Encoding.EncodeToString(payload) + env.PayloadType = payloadType + encPayload := dsse.PAE(payloadType, payload) + + // statement message digest + hash := util.S256(encPayload) + + // sign message digest + sig, err := signer.Sign(ctx, hash) + if err != nil { + return nil, fmt.Errorf("error signing attestation: %w", err) + } + + // get Key ID from signer + keyId, err := signer.KeyID() + if err != nil { + return nil, fmt.Errorf("error getting public key ID: %w", err) + } + + // upload to TL + entry, err := t.UploadLogEntry(ctx, keyId, encPayload, sig, signer) + if err != nil { + return nil, fmt.Errorf("error uploading TL entry: %w", err) + } + entryObj, err := t.UnmarshalEntry(entry) + if err != nil { + return nil, fmt.Errorf("error unmarshaling tl entry: %w", err) + } + + // add signature w/ tl extension to dsse envelope + env.Signatures = append(env.Signatures, Signature{ + KeyID: keyId, + Sig: base64Encoding.EncodeToString(sig), + Extension: Extension{ + Kind: DockerDsseExtKind, + Ext: DockerDsseExtension{ + Tl: DockerTlExtension{ + Kind: RekorTlExtKind, + Data: entryObj, // transparency log entry metadata + }, + }, + }, + }) + + return env, nil +} diff --git a/pkg/attestation/sign_test.go b/pkg/attestation/sign_test.go new file mode 100644 index 0000000..3af09df --- /dev/null +++ b/pkg/attestation/sign_test.go @@ -0,0 +1,147 @@ +package attestation_test + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "encoding/json" + "fmt" + "testing" + "time" + + "github.com/docker/attest/internal/test" + "github.com/docker/attest/pkg/attestation" + "github.com/docker/attest/pkg/signerverifier" + intoto "github.com/in-toto/in-toto-golang/in_toto" + "github.com/stretchr/testify/assert" +) + +func TestSignVerifyAttestation(t *testing.T) { + ctx, signer := test.Setup(t) + stmt := &intoto.Statement{ + StatementHeader: intoto.StatementHeader{ + Type: intoto.StatementInTotoV01, + PredicateType: intoto.PredicateSPDX, + }, + Predicate: "test", + } + + payload, err := json.Marshal(stmt) + assert.NoError(t, err) + + env, err := attestation.SignDSSE(ctx, payload, intoto.PayloadType, signer) + assert.NoError(t, err) + + // marshal envelope to json to test for bugs when marshaling envelope data + serializedEnv, err := json.Marshal(env) + assert.NoError(t, err) + deserializedEnv := new(attestation.Envelope) + err = json.Unmarshal(serializedEnv, deserializedEnv) + assert.NoError(t, err) + + // signer.Public() calls AWS API when using AWS signer, use attestation.GetPublicVerificationKey() to get key from TUF repo + // signer.Public() used here for test purposes + ecPub, ok := signer.Public().(*ecdsa.PublicKey) + assert.True(t, ok) + pem, err := signerverifier.ToPEM(ecPub) + assert.NoError(t, err) + keyId, err := signerverifier.KeyID(ecPub) + assert.NoError(t, err) + + badKeyPriv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + assert.NoError(t, err) + badKey := &badKeyPriv.PublicKey + badPEM, err := signerverifier.ToPEM(badKey) + assert.NoError(t, err) + + testCases := []struct { + name string + keyId string + pem []byte + distrust bool + from time.Time + to *time.Time + status string + expectedError string + }{ + { + name: "all OK", + keyId: keyId, + pem: pem, + distrust: false, + from: time.Time{}, + to: nil, + status: "active", + expectedError: "", + }, + { + name: "key not found", + keyId: "someotherkey", + pem: pem, + distrust: false, + from: time.Time{}, + to: nil, + status: "active", + expectedError: fmt.Sprintf("key not found: %s", keyId), + }, + { + name: "key distrusted", + keyId: keyId, + pem: pem, + distrust: true, + from: time.Time{}, + to: nil, + status: "active", + expectedError: "distrusted", + }, + { + name: "key not yet valid", + keyId: keyId, + pem: pem, + distrust: false, + from: time.Now().Add(time.Hour), + to: nil, + status: "active", + expectedError: "not yet valid", + }, + { + name: "key already revoked", + keyId: keyId, + pem: pem, + distrust: false, + from: time.Time{}, + to: new(time.Time), + status: "revoked", + expectedError: "already revoked", + }, + { + name: "bad key", + keyId: keyId, + pem: badPEM, + distrust: false, + from: time.Time{}, + to: nil, + status: "active", + expectedError: "signature is not valid", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + keyMeta := attestation.KeyMetadata{ + ID: tc.keyId, + PEM: string(tc.pem), + Distrust: tc.distrust, + From: tc.from, + To: tc.to, + Status: tc.status, + } + _, err = attestation.VerifyDSSE(ctx, deserializedEnv, attestation.KeysMap{tc.keyId: keyMeta}) + if tc.expectedError != "" { + assert.Contains(t, err.Error(), tc.expectedError) + } else { + assert.NoError(t, err) + } + }) + } +} diff --git a/pkg/attestation/types.go b/pkg/attestation/types.go new file mode 100644 index 0000000..7292e8d --- /dev/null +++ b/pkg/attestation/types.go @@ -0,0 +1,35 @@ +package attestation + +import "encoding/base64" + +const ( + DockerDsseExtKind = "application/vnd.docker.attestation-verification.v1+json" + RekorTlExtKind = "Rekor" +) + +var base64Encoding = base64.StdEncoding.Strict() + +// the following types are needed until https://github.com/secure-systems-lab/dsse/pull/61 is merged +type Envelope struct { + PayloadType string `json:"payloadType"` + Payload string `json:"payload"` + Signatures []Signature `json:"signatures"` +} +type Signature struct { + KeyID string `json:"keyid"` + Sig string `json:"sig"` + Extension Extension `json:"extension"` +} +type Extension struct { + Kind string `json:"kind"` + Ext DockerDsseExtension `json:"ext"` +} + +type DockerDsseExtension struct { + Tl DockerTlExtension `json:"tl"` +} + +type DockerTlExtension struct { + Kind string `json:"kind"` + Data any `json:"data"` +} diff --git a/pkg/attestation/verify.go b/pkg/attestation/verify.go new file mode 100644 index 0000000..bb3ffce --- /dev/null +++ b/pkg/attestation/verify.go @@ -0,0 +1,134 @@ +package attestation + +import ( + "context" + "crypto/ecdsa" + "crypto/x509" + "encoding/base64" + "encoding/json" + "fmt" + "time" + + "github.com/docker/attest/internal/util" + "github.com/docker/attest/pkg/signerverifier" + "github.com/docker/attest/pkg/tlog" + intoto "github.com/in-toto/in-toto-golang/in_toto" + ociv1 "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/secure-systems-lab/go-securesystemslib/dsse" +) + +type KeyMetadata struct { + ID string `json:"id"` + PEM string `json:"key"` + From time.Time `json:"from"` + To *time.Time `json:"to"` + Status string `json:"status"` + SigningFormat string `json:"signing-format"` + Distrust bool `json:"distrust,omitempty"` +} + +type Keys []KeyMetadata +type KeysMap map[string]KeyMetadata + +func VerifyDSSE(ctx context.Context, env *Envelope, keys KeysMap) ([]byte, error) { + // enforce payload type + if !ValidPayloadType(env.PayloadType) { + return nil, fmt.Errorf("unsupported payload type %s", env.PayloadType) + } + + if len(env.Signatures) == 0 { + return nil, fmt.Errorf("no signatures found") + } + + payload, err := base64Encoding.DecodeString(env.Payload) + if err != nil { + return nil, fmt.Errorf("error failed to decode payload: %w", err) + } + + encPayload := dsse.PAE(env.PayloadType, payload) + + // verify signatures and transparency log entry + for _, sig := range env.Signatures { + err := verifySignature(ctx, sig, encPayload, keys) + if err != nil { + return nil, err + } + } + + return payload, nil +} + +func verifySignature(ctx context.Context, sig Signature, payload []byte, keys KeysMap) error { + t := tlog.GetTL(ctx) + + if sig.Extension.Kind == "" { + return fmt.Errorf("error missing signature extension kind") + } + if sig.Extension.Kind != DockerDsseExtKind { + return fmt.Errorf("error unsupported signature extension kind: %s", sig.Extension.Kind) + } + + // verify TL entry + if sig.Extension.Ext.Tl.Kind != RekorTlExtKind { + return fmt.Errorf("error unsupported TL extension kind: %s", sig.Extension.Ext.Tl.Kind) + } + entry := sig.Extension.Ext.Tl.Data + entryBytes, err := json.Marshal(entry) + if err != nil { + return fmt.Errorf("failed to marshal TL entry: %w", err) + } + + integratedTime, err := t.VerifyLogEntry(ctx, entryBytes) + if err != nil { + return fmt.Errorf("TL entry failed verification: %w", err) + } + + keyMeta, ok := keys[sig.KeyID] + if !ok { + return fmt.Errorf("error key not found: %s", sig.KeyID) + } + + if keyMeta.Distrust { + return fmt.Errorf("key %s is distrusted", keyMeta.ID) + } + + if integratedTime.Before(keyMeta.From) { + return fmt.Errorf("key %s was not yet valid at TL log time %s (key valid from %s)", keyMeta.ID, integratedTime, keyMeta.From) + } + + if keyMeta.To != nil && !integratedTime.Before(*keyMeta.To) { + return fmt.Errorf("key %s was already %s at TL log time %s (key %s at %s)", keyMeta.ID, keyMeta.Status, integratedTime, keyMeta.Status, *keyMeta.To) + } + + // TODO: this is unmarshalling with MarshalPKIXPublicKey only for us to marshal it again + publicKey, err := signerverifier.Parse([]byte(keyMeta.PEM)) + if err != nil { + return fmt.Errorf("failed to parse public key: %w", err) + } + + // verify TL entry payload + encodedPub, err := x509.MarshalPKIXPublicKey(publicKey) + if err != nil { + return fmt.Errorf("error failed to marshal public key: %w", err) + } + err = t.VerifyEntryPayload(entryBytes, payload, encodedPub) + if err != nil { + return fmt.Errorf("TL entry failed payload verification: %w", err) + } + // decode signature + signature, err := base64.StdEncoding.Strict().DecodeString(sig.Sig) + if err != nil { + return fmt.Errorf("error failed to decode signature: %w", err) + } + // verify payload ecdsa signature + ok = ecdsa.VerifyASN1(publicKey, util.S256(payload), signature) + if !ok { + return fmt.Errorf("payload signature is not valid") + } + + return nil +} + +func ValidPayloadType(payloadType string) bool { + return payloadType == intoto.PayloadType || payloadType == ociv1.MediaTypeDescriptor +} diff --git a/pkg/attestation/verify_test.go b/pkg/attestation/verify_test.go new file mode 100644 index 0000000..7878523 --- /dev/null +++ b/pkg/attestation/verify_test.go @@ -0,0 +1,46 @@ +package attestation_test + +import ( + "encoding/base64" + "testing" + + "github.com/docker/attest/internal/test" + "github.com/docker/attest/pkg/attestation" + intoto "github.com/in-toto/in-toto-golang/in_toto" + ociv1 "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/stretchr/testify/assert" +) + +func TestValidPayloadType(t *testing.T) { + testCases := []struct { + name string + payloadType string + expected bool + }{ + {"valid in-toto payload type", intoto.PayloadType, true}, + {"valid oci descriptor payload type", ociv1.MediaTypeDescriptor, true}, + {"invalid payload type", "application/vnd.test.fail", false}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + assert.Equalf(t, tc.expected, attestation.ValidPayloadType(tc.payloadType), "expected %v for payload type %s", tc.expected, tc.payloadType) + }) + } +} + +func TestVerifyUnsignedAttestation(t *testing.T) { + ctx, _ := test.Setup(t) + + payload := []byte("payload") + env := &attestation.Envelope{ + // no signatures + Signatures: []attestation.Signature{}, + Payload: base64.StdEncoding.EncodeToString(payload), + PayloadType: intoto.PayloadType, + } + + _, err := attestation.VerifyDSSE(ctx, env, attestation.KeysMap{}) + assert.Error(t, err) + assert.Contains(t, err.Error(), "no signatures") +} diff --git a/pkg/mirror/metadata_test.go b/pkg/mirror/metadata_test.go index 2083a67..2f9ed6c 100644 --- a/pkg/mirror/metadata_test.go +++ b/pkg/mirror/metadata_test.go @@ -16,7 +16,7 @@ import ( ) func TestGetTufMetadataMirror(t *testing.T) { - server := httptest.NewServer(http.FileServer(http.Dir(filepath.Join("..", "..", "internal", "test", "testdata", "test-repo")))) + server := httptest.NewServer(http.FileServer(http.Dir(filepath.Join("..", "..", "test", "testdata", "tuf", "test-repo")))) defer server.Close() path := test.CreateTempDir(t, "", "tuf_temp") @@ -34,7 +34,7 @@ func TestGetTufMetadataMirror(t *testing.T) { } func TestGetMetadataManifest(t *testing.T) { - server := httptest.NewServer(http.FileServer(http.Dir(filepath.Join("..", "..", "internal", "test", "testdata", "test-repo")))) + server := httptest.NewServer(http.FileServer(http.Dir(filepath.Join("..", "..", "test", "testdata", "tuf", "test-repo")))) defer server.Close() path := test.CreateTempDir(t, "", "tuf_temp") @@ -74,7 +74,7 @@ func TestGetMetadataManifest(t *testing.T) { } func TestGetDelegatedMetadataMirrors(t *testing.T) { - server := httptest.NewServer(http.FileServer(http.Dir(filepath.Join("..", "..", "internal", "test", "testdata", "test-repo")))) + server := httptest.NewServer(http.FileServer(http.Dir(filepath.Join("..", "..", "test", "testdata", "tuf", "test-repo")))) defer server.Close() path := test.CreateTempDir(t, "", "tuf_temp") diff --git a/pkg/mirror/targets_test.go b/pkg/mirror/targets_test.go index 5ab41df..117063a 100644 --- a/pkg/mirror/targets_test.go +++ b/pkg/mirror/targets_test.go @@ -22,7 +22,7 @@ type Layers struct { } func TestGetTufTargetsMirror(t *testing.T) { - server := httptest.NewServer(http.FileServer(http.Dir(filepath.Join("..", "..", "internal", "test", "testdata", "test-repo")))) + server := httptest.NewServer(http.FileServer(http.Dir(filepath.Join("..", "..", "test", "testdata", "tuf", "test-repo")))) defer server.Close() path := test.CreateTempDir(t, "", "tuf_temp") @@ -56,7 +56,7 @@ func TestGetTufTargetsMirror(t *testing.T) { } func TestTargetDelegationMetadata(t *testing.T) { - server := httptest.NewServer(http.FileServer(http.Dir(filepath.Join("..", "..", "internal", "test", "testdata", "test-repo")))) + server := httptest.NewServer(http.FileServer(http.Dir(filepath.Join("..", "..", "test", "testdata", "tuf", "test-repo")))) defer server.Close() path := test.CreateTempDir(t, "", "tuf_temp") @@ -69,7 +69,7 @@ func TestTargetDelegationMetadata(t *testing.T) { } func TestGetDelegatedTargetMirrors(t *testing.T) { - server := httptest.NewServer(http.FileServer(http.Dir(filepath.Join("..", "..", "internal", "test", "testdata", "test-repo")))) + server := httptest.NewServer(http.FileServer(http.Dir(filepath.Join("..", "..", "test", "testdata", "tuf", "test-repo")))) defer server.Close() path := test.CreateTempDir(t, "", "tuf_temp") diff --git a/pkg/policy/evaluator.go b/pkg/policy/evaluator.go new file mode 100644 index 0000000..e4ada6b --- /dev/null +++ b/pkg/policy/evaluator.go @@ -0,0 +1,41 @@ +package policy + +import ( + "context" + "fmt" + + "github.com/docker/attest/internal/oci" +) + +type policyEvaluatorCtxKeyType struct{} + +var PolicyEvaluatorCtxKey policyEvaluatorCtxKeyType + +// sets PolicyEvaluator in context +func WithPolicyEvaluator(ctx context.Context, pe PolicyEvaluator) context.Context { + return context.WithValue(ctx, PolicyEvaluatorCtxKey, pe) +} + +// gets PolicyEvaluator from context, defaults to Rego PolicyEvaluator if not set +func GetPolicyEvaluator(ctx context.Context) (PolicyEvaluator, error) { + t, ok := ctx.Value(PolicyEvaluatorCtxKey).(PolicyEvaluator) + if !ok { + return nil, fmt.Errorf("no policy evaluator client set on context (set one with policy.WithPolicyEvaluator)") + } + return t, nil +} + +type PolicyEvaluator interface { + Evaluate(ctx context.Context, resolver oci.AttestationResolver, policy []*PolicyFile, input *PolicyInput) error +} + +type MockPolicyEvaluator struct { + EvaluateFunc func(ctx context.Context, resolver oci.AttestationResolver, policy []*PolicyFile, input *PolicyInput) error +} + +func (pe *MockPolicyEvaluator) Evaluate(ctx context.Context, resolver oci.AttestationResolver, policy []*PolicyFile, input *PolicyInput) error { + if pe.EvaluateFunc != nil { + return pe.EvaluateFunc(ctx, resolver, policy, input) + } + return nil +} diff --git a/pkg/policy/policy.go b/pkg/policy/policy.go new file mode 100644 index 0000000..c408aa6 --- /dev/null +++ b/pkg/policy/policy.go @@ -0,0 +1,203 @@ +package policy + +import ( + "context" + "fmt" + "os" + "path" + "path/filepath" + "strings" + + "github.com/distribution/reference" + "github.com/docker/attest/internal/oci" + "github.com/docker/attest/internal/util" + "github.com/docker/attest/pkg/tuf" + + goyaml "gopkg.in/yaml.v3" +) + +const ( + PolicyMappingFileName = "mapping.yaml" +) + +var ( + PolicyFileNames = []string{"data.yaml", "policy.rego"} +) + +type PolicyMappings struct { + Version string `json:"version"` + Kind string `json:"kind"` + Policies []PolicyMapping `json:"policies"` + Mirrors []PolicyMirror `json:"mirrors"` +} + +type PolicyMapping struct { + Name string `json:"namespace"` + Location string `json:"location"` + Description string `json:"description"` + Origin PolicyOrigin `json:"origin"` +} + +type PolicyMirror struct { + Name string `json:"name"` + Mirror MirrorSpec `json:"mirror"` +} + +type MirrorSpec struct { + Domains []string `json:"domains"` + Prefix string `json:"prefix"` +} + +type PolicyOrigin struct { + Name string `json:"name"` + Prefix string `json:"prefix"` + Domain string `json:"domain"` +} + +type PolicyOptions struct { + TufClient tuf.TUFClient + LocalTargetsDir string + LocalPolicyDir string +} + +type PolicyInput struct { + Digest string `json:"digest"` + Purl string `json:"purl"` + IsCanonical bool `json:"isCanonical"` +} + +type PolicyFile struct { + Path string + Content []byte +} + +func resolveLocalPolicy(opts *PolicyOptions, mapping *PolicyMapping) ([]*PolicyFile, error) { + if opts.LocalPolicyDir == "" { + return nil, fmt.Errorf("local policy dir not set") + } + files := make([]*PolicyFile, 0, len(PolicyFileNames)) + for _, filename := range PolicyFileNames { + filePath := path.Join(opts.LocalPolicyDir, mapping.Location, filename) + fileContents, err := os.ReadFile(filePath) + if err != nil { + return nil, fmt.Errorf("failed to read policy file %s: %w", filename, err) + } + files = append(files, &PolicyFile{ + Path: filename, + Content: fileContents, + }) + } + return files, nil +} + +func loadLocalMappings(opts *PolicyOptions) (*PolicyMappings, error) { + if opts.LocalPolicyDir == "" { + return nil, nil + } + mappings := &PolicyMappings{} + path := path.Join(opts.LocalPolicyDir, PolicyMappingFileName) + mappingFile, err := os.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("failed to read policy mapping file %s: %w", path, err) + } + err = goyaml.Unmarshal(mappingFile, mappings) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal policy mapping file %s: %w", path, err) + } + return mappings, nil +} + +func resolveTufPolicy(opts *PolicyOptions, mapping *PolicyMapping) ([]*PolicyFile, error) { + files := make([]*PolicyFile, 0, len(PolicyFileNames)) + for _, filename := range PolicyFileNames { + filePath := path.Join(mapping.Location, filename) + _, fileContents, err := opts.TufClient.DownloadTarget(filePath, filepath.Join(opts.LocalTargetsDir, filePath)) + if err != nil { + return nil, fmt.Errorf("failed to download policy file %s: %w", filename, err) + } + files = append(files, &PolicyFile{ + Path: filename, + Content: fileContents, + }) + } + return files, nil +} + +func loadTufMappings(tufClient tuf.TUFClient, localTargetsDir string) (*PolicyMappings, error) { + filename := PolicyMappingFileName + _, fileContents, err := tufClient.DownloadTarget(filename, filepath.Join(localTargetsDir, filename)) + if err != nil { + return nil, fmt.Errorf("failed to download policy file %s: %w", filename, err) + } + mappings := &PolicyMappings{} + + err = goyaml.Unmarshal(fileContents, mappings) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal policy mapping file %s: %w", filename, err) + } + return mappings, nil +} + +func findPolicyMatch(named reference.Named, mappings *PolicyMappings) (*PolicyMapping, *PolicyMirror) { + if mappings != nil { + for _, mapping := range mappings.Policies { + if mapping.Origin.Domain == reference.Domain(named) && + strings.HasPrefix(reference.Path(named), mapping.Origin.Prefix) { + return &mapping, nil + } + } + // now search mirrors + for _, mirror := range mappings.Mirrors { + if util.StringInSlice(reference.Domain(named), mirror.Mirror.Domains) && + strings.HasPrefix(reference.Path(named), mirror.Mirror.Prefix) { + for _, mapping := range mappings.Policies { + if mapping.Name == mirror.Name { + return &mapping, nil + } + } + return nil, &mirror + } + } + } + return nil, nil +} + +func ResolvePolicy(ctx context.Context, resolver oci.AttestationResolver, opts *PolicyOptions) ([]*PolicyFile, error) { + imageName, err := resolver.ImageName(ctx) + if err != nil { + return nil, fmt.Errorf("failed to get image name: %w", err) + } + named, err := reference.ParseNormalizedNamed(imageName) + if err != nil { + return nil, fmt.Errorf("failed to parse image name: %w", err) + } + localMappings, err := loadLocalMappings(opts) + if err != nil { + return nil, fmt.Errorf("failed to load local policy mappings: %w", err) + } + mapping, mirror := findPolicyMatch(named, localMappings) + if mapping != nil { + return resolveLocalPolicy(opts, mapping) + } + // must check tuf + tufMappings, err := loadTufMappings(opts.TufClient, opts.LocalTargetsDir) + if err != nil { + return nil, fmt.Errorf("failed to load tuf policy mappings: %w", err) + } + + // it's a mirror of a tuf policy + if mirror != nil { + for _, mapping := range tufMappings.Policies { + if mapping.Name == mirror.Name { + return resolveTufPolicy(opts, &mapping) + } + } + } + + // try to resolve a tuf policy directly + mapping, _ = findPolicyMatch(named, tufMappings) + if mapping == nil { + return nil, nil + } + return resolveTufPolicy(opts, mapping) +} diff --git a/pkg/policy/policy_test.go b/pkg/policy/policy_test.go new file mode 100644 index 0000000..21205da --- /dev/null +++ b/pkg/policy/policy_test.go @@ -0,0 +1,110 @@ +package policy_test + +import ( + "encoding/json" + "os" + "path/filepath" + "testing" + + "github.com/docker/attest/internal/oci" + "github.com/docker/attest/internal/test" + "github.com/docker/attest/pkg/attestation" + "github.com/docker/attest/pkg/policy" + "github.com/docker/attest/pkg/tuf" + "github.com/stretchr/testify/assert" +) + +func loadAttestation(t *testing.T, path string) *attestation.Envelope { + ex, err := os.ReadFile(path) + if err != nil { + t.Fatal(err) + } + + var env = new(attestation.Envelope) + err = json.Unmarshal(ex, env) + if err != nil { + t.Fatal(err) + } + return env +} + +func TestRegoEvaluator_Evaluate(t *testing.T) { + ctx, _ := test.Setup(t) + + TestDataPath := filepath.Join("..", "..", "test", "testdata") + MockTufRepo := filepath.Join(TestDataPath, "local-policy") + ExampleAttestation := filepath.Join(TestDataPath, "example_attestation.json") + VSA := filepath.Join(TestDataPath, "vsa.json") + + re := policy.NewRegoEvaluator(true) + + defaultInput := &policy.PolicyInput{ + Digest: "sha256:test-digest", + Purl: "test-purl", + IsCanonical: true, + } + + defaultResolver := oci.MockResolver{ + Envs: []*attestation.Envelope{loadAttestation(t, ExampleAttestation)}, + } + + vsaResolver := oci.MockResolver{ + Envs: []*attestation.Envelope{loadAttestation(t, ExampleAttestation), loadAttestation(t, VSA)}, + } + + testCases := []struct { + repo string + expectSuccess bool + input *policy.PolicyInput + resolver oci.AttestationResolver + policy *policy.PolicyOptions + }{ + {repo: "testdata/mock-tuf-allow", expectSuccess: true, input: defaultInput, resolver: defaultResolver}, + {repo: "testdata/mock-tuf-deny", expectSuccess: false, input: defaultInput, resolver: defaultResolver}, + {repo: "testdata/mock-tuf-verify-sig", expectSuccess: true, input: defaultInput, resolver: defaultResolver}, + {repo: "testdata/mock-tuf-wrong-key", expectSuccess: false, input: defaultInput, resolver: defaultResolver}, + {repo: MockTufRepo, expectSuccess: true, input: &policy.PolicyInput{ + Digest: "sha256:da8b190665956ea07890a0273e2a9c96bfe291662f08e2860e868eef69c34620", + Purl: "pkg:docker/test-image@test?platform=linux%2Famd64", + IsCanonical: true, + }, resolver: vsaResolver}, + {repo: MockTufRepo, expectSuccess: true, input: &policy.PolicyInput{ + Digest: "sha256:da8b190665956ea07890a0273e2a9c96bfe291662f08e2860e868eef69c34620", + Purl: "pkg:docker/test-image@test?platform=linux%2Famd64", + IsCanonical: false, + }, resolver: vsaResolver}, + // not a doi + {repo: MockTufRepo, expectSuccess: false, input: defaultInput, resolver: vsaResolver, policy: &policy.PolicyOptions{ + LocalPolicyDir: "testdata/mock-tuf-deny", + }}, + // digest mismatch + {repo: MockTufRepo, expectSuccess: false, input: &policy.PolicyInput{ + Digest: "sha256:test-digest-wrong", + Purl: "test-purl", + IsCanonical: false, + }, resolver: vsaResolver}, + } + + for _, tc := range testCases { + t.Run(tc.repo, func(t *testing.T) { + tufClient := tuf.NewMockTufClient(tc.repo, test.CreateTempDir(t, "", "tuf-dest")) + if tc.policy == nil { + tc.policy = &policy.PolicyOptions{ + TufClient: tufClient, + LocalTargetsDir: test.CreateTempDir(t, "", "tuf-targets"), + } + } + + policyFiles, err := policy.ResolvePolicy(ctx, tc.resolver, tc.policy) + assert.NoErrorf(t, err, "failed to resolve policy") + err = re.Evaluate(ctx, tc.resolver, policyFiles, tc.input) + + if tc.expectSuccess { + assert.NoErrorf(t, err, "Evaluate failed") + } else { + assert.Errorf(t, err, "Evaluate should have failed") + } + }) + } + +} diff --git a/pkg/policy/rego.go b/pkg/policy/rego.go new file mode 100644 index 0000000..b6d5c8a --- /dev/null +++ b/pkg/policy/rego.go @@ -0,0 +1,225 @@ +package policy + +import ( + "context" + "encoding/json" + "fmt" + "os" + "path/filepath" + + "github.com/docker/attest/internal/oci" + att "github.com/docker/attest/pkg/attestation" + intoto "github.com/in-toto/in-toto-golang/in_toto" + "github.com/open-policy-agent/opa/ast" + "github.com/open-policy-agent/opa/rego" + "github.com/open-policy-agent/opa/storage" + "github.com/open-policy-agent/opa/storage/inmem" + "github.com/open-policy-agent/opa/tester" + "github.com/open-policy-agent/opa/topdown" + "github.com/open-policy-agent/opa/types" + opa "github.com/open-policy-agent/opa/util" + "sigs.k8s.io/yaml" +) + +type regoEvaluator struct { + debug bool +} + +func NewRegoEvaluator(debug bool) PolicyEvaluator { + return ®oEvaluator{ + debug: debug, + } +} + +func (re *regoEvaluator) Evaluate(ctx context.Context, resolver oci.AttestationResolver, files []*PolicyFile, input *PolicyInput) error { + var regoOpts []func(*rego.Rego) + + // Create a new in-memory store + store := inmem.New() + params := storage.TransactionParams{} + params.Write = true + txn, err := store.NewTransaction(ctx, params) + if err != nil { + return err + } + + for _, target := range files { + // load yaml as data (no rego opt for this!?) + if filepath.Ext(target.Path) == ".yaml" { + yamlData, err := loadYAML(target.Path, target.Content) + if err != nil { + return err + } + err = store.Write(ctx, txn, storage.AddOp, storage.Path{}, yamlData) + if err != nil { + return err + } + } else { + regoOpts = append(regoOpts, rego.Module(target.Path, string(target.Content))) + } + } + + err = store.Commit(ctx, txn) + if err != nil { + store.Abort(ctx, txn) + return err + } + + if re.debug { + regoOpts = append(regoOpts, + rego.EnablePrintStatements(true), + rego.PrintHook(topdown.NewPrintHook(os.Stderr)), + rego.Dump(os.Stderr), + ) + } + + regoOpts = append(regoOpts, + rego.Query("data.docker.allow"), + rego.StrictBuiltinErrors(true), + rego.Input(input), + rego.Store(store), + ) + for _, custom := range RegoFunctions(resolver) { + regoOpts = append(regoOpts, custom.Func) + } + + r := rego.New(regoOpts...) + rs, err := r.Eval(ctx) + if err != nil { + return fmt.Errorf("error from Eval: %w", err) + } + + if !rs.Allowed() { + return fmt.Errorf("policy evaluation failed") + } + + return nil +} + +var dynamicObj = types.NewObject(nil, types.NewDynamicProperty(types.S, types.A)) +var arrayObj = types.NewArray(nil, dynamicObj) +var verifyDecl = &ast.Builtin{ + Name: "attestations.verify_envelope", + Decl: types.NewFunction(types.Args(dynamicObj, arrayObj), dynamicObj), + Nondeterministic: true, +} +var attestDecl = &ast.Builtin{ + Name: "attestations.attestation", + Decl: types.NewFunction(types.Args(types.S), dynamicObj), + Nondeterministic: true, +} + +func RegoFunctions(resolver oci.AttestationResolver) []*tester.Builtin { + return []*tester.Builtin{ + { + Decl: verifyDecl, + Func: rego.Function2( + ®o.Function{ + Name: verifyDecl.Name, + Decl: verifyDecl.Decl, + Memoize: true, + Nondeterministic: verifyDecl.Nondeterministic, + }, + verifyIntotoEnvelope), + }, + { + Decl: attestDecl, + Func: rego.Function1( + ®o.Function{ + Name: attestDecl.Name, + Decl: attestDecl.Decl, + Memoize: true, + Nondeterministic: attestDecl.Nondeterministic, + }, + fetchIntotoAttestations(resolver)), + }, + } +} + +func fetchIntotoAttestations(resolver oci.AttestationResolver) func(rego.BuiltinContext, *ast.Term) (*ast.Term, error) { + return func(rCtx rego.BuiltinContext, predicateTypeTerm *ast.Term) (*ast.Term, error) { + predicateTypeStr, ok := predicateTypeTerm.Value.(ast.String) + if !ok { + return nil, fmt.Errorf("predicateTypeTerm is not a string") + } + predicateType := string(predicateTypeStr) + + envelopes, err := resolver.Attestations(rCtx.Context, predicateType) + if err != nil { + return nil, err + } + + // Convert each envelope to an ast.Value. + values := make([]*ast.Term, len(envelopes)) + for i, envelope := range envelopes { + value, err := ast.InterfaceToValue(envelope) + if err != nil { + return nil, err + } + values[i] = ast.NewTerm(value) + } + + // Wrap the values in an ast.Array and convert it to an ast.Term. + array := ast.NewTerm(ast.NewArray(values...)) + + return array, nil + } +} +func verifyIntotoEnvelope(rCtx rego.BuiltinContext, envTerm, keysTerm *ast.Term) (*ast.Term, error) { + env := new(att.Envelope) + var keys att.Keys + err := ast.As(envTerm.Value, env) + if err != nil { + return nil, fmt.Errorf("failed to cast envelope: %w", err) + } + err = ast.As(keysTerm.Value, &keys) + if err != nil { + return nil, fmt.Errorf("failed to cast keys: %w", err) + } + keysmap := make(map[string]att.KeyMetadata, len(keys)) + for _, key := range keys { + keysmap[key.ID] = key + } + payload, err := att.VerifyDSSE(rCtx.Context, env, keysmap) + if err != nil { + return nil, err + } + + statement := new(intoto.Statement) + + switch env.PayloadType { + case intoto.PayloadType: + err = json.Unmarshal(payload, statement) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal statement: %w", err) + } + // TODO: implement other types of envelope + default: + return nil, fmt.Errorf("unsupported payload type: %s", env.PayloadType) + } + + value, err := ast.InterfaceToValue(statement) + if err != nil { + return nil, err + } + + return ast.NewTerm(value), nil +} + +// copied from opa +func loadYAML(path string, bs []byte) (interface{}, error) { + bs, err := yaml.YAMLToJSON(bs) + if err != nil { + return nil, fmt.Errorf("%v: error converting YAML to JSON: %v", path, err) + } + return loadJSON(path, bs) +} + +func loadJSON(path string, bs []byte) (interface{}, error) { + var x interface{} + err := opa.UnmarshalJSON(bs, &x) + if err != nil { + return nil, fmt.Errorf("%s: %w", path, err) + } + return x, nil +} diff --git a/pkg/policy/testdata/mock-tuf-allow/doi/data.yaml b/pkg/policy/testdata/mock-tuf-allow/doi/data.yaml new file mode 100644 index 0000000..1e2812b --- /dev/null +++ b/pkg/policy/testdata/mock-tuf-allow/doi/data.yaml @@ -0,0 +1 @@ +config: diff --git a/pkg/policy/testdata/mock-tuf-allow/doi/policy.rego b/pkg/policy/testdata/mock-tuf-allow/doi/policy.rego new file mode 100644 index 0000000..5e3b8ab --- /dev/null +++ b/pkg/policy/testdata/mock-tuf-allow/doi/policy.rego @@ -0,0 +1,5 @@ +package docker + +import rego.v1 + +allow := true diff --git a/pkg/policy/testdata/mock-tuf-allow/mapping.yaml b/pkg/policy/testdata/mock-tuf-allow/mapping.yaml new file mode 100644 index 0000000..e637a9c --- /dev/null +++ b/pkg/policy/testdata/mock-tuf-allow/mapping.yaml @@ -0,0 +1,10 @@ +# map repos to policies +version: v1 +kind: policy-mapping +policies: + - origin: + domain: docker.io + prefix: library/ + name: docker-official-images + description: Docker Official Images + location: doi diff --git a/pkg/policy/testdata/mock-tuf-deny/doi/data.yaml b/pkg/policy/testdata/mock-tuf-deny/doi/data.yaml new file mode 100644 index 0000000..1e2812b --- /dev/null +++ b/pkg/policy/testdata/mock-tuf-deny/doi/data.yaml @@ -0,0 +1 @@ +config: diff --git a/pkg/policy/testdata/mock-tuf-deny/doi/policy.rego b/pkg/policy/testdata/mock-tuf-deny/doi/policy.rego new file mode 100644 index 0000000..ad73140 --- /dev/null +++ b/pkg/policy/testdata/mock-tuf-deny/doi/policy.rego @@ -0,0 +1,5 @@ +package docker + +import rego.v1 + +allow := false diff --git a/pkg/policy/testdata/mock-tuf-deny/mapping.yaml b/pkg/policy/testdata/mock-tuf-deny/mapping.yaml new file mode 100644 index 0000000..e637a9c --- /dev/null +++ b/pkg/policy/testdata/mock-tuf-deny/mapping.yaml @@ -0,0 +1,10 @@ +# map repos to policies +version: v1 +kind: policy-mapping +policies: + - origin: + domain: docker.io + prefix: library/ + name: docker-official-images + description: Docker Official Images + location: doi diff --git a/pkg/policy/testdata/mock-tuf-verify-sig/doi/data.yaml b/pkg/policy/testdata/mock-tuf-verify-sig/doi/data.yaml new file mode 100644 index 0000000..1e2812b --- /dev/null +++ b/pkg/policy/testdata/mock-tuf-verify-sig/doi/data.yaml @@ -0,0 +1 @@ +config: diff --git a/pkg/policy/testdata/mock-tuf-verify-sig/doi/policy.rego b/pkg/policy/testdata/mock-tuf-verify-sig/doi/policy.rego new file mode 100644 index 0000000..e2456af --- /dev/null +++ b/pkg/policy/testdata/mock-tuf-verify-sig/doi/policy.rego @@ -0,0 +1,15 @@ +package docker + +import rego.v1 + +keys := [{ + "id": "a0c296026645799b2a297913878e81b0aefff2a0c301e97232f717e14402f3e4", + "key": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEgH23D1i2+ZIOtVjmfB7iFvX8AhVN\n9CPJ4ie9axw+WRHozGnRy99U2dRge3zueBBg2MweF0zrToXGig2v3YOrdw==\n-----END PUBLIC KEY-----", + "from": "2023-12-15T14:00:00Z", + "to": null +}] + +allow if { + some env in attestations.attestation("foo") + statement := attestations.verify_envelope(env, keys) +} diff --git a/pkg/policy/testdata/mock-tuf-verify-sig/mapping.yaml b/pkg/policy/testdata/mock-tuf-verify-sig/mapping.yaml new file mode 100644 index 0000000..e637a9c --- /dev/null +++ b/pkg/policy/testdata/mock-tuf-verify-sig/mapping.yaml @@ -0,0 +1,10 @@ +# map repos to policies +version: v1 +kind: policy-mapping +policies: + - origin: + domain: docker.io + prefix: library/ + name: docker-official-images + description: Docker Official Images + location: doi diff --git a/pkg/policy/testdata/mock-tuf-wrong-key/doi/data.yaml b/pkg/policy/testdata/mock-tuf-wrong-key/doi/data.yaml new file mode 100644 index 0000000..1e2812b --- /dev/null +++ b/pkg/policy/testdata/mock-tuf-wrong-key/doi/data.yaml @@ -0,0 +1 @@ +config: diff --git a/pkg/policy/testdata/mock-tuf-wrong-key/doi/policy.rego b/pkg/policy/testdata/mock-tuf-wrong-key/doi/policy.rego new file mode 100644 index 0000000..131129a --- /dev/null +++ b/pkg/policy/testdata/mock-tuf-wrong-key/doi/policy.rego @@ -0,0 +1,19 @@ +package docker + +import rego.v1 + +keys := { + "a0c296026645799b2a297913878e81b0aefff2a0c301e97232f717e14402f3e4": { + "id": "a0c296026645799b2a297913878e81b0aefff2a0c301e97232f717e14402f3e4", + "key": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEHyZpSgzvqFqNv7f3x7865OS38rAb\nQMcff55zM2UH/KR3Pr84a8QsGDNgaNGzJQJWjtMSgfV8WnNoffNK+svFNg==\n-----END PUBLIC KEY-----", + "from": "2023-12-15T14:00:00Z", + "to": null, + } +} + +allow if { + some env in attestations.attestation("foo") + statement := attestations.verify_envelope(env, keys) +} + +allow := true diff --git a/pkg/policy/testdata/mock-tuf-wrong-key/mapping.yaml b/pkg/policy/testdata/mock-tuf-wrong-key/mapping.yaml new file mode 100644 index 0000000..e637a9c --- /dev/null +++ b/pkg/policy/testdata/mock-tuf-wrong-key/mapping.yaml @@ -0,0 +1,10 @@ +# map repos to policies +version: v1 +kind: policy-mapping +policies: + - origin: + domain: docker.io + prefix: library/ + name: docker-official-images + description: Docker Official Images + location: doi diff --git a/pkg/tuf/registry_test.go b/pkg/tuf/registry_test.go index 662ba72..2ab176a 100644 --- a/pkg/tuf/registry_test.go +++ b/pkg/tuf/registry_test.go @@ -10,7 +10,6 @@ import ( "testing" "github.com/docker/attest/internal/embed" - "github.com/docker/attest/internal/test" "github.com/docker/attest/internal/util" "github.com/google/go-containerregistry/pkg/authn" "github.com/google/go-containerregistry/pkg/crane" @@ -49,8 +48,8 @@ func TestRegistryFetcher(t *testing.T) { targetsRepo := regAddr.Host + "/tuf-targets" targetFile := "test.txt" delegatedRole := "test-role" - dir := test.CreateTempDir(t, "", "tuf_temp") - delegatedDir := test.CreateTempDir(t, dir, delegatedRole) + dir := CreateTempDir(t, "", "tuf_temp") + delegatedDir := CreateTempDir(t, dir, delegatedRole) delegatedTargetFile := fmt.Sprintf("%s/%s", delegatedRole, targetFile) cfg, err := config.New(metadataRepo, embed.DevRoot) diff --git a/pkg/tuf/tuf_test.go b/pkg/tuf/tuf_test.go index dd3eba4..dc71a67 100644 --- a/pkg/tuf/tuf_test.go +++ b/pkg/tuf/tuf_test.go @@ -4,24 +4,40 @@ import ( "context" "net/http" "net/http/httptest" + "os" "path/filepath" "testing" "github.com/docker/attest/internal/embed" - "github.com/docker/attest/internal/test" "github.com/stretchr/testify/assert" ) var ( - HttpTufTestDataPath = filepath.Join("..", "..", "internal", "test", "testdata", "test-repo") - OciTufTestDataPath = filepath.Join("..", "..", "internal", "test", "testdata", "test-repo-oci") + HttpTufTestDataPath = filepath.Join("..", "..", "test", "testdata", "tuf", "test-repo") + OciTufTestDataPath = filepath.Join("..", "..", "test", "testdata", "tuf", "test-repo-oci") ) +func CreateTempDir(t *testing.T, dir, pattern string) string { + // Create a temporary directory for output oci layout + tempDir, err := os.MkdirTemp(dir, pattern) + if err != nil { + t.Fatalf("Failed to create temp directory: %v", err) + } + + // Register a cleanup function to delete the temp directory when the test exits + t.Cleanup(func() { + if err := os.RemoveAll(tempDir); err != nil { + t.Errorf("Failed to remove temp directory: %v", err) + } + }) + return tempDir +} + // NewTufClient creates a new TUF client func TestRootInit(t *testing.T) { - tufPath := test.CreateTempDir(t, "", "tuf_temp") + tufPath := CreateTempDir(t, "", "tuf_temp") - // Start a test HTTP server to serve data from ./testdata/test-repo/ paths + // Start a test HTTP server to serve data from /test/testdata/tuf/test-repo/ paths server := httptest.NewServer(http.FileServer(http.Dir(HttpTufTestDataPath))) defer server.Close() diff --git a/test/testdata/example_attestation.json b/test/testdata/example_attestation.json new file mode 100644 index 0000000..c0bb69f --- /dev/null +++ b/test/testdata/example_attestation.json @@ -0,0 +1,48 @@ +{ + "payloadType": "application/vnd.in-toto+json", + "payload": "eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL3Nsc2EuZGV2L3Byb3ZlbmFuY2UvdjAuMiIsInN1YmplY3QiOlt7Im5hbWUiOiJwa2c6ZG9ja2VyL3Rlc3QtaW1hZ2VAdGVzdD9wbGF0Zm9ybT1saW51eCUyRmFybTY0IiwiZGlnZXN0Ijp7InNoYTI1NiI6IjdhNzZjZWM5NDM4NTNmOWY3MTA1YjE5NzZhZmExYmY3Y2Q1YmI2YWZjNGU5ZDU4NTJkZDhkYTdjZjgxYWU4NmUifX1dLCJwcmVkaWNhdGUiOnsiYnVpbGRDb25maWciOnsiZGlnZXN0TWFwcGluZyI6eyJzaGEyNTY6OWVmN2JlYWVhMDQ3MzA5MTg1MDQ3NzY0ZTNhYTBkNGYyOTk3OWRhY2M4OGU1NzNkYWE5ZDBmODIwNTBiNGFiMSI6InN0ZXAwIiwic2hhMjU2OmMzNTI5ZDU5YzE2NGM0YmM2ZmQ5NzQ4MjAyMGEyMTQ3ZmEyMGI5OTFkNDg5MTYzMTVkODA1MDQ2NzNjODcxYmYiOiJzdGVwMiIsInNoYTI1NjpjOTljZjdlY2FjZDE0OGQ2NTNmY2FjYjUxYzJlYWRmNjg1MDRhNGQzMmM4NzBmNmRlMjY3MDY2MzViMDhkZjI4Ijoic3RlcDEiLCJzaGEyNTY6ZjlhY2RlYzI0MjczODYwYWI1NzNlMDhhNWU3NWE2Y2I4YzRiYWVmNjYwMjU3MWRmNTk5YjNkOWI3NDViN2IwZiI6InN0ZXAzIn0sImxsYkRlZmluaXRpb24iOlt7ImlkIjoic3RlcDAiLCJvcCI6eyJPcCI6eyJzb3VyY2UiOnsiaWRlbnRpZmllciI6ImRvY2tlci1pbWFnZTovL2RvY2tlci5pby9saWJyYXJ5L2FscGluZTpsYXRlc3RAc2hhMjU2OmM1YjEyNjFkNmQzZTQzMDcxNjI2OTMxZmMwMDRmNzAxNDliYWViYTJjOGVjNjcyYmQ0ZjI3NzYxZjhlMWFkNmIifX0sImNvbnN0cmFpbnRzIjp7fSwicGxhdGZvcm0iOnsiQXJjaGl0ZWN0dXJlIjoiYXJtNjQiLCJPUyI6ImxpbnV4In19fSx7ImlkIjoic3RlcDEiLCJpbnB1dHMiOlsic3RlcDA6MCJdLCJvcCI6eyJPcCI6eyJleGVjIjp7Im1ldGEiOnsiYXJncyI6WyIvYmluL3NoIiwiLWMiLCJlY2hvIFwiaGVsbG8gd29ybGRcIiBcdTAwM2UgL3RtcC9oZWxsby50eHQiXSwiY3dkIjoiLyIsImVudiI6WyJQQVRIPS91c3IvbG9jYWwvc2JpbjovdXNyL2xvY2FsL2JpbjovdXNyL3NiaW46L3Vzci9iaW46L3NiaW46L2JpbiJdLCJyZW1vdmVNb3VudFN0dWJzUmVjdXJzaXZlIjp0cnVlfSwibW91bnRzIjpbeyJkZXN0IjoiLyIsImlucHV0IjowLCJvdXRwdXQiOjB9XX19LCJjb25zdHJhaW50cyI6e30sInBsYXRmb3JtIjp7IkFyY2hpdGVjdHVyZSI6ImFybTY0IiwiT1MiOiJsaW51eCJ9fX0seyJpZCI6InN0ZXAyIiwiaW5wdXRzIjpbInN0ZXAxOjAiXSwib3AiOnsiT3AiOnsiZmlsZSI6eyJhY3Rpb25zIjpbeyJBY3Rpb24iOnsiY29weSI6eyJhbGxvd0VtcHR5V2lsZGNhcmQiOnRydWUsImFsbG93V2lsZGNhcmQiOnRydWUsImNyZWF0ZURlc3RQYXRoIjp0cnVlLCJkZXN0IjoiLyIsImRpckNvcHlDb250ZW50cyI6dHJ1ZSwiZm9sbG93U3ltbGluayI6dHJ1ZSwibW9kZSI6LTEsInNyYyI6Ii90bXAvaGVsbG8udHh0IiwidGltZXN0YW1wIjotMX19LCJpbnB1dCI6LTEsIm91dHB1dCI6MCwic2Vjb25kYXJ5SW5wdXQiOjB9XX19LCJjb25zdHJhaW50cyI6e319fSx7ImlkIjoic3RlcDMiLCJpbnB1dHMiOlsic3RlcDI6MCJdLCJvcCI6eyJPcCI6bnVsbH19XX0sImJ1aWxkVHlwZSI6Imh0dHBzOi8vbW9ieXByb2plY3Qub3JnL2J1aWxka2l0QHYxIiwiYnVpbGRlciI6eyJpZCI6IiJ9LCJpbnZvY2F0aW9uIjp7ImNvbmZpZ1NvdXJjZSI6eyJlbnRyeVBvaW50IjoiRG9ja2VyZmlsZSJ9LCJlbnZpcm9ubWVudCI6eyJwbGF0Zm9ybSI6ImxpbnV4L2FtZDY0In0sInBhcmFtZXRlcnMiOnsiZnJvbnRlbmQiOiJkb2NrZXJmaWxlLnYwIiwibG9jYWxzIjpbeyJuYW1lIjoiY29udGV4dCJ9LHsibmFtZSI6ImRvY2tlcmZpbGUifV19fSwibWF0ZXJpYWxzIjpbeyJkaWdlc3QiOnsic2hhMjU2IjoiMTc2ZTA4NjljMzhhZWFlZGUzN2U1OTRmY2YxODJjOTFkNDQzOTFhOTMyZTFkNzFlOTllYzIwNDg3MzQ0NWEzMyJ9LCJ1cmkiOiJwa2c6ZG9ja2VyL2RvY2tlci9idWlsZGtpdC1zeWZ0LXNjYW5uZXJAc3RhYmxlLTEifSx7ImRpZ2VzdCI6eyJzaGEyNTYiOiJjNWIxMjYxZDZkM2U0MzA3MTYyNjkzMWZjMDA0ZjcwMTQ5YmFlYmEyYzhlYzY3MmJkNGYyNzc2MWY4ZTFhZDZiIn0sInVyaSI6InBrZzpkb2NrZXIvYWxwaW5lQGxhdGVzdD9wbGF0Zm9ybT1saW51eCUyRmFybTY0In1dLCJtZXRhZGF0YSI6eyJidWlsZEZpbmlzaGVkT24iOiIyMDI0LTA0LTE5VDE2OjI1OjA1LjgzMTQ0MDc2M1oiLCJidWlsZEludm9jYXRpb25JRCI6Im91ZnloZDBhY2l3NzE0bTFpNXk2bDA5eHoiLCJidWlsZFN0YXJ0ZWRPbiI6IjIwMjQtMDQtMTlUMTY6MjU6MDQuMjA5MTExNjU2WiIsImNvbXBsZXRlbmVzcyI6eyJlbnZpcm9ubWVudCI6dHJ1ZSwibWF0ZXJpYWxzIjpmYWxzZSwicGFyYW1ldGVycyI6dHJ1ZX0sImh0dHBzOi8vbW9ieXByb2plY3Qub3JnL2J1aWxka2l0QHYxI21ldGFkYXRhIjp7ImxheWVycyI6eyJzdGVwMDowIjpbW3siZGlnZXN0Ijoic2hhMjU2OmJjYTQyOTBhOTYzOTBkN2E2ZmM2ZjJmOTkyOTM3MGQwNmY4ZGZjYWNiYTU5MWM3NmUzZDVjNTA0NGU3ZjQyMGMiLCJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQuZG9ja2VyLmltYWdlLnJvb3Rmcy5kaWZmLnRhci5nemlwIiwic2l6ZSI6MzM0NzcxNX1dXSwic3RlcDI6MCI6W1t7ImRpZ2VzdCI6InNoYTI1Njo5N2E1NDhmOGQ2NWQ5YWI2MTdmNjA4ZGQ2MjFmNTllMGQ0M2EzYjM0NmYzNGMzNGViNThkYTMxZjAwYTliMGFkIiwibWVkaWFUeXBlIjoiYXBwbGljYXRpb24vdm5kLm9jaS5pbWFnZS5sYXllci52MS50YXIrZ3ppcCIsInNpemUiOjExNn1dXX0sInNvdXJjZSI6eyJpbmZvcyI6W3siZGF0YSI6IlJsSlBUU0JoYkhCcGJtVWdRVk1nWW5WcGJHUUtVbFZPSUdWamFHOGdJbWhsYkd4dklIZHZjbXhrSWlBK0lDOTBiWEF2YUdWc2JHOHVkSGgwQ2dwR1VrOU5JSE5qY21GMFkyZ0tRMDlRV1NBdExXWnliMjA5WW5WcGJHUWdMM1J0Y0M5b1pXeHNieTUwZUhRZ0x3bz0iLCJkaWdlc3RNYXBwaW5nIjp7InNoYTI1NjoxOGIxNjZiNTk0ZGY1MjcwYmY2YTRhYThkY2QwYTk2YjM2MzVkMGEyMzEwNmQ4MTk2MjBjYmFiMmYzZjVjZjIyIjoic3RlcDAiLCJzaGEyNTY6MzFkNmNiYmEzZjQzN2NmYjU5MzNlNjBkNWY0ZDJiZTY5MTA5YzJmYWU5ZWQ1ZmZlMDRkYTE2OTMxYTQ1OWNjYSI6InN0ZXAxIn0sImZpbGVuYW1lIjoiRG9ja2VyZmlsZSIsImxhbmd1YWdlIjoiRG9ja2VyZmlsZSIsImxsYkRlZmluaXRpb24iOlt7ImlkIjoic3RlcDAiLCJvcCI6eyJPcCI6eyJzb3VyY2UiOnsiYXR0cnMiOnsibG9jYWwuZGlmZmVyIjoibm9uZSIsImxvY2FsLmZvbGxvd3BhdGhzIjoiW1wiRG9ja2VyZmlsZVwiLFwiRG9ja2VyZmlsZS5kb2NrZXJpZ25vcmVcIixcImRvY2tlcmZpbGVcIl0iLCJsb2NhbC5zaGFyZWRrZXloaW50IjoiZG9ja2VyZmlsZSJ9LCJpZGVudGlmaWVyIjoibG9jYWw6Ly9kb2NrZXJmaWxlIn19LCJjb25zdHJhaW50cyI6e319fSx7ImlkIjoic3RlcDEiLCJpbnB1dHMiOlsic3RlcDA6MCJdLCJvcCI6eyJPcCI6bnVsbH19XX1dLCJsb2NhdGlvbnMiOnsic3RlcDAiOnsibG9jYXRpb25zIjpbeyJyYW5nZXMiOlt7ImVuZCI6eyJsaW5lIjoxfSwic3RhcnQiOnsibGluZSI6MX19XX1dfSwic3RlcDEiOnsibG9jYXRpb25zIjpbeyJyYW5nZXMiOlt7ImVuZCI6eyJsaW5lIjoyfSwic3RhcnQiOnsibGluZSI6Mn19XX1dfSwic3RlcDIiOnsibG9jYXRpb25zIjpbeyJyYW5nZXMiOlt7ImVuZCI6eyJsaW5lIjo1fSwic3RhcnQiOnsibGluZSI6NX19XX1dfX19LCJ2Y3MiOnsicmV2aXNpb24iOiI1OTE0ZGYyOTMwYWJhNzAxZDZkZGMzYmE5MjcyYTU3MzJkZmU4YWI0Iiwic291cmNlIjoiZ2l0QGdpdGh1Yi5jb206ZG9ja2VyL2ltYWdlLXNpZ25lci12ZXJpZmllci5naXQifX0sInJlcHJvZHVjaWJsZSI6ZmFsc2V9fX0=", + "signatures": [ + { + "keyid": "a0c296026645799b2a297913878e81b0aefff2a0c301e97232f717e14402f3e4", + "sig": "MEYCIQCC4QpW11nYK0twPWjNNueyyc9/bjWlMSyhiCcHF9WpSwIhALUdUTk70NJlC0IBGP9NHeP1lAqeemGeuYMLubzRSsVU", + "extension": { + "kind": "application/vnd.docker.attestation-verification.v1+json", + "ext": { + "tl": { + "kind": "Rekor", + "data": { + "body": "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiIxMjA3MjMwZTY0ZTk5OTYwMTFmY2MxOTkyZDcyYTZhZWUyNmViZjIxM2ZjNDVlN2RmYjc1MDExZjAwNjViZDVhIn19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FWUNJUUNDNFFwVzExbllLMHR3UFdqTk51ZXl5YzkvYmpXbE1TeWhpQ2NIRjlXcFN3SWhBTFVkVVRrNzBOSmxDMElCR1A5TkhlUDFsQXFlZW1HZXVZTUx1YnpSU3NWVSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVTkRWRU5EUVdFclowRjNTVUpCWjBsQ1FWUkJTMEpuWjNGb2EycFBVRkZSUkVGcVFreE5WV3QzVW5kWlJGWlJVVVJGTUVKb1RVZE5lVTlVV1hjS1RXcFpNazVFVlROUFZHeHBUVzFGZVU5VVl6Vk5WRTAwVG5wb2JFOUVSbWxOUjBac1dtMWFiVTF0UlhkWmVrMTNUVmRWTlU1NlNYcE5iVmt6VFZSa2JBcE5WRkV3VFVSS2JVMHlWVEJOUWpSWVJGUkpNRTFFVVhoUFZFVXlUV3BWZUUxc2IxaEVWRWt4VFVSUmVFOVVSVEpOYWxWNFRXeHZkMU42UmtwTlJXTkhDa0V4VlVWQmVFNUJXVlJDYWsxcWF6Sk5SRWt5VG1wUk1VNTZhelZaYWtwb1RXcHJNMDlVUlhwUFJHTTBXbFJuZUZscVFtaGFWMXB0V21wS2FFMUhUWG9LVFVSR2JFOVVZM2xOZWtwdFRucEZNMXBVUlRCT1JFRjVXbXBPYkU1RVFscE5RazFIUW5seFIxTk5ORGxCWjBWSFEwTnhSMU5OTkRsQmQwVklRVEJKUVFwQ1NVSTVkSGM1V1hSMmJWTkVjbFpaTlc1M1pUUm9ZakV2UVVsV1ZHWlJhbmxsU1c1MlYzTmpVR3hyVWpaTmVIQXdZM1ptVms1dVZWbElkRGczYm1kUkNsbE9hazFJYUdSTk5qQTJSbmh2YjA1eU9USkVjVE5sYW1kWlRYZG5XVUYzUkdkWlJGWlNNRkJCVVVndlFrRlJSRUZuWlVGTlFrMUhRVEZWWkVwUlVVMEtUVUZ2UjBORGMwZEJVVlZHUW5kTlJFMUJkMGRCTVZWa1JYZEZRaTkzVVVOTlFVRjNVM2RaUkZaU01GSkNSVkYzVVc5S1FWbFVRbXBOYW1zeVRVUkpNZ3BPYWxFeFRucHJOVmxxU21oTmFtc3pUMVJGZWs5RVl6UmFWR2Q0V1dwQ2FGcFhXbTFhYWtwb1RVZE5lazFFUm14UFZHTjVUWHBLYlU1NlJUTmFWRVV3Q2s1RVFYbGFhazVzVGtSQlMwSm5aM0ZvYTJwUFVGRlJSRUZuVGtsQlJFSkdRV2xGUVhaNmExcEZOa0pRV0hkUk1FcFpaRk1yZEhBeGJXRkNObmc0ZVVJS1pWZEZXbGx3TDBSaFVsY3pXbGhCUTBsSGNWRmhlblV4Vm10a2VGaEljakp2ZW5KWk5GRk1RMWRTYzNCalZVOTNOSGx0VGxoeEwydFpZbE5yQ2kwdExTMHRSVTVFSUVORlVsUkpSa2xEUVZSRkxTMHRMUzBLIn19fX0=", + "integratedTime": 1713543911, + "logID": "c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d", + "logIndex": 87076944, + "verification": { + "inclusionProof": { + "checkpoint": "rekor.sigstore.dev - 2605736670972794746\n82913514\nPPIDn2MHc7QqaxCf3XQK1aTwWybNmIRyQJx5RUUi8rs=\n\n— rekor.sigstore.dev wNI9ajBFAiBUfP8X07JxeluTYWxcN1osvsVMBtlyOCGvYbS4ezzPlAIhAIoYON/6d4ln87m885lhtQ59MBdNSSuA1GRPrwWfzakb\n", + "hashes": [ + "27bf9e39196ac51b75ed5e5c0197f312a0603e187792c837cf451aea54857a1b", + "c9d49521d3bf67cd8b3c20cf661625358a7e206a1a62e48e874dc8eac32f2fbf", + "7828aaacc3c0f3e4ef486d002073e9147305b30fc9680b599ecd0e9d5473160f", + "c11b0503e880276f1d6547843000b62f1503e34d440c2cfe67c822fa9f1dec01", + "8fa564c7c1f550540d70ac5469c77b63b6eee30dbfdf935a1d27f01b90852da7", + "de82a665a685584f93b92f15cd310bdd28a33faf5b592dea5c25e6f24ead3cf7", + "5ccc2074cecc5e7e14bbc684ff420ec3d6658d2b54d95727b740761080dd4a8e", + "690cf19a242b1dc4e4eb95c7249dda2bae94d793a85b70b83080c1375a736199", + "421731b5a60ac1a92c79624f4d1b64b465ea0c36e4d67ba99f88157910bd4bec", + "de48cf7a09019cd05ee06aa911cb8f103382632fa1b363921d4c4a19d6b1026e", + "b66caf5e8b1f7b1fcd5a06ad2371b53dc1ae6524eb4775aed563ba31d565b426", + "0c60918bcf6f554648566bcad8014e99e32a101ea7f91f7a65efaf8d601906fc", + "f7c7a7ccc682fb1e6808cbc8650039cfcbeed9aa4330216f13ff77e4d7ee3f0f" + ], + "logIndex": 82913513, + "rootHash": "3cf2039f630773b42a6b109fdd740ad5a4f05b26cd988472409c79454522f2bb", + "treeSize": 82913514 + }, + "signedEntryTimestamp": "MEUCIBl/clGu2eWEOsvTebGP1Ao62zhgQcKlN8IZWOG9rQ3TAiEA2Ff679UvULchb6IDqSzc/I2b8koSNlVnWYgzoRfdGBQ=" + } + } + } + } + } + } + ] +} diff --git a/test/testdata/local-policy/doi/data.yaml b/test/testdata/local-policy/doi/data.yaml new file mode 100644 index 0000000..f4c0bdd --- /dev/null +++ b/test/testdata/local-policy/doi/data.yaml @@ -0,0 +1,58 @@ +config: + doi: + keys: + - id: "f6a29392b1c08891ff456100aa448b4f6bf9c315850e11cc0883fe9c3c4412db" + key: | + -----BEGIN PUBLIC KEY----- + MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE+XOm2uWjLJhpsJtHCFdGic26suOy + mCl2pBgCof+AHGFZFca40JL833OT+nRSZJRMPKBGibWqsjFrLdRCkOB7bA== + -----END PUBLIC KEY----- + from: "2024-01-01T00:00:00Z" + to: "2024-01-15T12:00:00Z" + # this key was rotated at a planned time + status: "rotated" + signing-format: "dssev1" + - id: "e6f4c70fbba21cbcac44915fff53fd2fdf90dd8849445795fe58014c2b5f8c64" + key: | + -----BEGIN PUBLIC KEY----- + MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZSkTE3si/JkRbuLjaYraS3//YBnX + 8KtEcgdYKZQPl2DnSl4gPsu3KiVeEBWp5GK06IoZlcBAL3NF0OsUUP+yVg== + -----END PUBLIC KEY----- + from: "2024-01-15T12:00:00Z" + to: "2024-01-15T14:00:00Z" + # this key was leaked at a known time, so it revoked from that time + # this behaves the same way as "rotated" but might give another failure message + status: "revoked" + signing-format: "dssev1" + - id: "d45980c5cf39a5e1bab9febe3f16c1c0820b97a8fd061b0064e54b0826e856e4" + key: | + -----BEGIN PUBLIC KEY----- + MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEafssq2x1EDQcKDZhuSrCOxWWl5D4 + JBa9iDJYDnLZp9kPKvv4RnD4rz7Ucfmd0l/zzM45qT29fSBTlguKmnOA8A== + -----END PUBLIC KEY----- + # this key was leaked at an unknown time, so it's completely distrusted + distrust: true + status: "revoked" + signing-format: "dssev1" + - id: "a0c296026645799b2a297913878e81b0aefff2a0c301e97232f717e14402f3e4" + key: | + -----BEGIN PUBLIC KEY----- + MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEgH23D1i2+ZIOtVjmfB7iFvX8AhVN + 9CPJ4ie9axw+WRHozGnRy99U2dRge3zueBBg2MweF0zrToXGig2v3YOrdw== + -----END PUBLIC KEY----- + from: "2023-12-15T14:00:00Z" + to: null + # this key is still active + status: "active" + signing-format: "dssev1" + - id: "b281835e00059de24fb06bd6db06eb0e4a33d7bd7210d7027c209f14b19e812a" + key: | + -----BEGIN PUBLIC KEY----- + MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEgE4Jz6FrLc3lp/YRlbuwOjK4n6ac + jVkSDAmFhi3Ir2Jy+cKeEB7iRPcLvBy9qoMZ9E93m1NdWY6KtDo+Qi52Rg== + -----END PUBLIC KEY----- + from: "2024-01-15T14:00:00Z" + to: null + # this key is still active + status: "active" + signing-format: "dssev1" diff --git a/test/testdata/local-policy/doi/policy.rego b/test/testdata/local-policy/doi/policy.rego new file mode 100644 index 0000000..ed35cbe --- /dev/null +++ b/test/testdata/local-policy/doi/policy.rego @@ -0,0 +1,49 @@ +package docker + +import rego.v1 + +import data.config + +splitDigest := split(input.digest, ":") + +digestType := splitDigest[0] + +digest := splitDigest[1] + +allow if { + some env in attestations.attestation("https://slsa.dev/verification_summary/v0.1") + some statement in verified_statements(config.doi.keys, env) +} + + +verified_statements(keys, env) := statements if { + statements := {statement | + statement := attestations.verify_envelope(env, keys) + some subject in statement.subject + valid_subject(subject) + } +} + + +valid_subject(sub) if { + print("valid_subject") + print("sub.digest[digestType]:", sub.digest[digestType]) + print("digest", digest) + sub.digest[digestType] == digest + print("digest matches") + valid_subject_name(sub.name) +} + +valid_subject_name(name) if { + input.canonical + print("is canonical, ignoring name") +} + +valid_subject_name(name) if { + not input.canonical + print("valid_subject_name...") + print("name:", name) + print("input.purl:", input.purl) + name == input.purl + print("name match") +} diff --git a/test/testdata/local-policy/doi/policy_test.rego b/test/testdata/local-policy/doi/policy_test.rego new file mode 100644 index 0000000..fa81aa9 --- /dev/null +++ b/test/testdata/local-policy/doi/policy_test.rego @@ -0,0 +1,25 @@ +package docker +import rego.v1 + +config := {"keys": []} +envs := [{"env": "test"}] +purl := "pkg:docker/library/alpine:1.2.3" + +statement := {"subject": [{"name": purl, "digest": {"sha256": "dea014f47cd49d694d3a68564eb9e6ae38a7ee9624fd52ec05ccbef3f3fab8a0"}}]} +input_digest := "sha256:dea014f47cd49d694d3a68564eb9e6ae38a7ee9624fd52ec05ccbef3f3fab8a0" + +test_with_mock_data if { + allow with attestations.attestation as envs + with attestations.verify_envelope as statement + with input.digest as input_digest + with input.purl as purl + with input.canonical as false +} + +layout_digest := "sha256:da8b190665956ea07890a0273e2a9c96bfe291662f08e2860e868eef69c34620" +outout_purl := "pkg:docker/test-image@test?platform=linux%2Famd64" +test_with_signed_oci_layout if { + allow with input.digest as layout_digest + with input.purl as outout_purl + with input.canonical as false +} diff --git a/test/testdata/local-policy/mapping.yaml b/test/testdata/local-policy/mapping.yaml new file mode 100644 index 0000000..9db0edf --- /dev/null +++ b/test/testdata/local-policy/mapping.yaml @@ -0,0 +1,16 @@ +# map repos to policies +version: v1 +kind: policy-mapping +policies: + - origin: + domain: docker.io + prefix: library/ + name: test-images + description: Local test images + location: doi + +mirrors: + - name: test-images + mirror: + domains: [localhost:5001] + prefix: "" diff --git a/internal/test/testdata/test-repo-oci/metadata/blobs/sha256/1fd0d9781f02486718fcbd7724db0e4c4ba47b649930cec22a3e7e6b6077ba38 b/test/testdata/tuf/test-repo-oci/metadata/blobs/sha256/1fd0d9781f02486718fcbd7724db0e4c4ba47b649930cec22a3e7e6b6077ba38 similarity index 100% rename from internal/test/testdata/test-repo-oci/metadata/blobs/sha256/1fd0d9781f02486718fcbd7724db0e4c4ba47b649930cec22a3e7e6b6077ba38 rename to test/testdata/tuf/test-repo-oci/metadata/blobs/sha256/1fd0d9781f02486718fcbd7724db0e4c4ba47b649930cec22a3e7e6b6077ba38 diff --git a/internal/test/testdata/test-repo-oci/metadata/blobs/sha256/4c1054844dba3241525cbd71ff9e58becca652fb1ce4a0e6ea55a01c4ec41950 b/test/testdata/tuf/test-repo-oci/metadata/blobs/sha256/4c1054844dba3241525cbd71ff9e58becca652fb1ce4a0e6ea55a01c4ec41950 similarity index 100% rename from internal/test/testdata/test-repo-oci/metadata/blobs/sha256/4c1054844dba3241525cbd71ff9e58becca652fb1ce4a0e6ea55a01c4ec41950 rename to test/testdata/tuf/test-repo-oci/metadata/blobs/sha256/4c1054844dba3241525cbd71ff9e58becca652fb1ce4a0e6ea55a01c4ec41950 diff --git a/internal/test/testdata/test-repo-oci/metadata/blobs/sha256/61a98e1e86ae279e59415d927e38beae430d7e6d2bd6207054179429ea9b6763 b/test/testdata/tuf/test-repo-oci/metadata/blobs/sha256/61a98e1e86ae279e59415d927e38beae430d7e6d2bd6207054179429ea9b6763 similarity index 100% rename from internal/test/testdata/test-repo-oci/metadata/blobs/sha256/61a98e1e86ae279e59415d927e38beae430d7e6d2bd6207054179429ea9b6763 rename to test/testdata/tuf/test-repo-oci/metadata/blobs/sha256/61a98e1e86ae279e59415d927e38beae430d7e6d2bd6207054179429ea9b6763 diff --git a/internal/test/testdata/test-repo-oci/metadata/blobs/sha256/a152e59e7b58a3a1ec718192b07fbf630d2fe2d80a6f1a9dc09e1321cbd338a7 b/test/testdata/tuf/test-repo-oci/metadata/blobs/sha256/a152e59e7b58a3a1ec718192b07fbf630d2fe2d80a6f1a9dc09e1321cbd338a7 similarity index 100% rename from internal/test/testdata/test-repo-oci/metadata/blobs/sha256/a152e59e7b58a3a1ec718192b07fbf630d2fe2d80a6f1a9dc09e1321cbd338a7 rename to test/testdata/tuf/test-repo-oci/metadata/blobs/sha256/a152e59e7b58a3a1ec718192b07fbf630d2fe2d80a6f1a9dc09e1321cbd338a7 diff --git a/internal/test/testdata/test-repo-oci/metadata/blobs/sha256/a2e026ce65c198ee68a7ed2df6978ed0287bb38342f6ddb7bf934a456f1d6f87 b/test/testdata/tuf/test-repo-oci/metadata/blobs/sha256/a2e026ce65c198ee68a7ed2df6978ed0287bb38342f6ddb7bf934a456f1d6f87 similarity index 100% rename from internal/test/testdata/test-repo-oci/metadata/blobs/sha256/a2e026ce65c198ee68a7ed2df6978ed0287bb38342f6ddb7bf934a456f1d6f87 rename to test/testdata/tuf/test-repo-oci/metadata/blobs/sha256/a2e026ce65c198ee68a7ed2df6978ed0287bb38342f6ddb7bf934a456f1d6f87 diff --git a/internal/test/testdata/test-repo-oci/metadata/blobs/sha256/a744a2f1e62ae4ce410822b5e3f5508dbaf6a76768a9d23741828172bab1dc97 b/test/testdata/tuf/test-repo-oci/metadata/blobs/sha256/a744a2f1e62ae4ce410822b5e3f5508dbaf6a76768a9d23741828172bab1dc97 similarity index 100% rename from internal/test/testdata/test-repo-oci/metadata/blobs/sha256/a744a2f1e62ae4ce410822b5e3f5508dbaf6a76768a9d23741828172bab1dc97 rename to test/testdata/tuf/test-repo-oci/metadata/blobs/sha256/a744a2f1e62ae4ce410822b5e3f5508dbaf6a76768a9d23741828172bab1dc97 diff --git a/internal/test/testdata/test-repo-oci/metadata/blobs/sha256/ad4cacc170229608305ffccd8d09eeb59578fcb72ae394763cf7ef492175b1ee b/test/testdata/tuf/test-repo-oci/metadata/blobs/sha256/ad4cacc170229608305ffccd8d09eeb59578fcb72ae394763cf7ef492175b1ee similarity index 100% rename from internal/test/testdata/test-repo-oci/metadata/blobs/sha256/ad4cacc170229608305ffccd8d09eeb59578fcb72ae394763cf7ef492175b1ee rename to test/testdata/tuf/test-repo-oci/metadata/blobs/sha256/ad4cacc170229608305ffccd8d09eeb59578fcb72ae394763cf7ef492175b1ee diff --git a/internal/test/testdata/test-repo-oci/metadata/blobs/sha256/c927b30f17fa8c64e3c20b8f92b7e348733f9c1281b5b7e6b6d669a8a74230a7 b/test/testdata/tuf/test-repo-oci/metadata/blobs/sha256/c927b30f17fa8c64e3c20b8f92b7e348733f9c1281b5b7e6b6d669a8a74230a7 similarity index 100% rename from internal/test/testdata/test-repo-oci/metadata/blobs/sha256/c927b30f17fa8c64e3c20b8f92b7e348733f9c1281b5b7e6b6d669a8a74230a7 rename to test/testdata/tuf/test-repo-oci/metadata/blobs/sha256/c927b30f17fa8c64e3c20b8f92b7e348733f9c1281b5b7e6b6d669a8a74230a7 diff --git a/internal/test/testdata/test-repo-oci/metadata/blobs/sha256/ea7713eb649ca1a33d79ebdccda9f7f066595b1b2c6e37e52dbfd250f5287260 b/test/testdata/tuf/test-repo-oci/metadata/blobs/sha256/ea7713eb649ca1a33d79ebdccda9f7f066595b1b2c6e37e52dbfd250f5287260 similarity index 100% rename from internal/test/testdata/test-repo-oci/metadata/blobs/sha256/ea7713eb649ca1a33d79ebdccda9f7f066595b1b2c6e37e52dbfd250f5287260 rename to test/testdata/tuf/test-repo-oci/metadata/blobs/sha256/ea7713eb649ca1a33d79ebdccda9f7f066595b1b2c6e37e52dbfd250f5287260 diff --git a/internal/test/testdata/test-repo-oci/metadata/index.json b/test/testdata/tuf/test-repo-oci/metadata/index.json similarity index 100% rename from internal/test/testdata/test-repo-oci/metadata/index.json rename to test/testdata/tuf/test-repo-oci/metadata/index.json diff --git a/internal/test/testdata/test-repo-oci/metadata/oci-layout b/test/testdata/tuf/test-repo-oci/metadata/oci-layout similarity index 100% rename from internal/test/testdata/test-repo-oci/metadata/oci-layout rename to test/testdata/tuf/test-repo-oci/metadata/oci-layout diff --git a/internal/test/testdata/test-repo-oci/metadata/test-role/blobs/sha256/2b2d4fba192ec164e05e6d90399c5cf4a45e4fe2ddebb9066c55aa2bcf0a73d3 b/test/testdata/tuf/test-repo-oci/metadata/test-role/blobs/sha256/2b2d4fba192ec164e05e6d90399c5cf4a45e4fe2ddebb9066c55aa2bcf0a73d3 similarity index 100% rename from internal/test/testdata/test-repo-oci/metadata/test-role/blobs/sha256/2b2d4fba192ec164e05e6d90399c5cf4a45e4fe2ddebb9066c55aa2bcf0a73d3 rename to test/testdata/tuf/test-repo-oci/metadata/test-role/blobs/sha256/2b2d4fba192ec164e05e6d90399c5cf4a45e4fe2ddebb9066c55aa2bcf0a73d3 diff --git a/internal/test/testdata/test-repo-oci/metadata/test-role/blobs/sha256/7c8d8f5dfca62068e3a4b18bb41cf85dad23ec9cdc7d7d2e10bc37b86ebffff5 b/test/testdata/tuf/test-repo-oci/metadata/test-role/blobs/sha256/7c8d8f5dfca62068e3a4b18bb41cf85dad23ec9cdc7d7d2e10bc37b86ebffff5 similarity index 100% rename from internal/test/testdata/test-repo-oci/metadata/test-role/blobs/sha256/7c8d8f5dfca62068e3a4b18bb41cf85dad23ec9cdc7d7d2e10bc37b86ebffff5 rename to test/testdata/tuf/test-repo-oci/metadata/test-role/blobs/sha256/7c8d8f5dfca62068e3a4b18bb41cf85dad23ec9cdc7d7d2e10bc37b86ebffff5 diff --git a/internal/test/testdata/test-repo-oci/metadata/test-role/blobs/sha256/9edf24c022c2cd6796e87f49ec6a6ea2fad3e7c939c32a8219aaa4726792457c b/test/testdata/tuf/test-repo-oci/metadata/test-role/blobs/sha256/9edf24c022c2cd6796e87f49ec6a6ea2fad3e7c939c32a8219aaa4726792457c similarity index 100% rename from internal/test/testdata/test-repo-oci/metadata/test-role/blobs/sha256/9edf24c022c2cd6796e87f49ec6a6ea2fad3e7c939c32a8219aaa4726792457c rename to test/testdata/tuf/test-repo-oci/metadata/test-role/blobs/sha256/9edf24c022c2cd6796e87f49ec6a6ea2fad3e7c939c32a8219aaa4726792457c diff --git a/internal/test/testdata/test-repo-oci/metadata/test-role/index.json b/test/testdata/tuf/test-repo-oci/metadata/test-role/index.json similarity index 100% rename from internal/test/testdata/test-repo-oci/metadata/test-role/index.json rename to test/testdata/tuf/test-repo-oci/metadata/test-role/index.json diff --git a/internal/test/testdata/test-repo-oci/metadata/test-role/oci-layout b/test/testdata/tuf/test-repo-oci/metadata/test-role/oci-layout similarity index 100% rename from internal/test/testdata/test-repo-oci/metadata/test-role/oci-layout rename to test/testdata/tuf/test-repo-oci/metadata/test-role/oci-layout diff --git a/internal/test/testdata/test-repo-oci/targets/02119a076ec3878c736c3a95e20794f5a8d5bce3d7ecc264681bb7334ca2e24b.test.txt/blobs/sha256/02119a076ec3878c736c3a95e20794f5a8d5bce3d7ecc264681bb7334ca2e24b b/test/testdata/tuf/test-repo-oci/targets/02119a076ec3878c736c3a95e20794f5a8d5bce3d7ecc264681bb7334ca2e24b.test.txt/blobs/sha256/02119a076ec3878c736c3a95e20794f5a8d5bce3d7ecc264681bb7334ca2e24b similarity index 100% rename from internal/test/testdata/test-repo-oci/targets/02119a076ec3878c736c3a95e20794f5a8d5bce3d7ecc264681bb7334ca2e24b.test.txt/blobs/sha256/02119a076ec3878c736c3a95e20794f5a8d5bce3d7ecc264681bb7334ca2e24b rename to test/testdata/tuf/test-repo-oci/targets/02119a076ec3878c736c3a95e20794f5a8d5bce3d7ecc264681bb7334ca2e24b.test.txt/blobs/sha256/02119a076ec3878c736c3a95e20794f5a8d5bce3d7ecc264681bb7334ca2e24b diff --git a/internal/test/testdata/test-repo-oci/targets/02119a076ec3878c736c3a95e20794f5a8d5bce3d7ecc264681bb7334ca2e24b.test.txt/blobs/sha256/cf0c754e6415fab25e2f59fb6b010dcf0c2369f7a59a45ff29c693c844163ca7 b/test/testdata/tuf/test-repo-oci/targets/02119a076ec3878c736c3a95e20794f5a8d5bce3d7ecc264681bb7334ca2e24b.test.txt/blobs/sha256/cf0c754e6415fab25e2f59fb6b010dcf0c2369f7a59a45ff29c693c844163ca7 similarity index 100% rename from internal/test/testdata/test-repo-oci/targets/02119a076ec3878c736c3a95e20794f5a8d5bce3d7ecc264681bb7334ca2e24b.test.txt/blobs/sha256/cf0c754e6415fab25e2f59fb6b010dcf0c2369f7a59a45ff29c693c844163ca7 rename to test/testdata/tuf/test-repo-oci/targets/02119a076ec3878c736c3a95e20794f5a8d5bce3d7ecc264681bb7334ca2e24b.test.txt/blobs/sha256/cf0c754e6415fab25e2f59fb6b010dcf0c2369f7a59a45ff29c693c844163ca7 diff --git a/internal/test/testdata/test-repo-oci/targets/02119a076ec3878c736c3a95e20794f5a8d5bce3d7ecc264681bb7334ca2e24b.test.txt/blobs/sha256/cf70a3b91fd7dfaa30952dfa9f094809e6cd9bd7364942c7f067c747bc535f94 b/test/testdata/tuf/test-repo-oci/targets/02119a076ec3878c736c3a95e20794f5a8d5bce3d7ecc264681bb7334ca2e24b.test.txt/blobs/sha256/cf70a3b91fd7dfaa30952dfa9f094809e6cd9bd7364942c7f067c747bc535f94 similarity index 100% rename from internal/test/testdata/test-repo-oci/targets/02119a076ec3878c736c3a95e20794f5a8d5bce3d7ecc264681bb7334ca2e24b.test.txt/blobs/sha256/cf70a3b91fd7dfaa30952dfa9f094809e6cd9bd7364942c7f067c747bc535f94 rename to test/testdata/tuf/test-repo-oci/targets/02119a076ec3878c736c3a95e20794f5a8d5bce3d7ecc264681bb7334ca2e24b.test.txt/blobs/sha256/cf70a3b91fd7dfaa30952dfa9f094809e6cd9bd7364942c7f067c747bc535f94 diff --git a/internal/test/testdata/test-repo-oci/targets/02119a076ec3878c736c3a95e20794f5a8d5bce3d7ecc264681bb7334ca2e24b.test.txt/index.json b/test/testdata/tuf/test-repo-oci/targets/02119a076ec3878c736c3a95e20794f5a8d5bce3d7ecc264681bb7334ca2e24b.test.txt/index.json similarity index 100% rename from internal/test/testdata/test-repo-oci/targets/02119a076ec3878c736c3a95e20794f5a8d5bce3d7ecc264681bb7334ca2e24b.test.txt/index.json rename to test/testdata/tuf/test-repo-oci/targets/02119a076ec3878c736c3a95e20794f5a8d5bce3d7ecc264681bb7334ca2e24b.test.txt/index.json diff --git a/internal/test/testdata/test-repo-oci/targets/02119a076ec3878c736c3a95e20794f5a8d5bce3d7ecc264681bb7334ca2e24b.test.txt/oci-layout b/test/testdata/tuf/test-repo-oci/targets/02119a076ec3878c736c3a95e20794f5a8d5bce3d7ecc264681bb7334ca2e24b.test.txt/oci-layout similarity index 100% rename from internal/test/testdata/test-repo-oci/targets/02119a076ec3878c736c3a95e20794f5a8d5bce3d7ecc264681bb7334ca2e24b.test.txt/oci-layout rename to test/testdata/tuf/test-repo-oci/targets/02119a076ec3878c736c3a95e20794f5a8d5bce3d7ecc264681bb7334ca2e24b.test.txt/oci-layout diff --git a/internal/test/testdata/test-repo-oci/targets/test-role/blobs/sha256/0a4afcdad291941327b070ab4feaf052425fbf4ded864bc55c18cfefec8be6e2 b/test/testdata/tuf/test-repo-oci/targets/test-role/blobs/sha256/0a4afcdad291941327b070ab4feaf052425fbf4ded864bc55c18cfefec8be6e2 similarity index 100% rename from internal/test/testdata/test-repo-oci/targets/test-role/blobs/sha256/0a4afcdad291941327b070ab4feaf052425fbf4ded864bc55c18cfefec8be6e2 rename to test/testdata/tuf/test-repo-oci/targets/test-role/blobs/sha256/0a4afcdad291941327b070ab4feaf052425fbf4ded864bc55c18cfefec8be6e2 diff --git a/internal/test/testdata/test-repo-oci/targets/test-role/blobs/sha256/0b6b8fdb10421310b9aca2f1fb6ce51537baa243fb9fccca03f2ff3c15fb52f8 b/test/testdata/tuf/test-repo-oci/targets/test-role/blobs/sha256/0b6b8fdb10421310b9aca2f1fb6ce51537baa243fb9fccca03f2ff3c15fb52f8 similarity index 100% rename from internal/test/testdata/test-repo-oci/targets/test-role/blobs/sha256/0b6b8fdb10421310b9aca2f1fb6ce51537baa243fb9fccca03f2ff3c15fb52f8 rename to test/testdata/tuf/test-repo-oci/targets/test-role/blobs/sha256/0b6b8fdb10421310b9aca2f1fb6ce51537baa243fb9fccca03f2ff3c15fb52f8 diff --git a/internal/test/testdata/test-repo-oci/targets/test-role/blobs/sha256/8d320e9d3f3663613df6e4fca1651604a6c0323011023145a140b38f02105b04 b/test/testdata/tuf/test-repo-oci/targets/test-role/blobs/sha256/8d320e9d3f3663613df6e4fca1651604a6c0323011023145a140b38f02105b04 similarity index 100% rename from internal/test/testdata/test-repo-oci/targets/test-role/blobs/sha256/8d320e9d3f3663613df6e4fca1651604a6c0323011023145a140b38f02105b04 rename to test/testdata/tuf/test-repo-oci/targets/test-role/blobs/sha256/8d320e9d3f3663613df6e4fca1651604a6c0323011023145a140b38f02105b04 diff --git a/internal/test/testdata/test-repo-oci/targets/test-role/blobs/sha256/d1bb6181284970ae43fbbc88b5e72f9a5942ebac20588aa0c4bf78ba621e1ee2 b/test/testdata/tuf/test-repo-oci/targets/test-role/blobs/sha256/d1bb6181284970ae43fbbc88b5e72f9a5942ebac20588aa0c4bf78ba621e1ee2 similarity index 100% rename from internal/test/testdata/test-repo-oci/targets/test-role/blobs/sha256/d1bb6181284970ae43fbbc88b5e72f9a5942ebac20588aa0c4bf78ba621e1ee2 rename to test/testdata/tuf/test-repo-oci/targets/test-role/blobs/sha256/d1bb6181284970ae43fbbc88b5e72f9a5942ebac20588aa0c4bf78ba621e1ee2 diff --git a/internal/test/testdata/test-repo-oci/targets/test-role/blobs/sha256/d9941355ca037d7e878e04c1bc7cbf9c71a5d8035b6e27be0d9e5d9087599055 b/test/testdata/tuf/test-repo-oci/targets/test-role/blobs/sha256/d9941355ca037d7e878e04c1bc7cbf9c71a5d8035b6e27be0d9e5d9087599055 similarity index 100% rename from internal/test/testdata/test-repo-oci/targets/test-role/blobs/sha256/d9941355ca037d7e878e04c1bc7cbf9c71a5d8035b6e27be0d9e5d9087599055 rename to test/testdata/tuf/test-repo-oci/targets/test-role/blobs/sha256/d9941355ca037d7e878e04c1bc7cbf9c71a5d8035b6e27be0d9e5d9087599055 diff --git a/internal/test/testdata/test-repo-oci/targets/test-role/blobs/sha256/ea230621c53e0bb858ea5526125414f8957fb29c08350528d50a162c620f36b1 b/test/testdata/tuf/test-repo-oci/targets/test-role/blobs/sha256/ea230621c53e0bb858ea5526125414f8957fb29c08350528d50a162c620f36b1 similarity index 100% rename from internal/test/testdata/test-repo-oci/targets/test-role/blobs/sha256/ea230621c53e0bb858ea5526125414f8957fb29c08350528d50a162c620f36b1 rename to test/testdata/tuf/test-repo-oci/targets/test-role/blobs/sha256/ea230621c53e0bb858ea5526125414f8957fb29c08350528d50a162c620f36b1 diff --git a/internal/test/testdata/test-repo-oci/targets/test-role/index.json b/test/testdata/tuf/test-repo-oci/targets/test-role/index.json similarity index 100% rename from internal/test/testdata/test-repo-oci/targets/test-role/index.json rename to test/testdata/tuf/test-repo-oci/targets/test-role/index.json diff --git a/internal/test/testdata/test-repo-oci/targets/test-role/oci-layout b/test/testdata/tuf/test-repo-oci/targets/test-role/oci-layout similarity index 100% rename from internal/test/testdata/test-repo-oci/targets/test-role/oci-layout rename to test/testdata/tuf/test-repo-oci/targets/test-role/oci-layout diff --git a/internal/test/testdata/test-repo/metadata/1.root.json b/test/testdata/tuf/test-repo/metadata/1.root.json similarity index 100% rename from internal/test/testdata/test-repo/metadata/1.root.json rename to test/testdata/tuf/test-repo/metadata/1.root.json diff --git a/internal/test/testdata/test-repo/metadata/2.root.json b/test/testdata/tuf/test-repo/metadata/2.root.json similarity index 100% rename from internal/test/testdata/test-repo/metadata/2.root.json rename to test/testdata/tuf/test-repo/metadata/2.root.json diff --git a/internal/test/testdata/test-repo/metadata/3.test-role.json b/test/testdata/tuf/test-repo/metadata/3.test-role.json similarity index 100% rename from internal/test/testdata/test-repo/metadata/3.test-role.json rename to test/testdata/tuf/test-repo/metadata/3.test-role.json diff --git a/internal/test/testdata/test-repo/metadata/5.targets.json b/test/testdata/tuf/test-repo/metadata/5.targets.json similarity index 100% rename from internal/test/testdata/test-repo/metadata/5.targets.json rename to test/testdata/tuf/test-repo/metadata/5.targets.json diff --git a/internal/test/testdata/test-repo/metadata/6.snapshot.json b/test/testdata/tuf/test-repo/metadata/6.snapshot.json similarity index 100% rename from internal/test/testdata/test-repo/metadata/6.snapshot.json rename to test/testdata/tuf/test-repo/metadata/6.snapshot.json diff --git a/internal/test/testdata/test-repo/metadata/timestamp.json b/test/testdata/tuf/test-repo/metadata/timestamp.json similarity index 100% rename from internal/test/testdata/test-repo/metadata/timestamp.json rename to test/testdata/tuf/test-repo/metadata/timestamp.json diff --git a/internal/test/testdata/test-repo/targets/02119a076ec3878c736c3a95e20794f5a8d5bce3d7ecc264681bb7334ca2e24b.test.txt b/test/testdata/tuf/test-repo/targets/02119a076ec3878c736c3a95e20794f5a8d5bce3d7ecc264681bb7334ca2e24b.test.txt similarity index 100% rename from internal/test/testdata/test-repo/targets/02119a076ec3878c736c3a95e20794f5a8d5bce3d7ecc264681bb7334ca2e24b.test.txt rename to test/testdata/tuf/test-repo/targets/02119a076ec3878c736c3a95e20794f5a8d5bce3d7ecc264681bb7334ca2e24b.test.txt diff --git a/internal/test/testdata/test-repo/targets/test-role/d1bb6181284970ae43fbbc88b5e72f9a5942ebac20588aa0c4bf78ba621e1ee2.test.txt b/test/testdata/tuf/test-repo/targets/test-role/d1bb6181284970ae43fbbc88b5e72f9a5942ebac20588aa0c4bf78ba621e1ee2.test.txt similarity index 100% rename from internal/test/testdata/test-repo/targets/test-role/d1bb6181284970ae43fbbc88b5e72f9a5942ebac20588aa0c4bf78ba621e1ee2.test.txt rename to test/testdata/tuf/test-repo/targets/test-role/d1bb6181284970ae43fbbc88b5e72f9a5942ebac20588aa0c4bf78ba621e1ee2.test.txt diff --git a/internal/test/testdata/test-repo/targets/test-role/dir1/dir2/dir3/ea230621c53e0bb858ea5526125414f8957fb29c08350528d50a162c620f36b1.myfile.txt b/test/testdata/tuf/test-repo/targets/test-role/dir1/dir2/dir3/ea230621c53e0bb858ea5526125414f8957fb29c08350528d50a162c620f36b1.myfile.txt similarity index 100% rename from internal/test/testdata/test-repo/targets/test-role/dir1/dir2/dir3/ea230621c53e0bb858ea5526125414f8957fb29c08350528d50a162c620f36b1.myfile.txt rename to test/testdata/tuf/test-repo/targets/test-role/dir1/dir2/dir3/ea230621c53e0bb858ea5526125414f8957fb29c08350528d50a162c620f36b1.myfile.txt diff --git a/test/testdata/vsa.json b/test/testdata/vsa.json new file mode 100644 index 0000000..d642483 --- /dev/null +++ b/test/testdata/vsa.json @@ -0,0 +1,48 @@ +{ + "payloadType": "application/vnd.in-toto+json", + "payload": "eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL3Nsc2EuZGV2L3ZlcmlmaWNhdGlvbl9zdW1tYXJ5L3YwLjEiLCJzdWJqZWN0IjpbeyJuYW1lIjoicGtnOmRvY2tlci90ZXN0LWltYWdlQHRlc3Q/cGxhdGZvcm09bGludXglMkZhbWQ2NCIsImRpZ2VzdCI6eyJzaGEyNTYiOiJkYThiMTkwNjY1OTU2ZWEwNzg5MGEwMjczZTJhOWM5NmJmZTI5MTY2MmYwOGUyODYwZTg2OGVlZjY5YzM0NjIwIn19XSwicHJlZGljYXRlIjp7InZlcmlmaWVyIjp7ImlkIjoiaHR0cHM6Ly9kb2NrZXIuY29tIn0sInRpbWVWZXJpZmllZCI6IjIwMjQtMDQtMTlUMTY6MjU6MTFaIiwicmVzb3VyY2VVcmkiOiJwa2c6ZG9ja2VyL3Rlc3QtaW1hZ2VAdGVzdD9kaWdlc3Q9c2hhMjU2JTNBZGE4YjE5MDY2NTk1NmVhMDc4OTBhMDI3M2UyYTljOTZiZmUyOTE2NjJmMDhlMjg2MGU4NjhlZWY2OWMzNDYyMFx1MDAyNnBsYXRmb3JtPWxpbnV4JTJGYW1kNjQiLCJwb2xpY3kiOnsidXJpIjoiaHR0cHM6Ly9kb2NrZXIuY29tL2xpYnJhcnkvcG9saWN5In0sImlucHV0QXR0ZXN0YXRpb25zIjpudWxsLCJ2ZXJpZmljYXRpb25SZXN1bHQiOiJQQVNTRUQiLCJ2ZXJpZmllZExldmVscyI6WyJTTFNBX0JVSUxEX0xFVkVMXzMiXX19", + "signatures": [ + { + "keyid": "a0c296026645799b2a297913878e81b0aefff2a0c301e97232f717e14402f3e4", + "sig": "MEUCIDtkq1k1x7exFlKmRx8asV0a/nycXxdw71pRvbtVltprAiEA4aV45oCN3tfIf4KbQaRH3RO0wCbemP3woL0gK3lkscg=", + "extension": { + "kind": "application/vnd.docker.attestation-verification.v1+json", + "ext": { + "tl": { + "kind": "Rekor", + "data": { + "body": "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiJiODU4MTM2MWUzMDcwZjhjNzUxZTk4ODE3MWFmYzFhMjE1ZGJlZjQxMzk2NTMxOTJiZDMyYWIwYzE3ZDM2YzM1In19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FVUNJRHRrcTFrMXg3ZXhGbEttUng4YXNWMGEvbnljWHhkdzcxcFJ2YnRWbHRwckFpRUE0YVY0NW9DTjN0ZklmNEtiUWFSSDNSTzB3Q2JlbVAzd29MMGdLM2xrc2NnPSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVTkRWRU5EUVdFclowRjNTVUpCWjBsQ1FWUkJTMEpuWjNGb2EycFBVRkZSUkVGcVFreE5WV3QzVW5kWlJGWlJVVVJGTUVKb1RVZE5lVTlVV1hjS1RXcFpNazVFVlROUFZHeHBUVzFGZVU5VVl6Vk5WRTAwVG5wb2JFOUVSbWxOUjBac1dtMWFiVTF0UlhkWmVrMTNUVmRWTlU1NlNYcE5iVmt6VFZSa2JBcE5WRkV3VFVSS2JVMHlWVEJOUWpSWVJGUkpNRTFFVVhoUFZFVXlUV3BWZUUxV2IxaEVWRWt4VFVSUmVFOVVSVEpOYWxWNFRWWnZkMU42UmtwTlJXTkhDa0V4VlVWQmVFNUJXVlJDYWsxcWF6Sk5SRWt5VG1wUk1VNTZhelZaYWtwb1RXcHJNMDlVUlhwUFJHTTBXbFJuZUZscVFtaGFWMXB0V21wS2FFMUhUWG9LVFVSR2JFOVVZM2xOZWtwdFRucEZNMXBVUlRCT1JFRjVXbXBPYkU1RVFscE5RazFIUW5seFIxTk5ORGxCWjBWSFEwTnhSMU5OTkRsQmQwVklRVEJKUVFwQ1NVSTVkSGM1V1hSMmJWTkVjbFpaTlc1M1pUUm9ZakV2UVVsV1ZHWlJhbmxsU1c1MlYzTmpVR3hyVWpaTmVIQXdZM1ptVms1dVZWbElkRGczYm1kUkNsbE9hazFJYUdSTk5qQTJSbmh2YjA1eU9USkVjVE5sYW1kWlRYZG5XVUYzUkdkWlJGWlNNRkJCVVVndlFrRlJSRUZuWlVGTlFrMUhRVEZWWkVwUlVVMEtUVUZ2UjBORGMwZEJVVlZHUW5kTlJFMUJkMGRCTVZWa1JYZEZRaTkzVVVOTlFVRjNVM2RaUkZaU01GSkNSVkYzVVc5S1FWbFVRbXBOYW1zeVRVUkpNZ3BPYWxFeFRucHJOVmxxU21oTmFtc3pUMVJGZWs5RVl6UmFWR2Q0V1dwQ2FGcFhXbTFhYWtwb1RVZE5lazFFUm14UFZHTjVUWHBLYlU1NlJUTmFWRVV3Q2s1RVFYbGFhazVzVGtSQlMwSm5aM0ZvYTJwUFVGRlJSRUZuVGtsQlJFSkdRV2xGUVRaVk5pdDBjRTFGWlhCT1JYSnpMM0JPYW1kdlFWbzNWM1JtVlZRS1kxTmxiSEp3VTJsTFdqQjNPRkpCUTBsRVlWUXpWbXBETmpoTVlVeEtTbWxDWkhSbFYzTnVNMlYyWTBST1NFZFVjMmRNVDFwcGNXdDRTakJsQ2kwdExTMHRSVTVFSUVORlVsUkpSa2xEUVZSRkxTMHRMUzBLIn19fX0=", + "integratedTime": 1713543910, + "logID": "c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d", + "logIndex": 87076941, + "verification": { + "inclusionProof": { + "checkpoint": "rekor.sigstore.dev - 2605736670972794746\n82913511\nBaX/1AgGwb1Cxm3NpleUYCBgWPt6pj2hq/xGYEmZl/c=\n\n— rekor.sigstore.dev wNI9ajBFAiEAsrpsGf41VBvC0uci2OPfaB3y+gm4ri+Pw2892vlCPqECIAbjtBCF3fJBOxxTDcXuAHk5F4D6TmLQCJ5glIkDlCuZ\n", + "hashes": [ + "1f87b20218e09db0b277220c8b32a32ca917775b1561f9ad63afd755e4ddbf62", + "508e07a2fc0a8ac9c00b908c7ac3504dee1af18c6499283619b6f00ec0369145", + "7828aaacc3c0f3e4ef486d002073e9147305b30fc9680b599ecd0e9d5473160f", + "c11b0503e880276f1d6547843000b62f1503e34d440c2cfe67c822fa9f1dec01", + "8fa564c7c1f550540d70ac5469c77b63b6eee30dbfdf935a1d27f01b90852da7", + "de82a665a685584f93b92f15cd310bdd28a33faf5b592dea5c25e6f24ead3cf7", + "5ccc2074cecc5e7e14bbc684ff420ec3d6658d2b54d95727b740761080dd4a8e", + "690cf19a242b1dc4e4eb95c7249dda2bae94d793a85b70b83080c1375a736199", + "421731b5a60ac1a92c79624f4d1b64b465ea0c36e4d67ba99f88157910bd4bec", + "de48cf7a09019cd05ee06aa911cb8f103382632fa1b363921d4c4a19d6b1026e", + "b66caf5e8b1f7b1fcd5a06ad2371b53dc1ae6524eb4775aed563ba31d565b426", + "0c60918bcf6f554648566bcad8014e99e32a101ea7f91f7a65efaf8d601906fc", + "f7c7a7ccc682fb1e6808cbc8650039cfcbeed9aa4330216f13ff77e4d7ee3f0f" + ], + "logIndex": 82913510, + "rootHash": "05a5ffd40806c1bd42c66dcda6579460206058fb7aa63da1abfc4660499997f7", + "treeSize": 82913511 + }, + "signedEntryTimestamp": "MEQCIHkKq/L6PD0eCsNch7D89YpUScYxzW9rOEumpzBhnLRCAiBQkdPEHa5LFgvB7KwYrvs6xT29AWuIFk2qns3cmdk6IA==" + } + } + } + } + } + } + ] +} From 1813ab8a7a9ba63534ff1d01442117cd6b6f93d7 Mon Sep 17 00:00:00 2001 From: mrjoelkamp Date: Mon, 22 Apr 2024 12:47:14 -0500 Subject: [PATCH 2/5] feat: add exported constants --- internal/oci/oci.go | 5 +++-- internal/oci/types.go | 3 --- pkg/types/types.go | 7 +++++++ 3 files changed, 10 insertions(+), 5 deletions(-) create mode 100644 pkg/types/types.go diff --git a/internal/oci/oci.go b/internal/oci/oci.go index 89af78c..0f50222 100644 --- a/internal/oci/oci.go +++ b/internal/oci/oci.go @@ -9,6 +9,7 @@ import ( "github.com/containerd/containerd/platforms" "github.com/distribution/reference" att "github.com/docker/attest/pkg/attestation" + "github.com/docker/attest/pkg/types" "github.com/google/go-containerregistry/pkg/authn" "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" @@ -71,11 +72,11 @@ func attestationManifestFromOCILayout(path string, platformStr string) (*Attesta } } for _, mf := range mfs2.Manifests { - if mf.Annotations[DockerReferenceType] != AttestationManifestType { + if mf.Annotations[types.DockerReferenceType] != types.AttestationManifestType { continue } - if mf.Annotations[DockerReferenceDigest] != imageDigest { + if mf.Annotations[types.DockerReferenceDigest] != imageDigest { continue } diff --git a/internal/oci/types.go b/internal/oci/types.go index 5af15eb..4f9367d 100644 --- a/internal/oci/types.go +++ b/internal/oci/types.go @@ -1,8 +1,5 @@ package oci const ( - DockerReferenceType = "vnd.docker.reference.type" - DockerReferenceDigest = "vnd.docker.reference.digest" - AttestationManifestType = "attestation-manifest" InTotoPredicateType = "in-toto.io/predicate-type" ) diff --git a/pkg/types/types.go b/pkg/types/types.go new file mode 100644 index 0000000..ee847f2 --- /dev/null +++ b/pkg/types/types.go @@ -0,0 +1,7 @@ +package types + +const ( + DockerReferenceType = "vnd.docker.reference.type" + DockerReferenceDigest = "vnd.docker.reference.digest" + AttestationManifestType = "attestation-manifest" +) From bf2dff9f669defeffff0e64fe2ed5596c237cea6 Mon Sep 17 00:00:00 2001 From: mrjoelkamp Date: Mon, 22 Apr 2024 12:50:52 -0500 Subject: [PATCH 3/5] revert: add exported constants --- pkg/types/types.go | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 pkg/types/types.go diff --git a/pkg/types/types.go b/pkg/types/types.go deleted file mode 100644 index ee847f2..0000000 --- a/pkg/types/types.go +++ /dev/null @@ -1,7 +0,0 @@ -package types - -const ( - DockerReferenceType = "vnd.docker.reference.type" - DockerReferenceDigest = "vnd.docker.reference.digest" - AttestationManifestType = "attestation-manifest" -) From 1b2f80d4c528ca2121e27d4f9086e3b1f6d3674e Mon Sep 17 00:00:00 2001 From: mrjoelkamp Date: Mon, 22 Apr 2024 12:51:10 -0500 Subject: [PATCH 4/5] refactor: export oci --- internal/oci/types.go | 5 ----- internal/test/test.go | 2 +- {internal => pkg}/oci/http.go | 0 {internal => pkg}/oci/oci.go | 5 ++--- {internal => pkg}/oci/oci_test.go | 0 {internal => pkg}/oci/resolver.go | 0 pkg/oci/types.go | 8 ++++++++ pkg/policy/evaluator.go | 2 +- pkg/policy/policy.go | 2 +- pkg/policy/policy_test.go | 2 +- pkg/policy/rego.go | 2 +- 11 files changed, 15 insertions(+), 13 deletions(-) delete mode 100644 internal/oci/types.go rename {internal => pkg}/oci/http.go (100%) rename {internal => pkg}/oci/oci.go (98%) rename {internal => pkg}/oci/oci_test.go (100%) rename {internal => pkg}/oci/resolver.go (100%) create mode 100644 pkg/oci/types.go diff --git a/internal/oci/types.go b/internal/oci/types.go deleted file mode 100644 index 4f9367d..0000000 --- a/internal/oci/types.go +++ /dev/null @@ -1,5 +0,0 @@ -package oci - -const ( - InTotoPredicateType = "in-toto.io/predicate-type" -) diff --git a/internal/test/test.go b/internal/test/test.go index b149693..87533d7 100644 --- a/internal/test/test.go +++ b/internal/test/test.go @@ -5,7 +5,7 @@ import ( "os" "testing" - "github.com/docker/attest/internal/oci" + "github.com/docker/attest/pkg/oci" "github.com/docker/attest/pkg/policy" "github.com/docker/attest/pkg/signerverifier" "github.com/docker/attest/pkg/tlog" diff --git a/internal/oci/http.go b/pkg/oci/http.go similarity index 100% rename from internal/oci/http.go rename to pkg/oci/http.go diff --git a/internal/oci/oci.go b/pkg/oci/oci.go similarity index 98% rename from internal/oci/oci.go rename to pkg/oci/oci.go index 0f50222..89af78c 100644 --- a/internal/oci/oci.go +++ b/pkg/oci/oci.go @@ -9,7 +9,6 @@ import ( "github.com/containerd/containerd/platforms" "github.com/distribution/reference" att "github.com/docker/attest/pkg/attestation" - "github.com/docker/attest/pkg/types" "github.com/google/go-containerregistry/pkg/authn" "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" @@ -72,11 +71,11 @@ func attestationManifestFromOCILayout(path string, platformStr string) (*Attesta } } for _, mf := range mfs2.Manifests { - if mf.Annotations[types.DockerReferenceType] != types.AttestationManifestType { + if mf.Annotations[DockerReferenceType] != AttestationManifestType { continue } - if mf.Annotations[types.DockerReferenceDigest] != imageDigest { + if mf.Annotations[DockerReferenceDigest] != imageDigest { continue } diff --git a/internal/oci/oci_test.go b/pkg/oci/oci_test.go similarity index 100% rename from internal/oci/oci_test.go rename to pkg/oci/oci_test.go diff --git a/internal/oci/resolver.go b/pkg/oci/resolver.go similarity index 100% rename from internal/oci/resolver.go rename to pkg/oci/resolver.go diff --git a/pkg/oci/types.go b/pkg/oci/types.go new file mode 100644 index 0000000..5af15eb --- /dev/null +++ b/pkg/oci/types.go @@ -0,0 +1,8 @@ +package oci + +const ( + DockerReferenceType = "vnd.docker.reference.type" + DockerReferenceDigest = "vnd.docker.reference.digest" + AttestationManifestType = "attestation-manifest" + InTotoPredicateType = "in-toto.io/predicate-type" +) diff --git a/pkg/policy/evaluator.go b/pkg/policy/evaluator.go index e4ada6b..42cce24 100644 --- a/pkg/policy/evaluator.go +++ b/pkg/policy/evaluator.go @@ -4,7 +4,7 @@ import ( "context" "fmt" - "github.com/docker/attest/internal/oci" + "github.com/docker/attest/pkg/oci" ) type policyEvaluatorCtxKeyType struct{} diff --git a/pkg/policy/policy.go b/pkg/policy/policy.go index c408aa6..ceb8de3 100644 --- a/pkg/policy/policy.go +++ b/pkg/policy/policy.go @@ -9,8 +9,8 @@ import ( "strings" "github.com/distribution/reference" - "github.com/docker/attest/internal/oci" "github.com/docker/attest/internal/util" + "github.com/docker/attest/pkg/oci" "github.com/docker/attest/pkg/tuf" goyaml "gopkg.in/yaml.v3" diff --git a/pkg/policy/policy_test.go b/pkg/policy/policy_test.go index 21205da..9e404b8 100644 --- a/pkg/policy/policy_test.go +++ b/pkg/policy/policy_test.go @@ -6,9 +6,9 @@ import ( "path/filepath" "testing" - "github.com/docker/attest/internal/oci" "github.com/docker/attest/internal/test" "github.com/docker/attest/pkg/attestation" + "github.com/docker/attest/pkg/oci" "github.com/docker/attest/pkg/policy" "github.com/docker/attest/pkg/tuf" "github.com/stretchr/testify/assert" diff --git a/pkg/policy/rego.go b/pkg/policy/rego.go index b6d5c8a..3dd42e2 100644 --- a/pkg/policy/rego.go +++ b/pkg/policy/rego.go @@ -7,8 +7,8 @@ import ( "os" "path/filepath" - "github.com/docker/attest/internal/oci" att "github.com/docker/attest/pkg/attestation" + "github.com/docker/attest/pkg/oci" intoto "github.com/in-toto/in-toto-golang/in_toto" "github.com/open-policy-agent/opa/ast" "github.com/open-policy-agent/opa/rego" From f8f40807bc937ec8dd9ac8104219025132ba3595 Mon Sep 17 00:00:00 2001 From: mrjoelkamp Date: Mon, 22 Apr 2024 14:16:45 -0500 Subject: [PATCH 5/5] feat: pr comments --- pkg/policy/rego.go | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/pkg/policy/rego.go b/pkg/policy/rego.go index 3dd42e2..fc9141a 100644 --- a/pkg/policy/rego.go +++ b/pkg/policy/rego.go @@ -206,18 +206,13 @@ func verifyIntotoEnvelope(rCtx rego.BuiltinContext, envTerm, keysTerm *ast.Term) return ast.NewTerm(value), nil } -// copied from opa func loadYAML(path string, bs []byte) (interface{}, error) { + var x interface{} bs, err := yaml.YAMLToJSON(bs) if err != nil { return nil, fmt.Errorf("%v: error converting YAML to JSON: %v", path, err) } - return loadJSON(path, bs) -} - -func loadJSON(path string, bs []byte) (interface{}, error) { - var x interface{} - err := opa.UnmarshalJSON(bs, &x) + err = opa.UnmarshalJSON(bs, &x) if err != nil { return nil, fmt.Errorf("%s: %w", path, err) }