Compare commits
60 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2e2bc49387 | ||
|
|
0330ea4755 | ||
|
|
0336a21a7d | ||
|
|
1754a98e4e | ||
|
|
a05fc10d53 | ||
|
|
e830271d01 | ||
|
|
1cb3e4a281 | ||
|
|
6b199f027a | ||
|
|
aaf043e9cd | ||
|
|
ac693a45c7 | ||
|
|
0038e3d23d | ||
|
|
0dd63bf5a3 | ||
|
|
5d56efa2df | ||
|
|
3ffef89dda | ||
|
|
3c26a89496 | ||
|
|
6ee1d32ddc | ||
|
|
3140e2d903 | ||
|
|
dd1141c231 | ||
|
|
bda1910107 | ||
|
|
80658a4b5f | ||
|
|
46db2b9fd5 | ||
|
|
e37f788865 | ||
|
|
13172cb502 | ||
|
|
abb3163628 | ||
|
|
742f98fbeb | ||
|
|
8cae188735 | ||
|
|
7586f4dfc4 | ||
|
|
acb862ea42 | ||
|
|
357768d421 | ||
|
|
6bd57e02b6 | ||
|
|
92985e9a12 | ||
|
|
08e823e05b | ||
|
|
ff38975c76 | ||
|
|
86878482c3 | ||
|
|
f95760d8b2 | ||
|
|
f611f81fff | ||
|
|
8e3c6a2ec5 | ||
|
|
a3921c206a | ||
|
|
130e1f640b | ||
|
|
0d0d86854c | ||
|
|
1d9e14b99f | ||
|
|
83c7d7634a | ||
|
|
5c07bd70d9 | ||
|
|
c02e628600 | ||
|
|
3d46780a1c | ||
|
|
83dfd746b9 | ||
|
|
845fe93c11 | ||
|
|
c154613c52 | ||
|
|
e44390d2bc | ||
|
|
8ba9656645 | ||
|
|
e120439035 | ||
|
|
b20f452004 | ||
|
|
4be882aeb0 | ||
|
|
3b5c506739 | ||
|
|
f36bb50af5 | ||
|
|
c8c148c70a | ||
|
|
a334599635 | ||
|
|
e81016fc31 | ||
|
|
2ae5606c92 | ||
|
|
8a6e75ce39 |
40
.github/workflows/test.yml
vendored
40
.github/workflows/test.yml
vendored
@@ -7,6 +7,9 @@ on:
|
||||
workflow_dispatch:
|
||||
jobs:
|
||||
golang:
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [1.21.x]
|
||||
@@ -21,9 +24,42 @@ jobs:
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
- name: Login to Docker Hub
|
||||
if: matrix.os == 'ubuntu-latest' && github.actor != 'dependabot[bot]'
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: dockerpublicbot
|
||||
password: ${{ secrets.DOCKERPUBLICBOT_WRITE_PAT }}
|
||||
- name: Authenticate to AWS
|
||||
if: matrix.os == 'ubuntu-latest' && github.actor != 'dependabot[bot]'
|
||||
uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 #v4.0.2
|
||||
with:
|
||||
aws-region: "us-east-1"
|
||||
role-to-assume: arn:aws:iam::175142243308:role/doi-github-actions-signing
|
||||
- name: auth-with-gcp
|
||||
if: matrix.os == 'ubuntu-latest' && github.actor != 'dependabot[bot]'
|
||||
uses: google-github-actions/auth@v2
|
||||
with:
|
||||
project_id: 'attest-kms-test'
|
||||
export_environment_variables: true
|
||||
workload_identity_provider: 'projects/385966116051/locations/global/workloadIdentityPools/attest-kms-test/providers/attest-kms-test'
|
||||
service_account: 'attest-kms-test@attest-kms-test.iam.gserviceaccount.com'
|
||||
- name: Setup Testcontainers Cloud Client
|
||||
uses: atomicjar/testcontainers-cloud-setup-action@v1
|
||||
with:
|
||||
token: ${{ secrets.TC_CLOUD_TOKEN }}
|
||||
- name: go test
|
||||
run: go test ./...
|
||||
- name: go test including e2e
|
||||
if: matrix.os == 'ubuntu-latest' && github.actor != 'dependabot[bot]'
|
||||
run: go test -tags=e2e -v ./... -coverprofile=coverage.out -covermode=atomic
|
||||
- name: go test excluding e2e
|
||||
if: matrix.os == 'macos-latest' || github.actor == 'dependabot[bot]'
|
||||
run: go test -v ./...
|
||||
- name: Upload coverage to Codecov
|
||||
if: matrix.os == 'ubuntu-latest' && github.actor != 'dependabot[bot]'
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
file: ./coverage.out
|
||||
flags: unittests
|
||||
name: codecov-umbrella
|
||||
fail_ci_if_error: true
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# attest
|
||||
library to create, verify, and evaluate policy for attestations on container images
|
||||
|
||||
[](https://codecov.io/gh/docker/attest)
|
||||
|
||||
# usage
|
||||
## signing and verifying attestations
|
||||
See [example_sign_test.go](./pkg/attest/example_sign_test.go)
|
||||
|
||||
2
codecov.yml
Normal file
2
codecov.yml
Normal file
@@ -0,0 +1,2 @@
|
||||
ignore:
|
||||
- "internal/test"
|
||||
90
go.mod
90
go.mod
@@ -4,31 +4,41 @@ go 1.22.1
|
||||
|
||||
require (
|
||||
github.com/Masterminds/semver/v3 v3.2.1
|
||||
github.com/aws/aws-sdk-go-v2/config v1.27.15
|
||||
github.com/containerd/containerd v1.7.17
|
||||
github.com/aws/aws-sdk-go-v2/config v1.27.24
|
||||
github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20231024185945-8841054dbdb8
|
||||
github.com/containerd/containerd v1.7.19
|
||||
github.com/distribution/reference v0.6.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/google/go-containerregistry v0.19.2
|
||||
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.64.1
|
||||
github.com/open-policy-agent/opa v0.66.0
|
||||
github.com/opencontainers/image-spec v1.1.0
|
||||
github.com/package-url/packageurl-go v0.1.3
|
||||
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
|
||||
github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.3
|
||||
github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.6
|
||||
github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.8.6
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/testcontainers/testcontainers-go v0.31.0
|
||||
github.com/testcontainers/testcontainers-go/modules/registry v0.31.0
|
||||
github.com/theupdateframework/go-tuf/v2 v2.0.0-20240504210453-5a634eb214ae // for https://github.com/theupdateframework/go-tuf/pull/632
|
||||
google.golang.org/api v0.187.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
sigs.k8s.io/yaml v1.4.0
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.115.0 // indirect
|
||||
cloud.google.com/go/auth v0.6.1 // indirect
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.3.0 // indirect
|
||||
cloud.google.com/go/iam v1.1.8 // indirect
|
||||
cloud.google.com/go/kms v1.18.0 // indirect
|
||||
cloud.google.com/go/longrunning v0.5.7 // indirect
|
||||
dario.cat/mergo v1.0.0 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
@@ -37,25 +47,28 @@ require (
|
||||
github.com/ProtonMail/go-crypto v1.0.0 // 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.27.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.15 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.30.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.24 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.9 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.13 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.13 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.9 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/kms v1.31.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.20.8 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.28.9 // indirect
|
||||
github.com/aws/smithy-go v1.20.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ecr v1.29.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.24.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.15 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/kms v1.34.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.22.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.30.1 // indirect
|
||||
github.com/aws/smithy-go v1.20.3 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/blang/semver v3.5.1+incompatible // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/cloudflare/circl v1.3.8 // indirect
|
||||
github.com/containerd/log v0.1.0 // indirect
|
||||
github.com/containerd/platforms v0.2.1 // indirect
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.15.1 // indirect
|
||||
github.com/cpuguy83/dockercfg v0.3.1 // indirect
|
||||
github.com/cyberphone/json-canonicalization v0.0.0-20231217050601-ba74d44ecf5f // indirect
|
||||
@@ -74,7 +87,7 @@ require (
|
||||
github.com/go-chi/chi v4.1.2+incompatible // indirect
|
||||
github.com/go-ini/ini v1.67.0 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.0.1 // indirect
|
||||
github.com/go-logr/logr v1.4.1 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||
github.com/go-openapi/analysis v0.23.0 // indirect
|
||||
@@ -87,18 +100,24 @@ require (
|
||||
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/groupcache v0.0.0-20210331224755-41bb18bfe9da // 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/s2a-go v0.1.7 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.12.5 // indirect
|
||||
github.com/gorilla/mux v1.8.1 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.6 // indirect
|
||||
github.com/hashicorp/hcl v1.0.1-vault-5 // 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
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/klauspost/compress v1.17.8 // indirect
|
||||
github.com/letsencrypt/boulder v0.0.0-20240515153123-6ae6aa8e9055 // indirect
|
||||
github.com/letsencrypt/boulder v0.0.0-20240620165639-de9c06129bec // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
@@ -128,13 +147,13 @@ require (
|
||||
github.com/shibumi/go-pathspec v1.3.0 // indirect
|
||||
github.com/shirou/gopsutil/v3 v3.24.4 // indirect
|
||||
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||
github.com/sigstore/sigstore v1.8.3 // indirect
|
||||
github.com/sigstore/sigstore v1.8.6 // indirect
|
||||
github.com/sigstore/timestamp-authority v1.2.2 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spf13/afero v1.11.0 // indirect
|
||||
github.com/spf13/cast v1.6.0 // indirect
|
||||
github.com/spf13/cobra v1.8.0 // indirect
|
||||
github.com/spf13/cobra v1.8.1 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/spf13/viper v1.18.2 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
@@ -151,23 +170,30 @@ require (
|
||||
github.com/yashtewari/glob-intersection v0.2.0 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
go.mongodb.org/mongo-driver v1.15.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 // indirect
|
||||
go.opentelemetry.io/otel v1.26.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.26.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.26.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.26.0 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.52.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 // indirect
|
||||
go.opentelemetry.io/otel v1.27.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.27.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.27.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.27.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
golang.org/x/crypto v0.23.0 // indirect
|
||||
golang.org/x/crypto v0.24.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect
|
||||
golang.org/x/mod v0.17.0 // indirect
|
||||
golang.org/x/net v0.26.0 // indirect
|
||||
golang.org/x/oauth2 v0.21.0 // indirect
|
||||
golang.org/x/sync v0.7.0 // indirect
|
||||
golang.org/x/sys v0.20.0 // indirect
|
||||
golang.org/x/term v0.20.0 // indirect
|
||||
golang.org/x/text v0.15.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 // indirect
|
||||
golang.org/x/sys v0.21.0 // indirect
|
||||
golang.org/x/term v0.21.0 // indirect
|
||||
golang.org/x/text v0.16.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20240624140628-dc46fd24d27d // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240617180043-68d350f18fd4 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240624140628-dc46fd24d27d // indirect
|
||||
google.golang.org/grpc v1.64.0 // indirect
|
||||
google.golang.org/protobuf v1.34.1 // indirect
|
||||
google.golang.org/protobuf v1.34.2 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
k8s.io/klog/v2 v2.120.1 // indirect
|
||||
|
||||
278
go.sum
278
go.sum
@@ -1,12 +1,18 @@
|
||||
cloud.google.com/go v0.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM=
|
||||
cloud.google.com/go/compute v1.25.1 h1:ZRpHJedLtTpKgr3RV1Fx23NuaAEN1Zfx9hw1u4aJdjU=
|
||||
cloud.google.com/go/compute v1.25.1/go.mod h1:oopOIR53ly6viBYxaDhBfJwzUAxf1zE//uf3IB011ls=
|
||||
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
|
||||
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
|
||||
cloud.google.com/go/iam v1.1.6 h1:bEa06k05IO4f4uJonbB5iAgKTPpABy1ayxaIZV/GHVc=
|
||||
cloud.google.com/go/iam v1.1.6/go.mod h1:O0zxdPeGBoFdWW3HWmBxJsk0pfvNM/p/qa82rWOGTwI=
|
||||
cloud.google.com/go/kms v1.15.8 h1:szIeDCowID8th2i8XE4uRev5PMxQFqW+JjwYxL9h6xs=
|
||||
cloud.google.com/go/kms v1.15.8/go.mod h1:WoUHcDjD9pluCg7pNds131awnH429QGvRM3N/4MyoVs=
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.115.0 h1:CnFSK6Xo3lDYRoBKEcAtia6VSC837/ZkJuRduSFnr14=
|
||||
cloud.google.com/go v0.115.0/go.mod h1:8jIM5vVgoAEoiVxQ/O4BFTfHqulPZgs/ufEzMcFMdWU=
|
||||
cloud.google.com/go/auth v0.6.1 h1:T0Zw1XM5c1GlpN2HYr2s+m3vr1p2wy+8VN+Z1FKxW38=
|
||||
cloud.google.com/go/auth v0.6.1/go.mod h1:eFHG7zDzbXHKmjJddFG/rBlcGp6t25SwRUiEQSlO4x4=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q=
|
||||
cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc=
|
||||
cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
|
||||
cloud.google.com/go/iam v1.1.8 h1:r7umDwhj+BQyz0ScZMp4QrGXjSTI3ZINnpgU2nlB/K0=
|
||||
cloud.google.com/go/iam v1.1.8/go.mod h1:GvE6lyMmfxXauzNq8NbgJbeVQNspG+tcdL/W8QO1+zE=
|
||||
cloud.google.com/go/kms v1.18.0 h1:pqNdaVmZJFP+i8OVLocjfpdTWETTYa20FWOegSCdrRo=
|
||||
cloud.google.com/go/kms v1.18.0/go.mod h1:DyRBeWD/pYBMeyiaXFa/DGNyxMDL3TslIKb8o/JkLkw=
|
||||
cloud.google.com/go/longrunning v0.5.7 h1:WLbHekDbjK1fVFD3ibpFFVoyizlLRl73I7YKuAKilhU=
|
||||
cloud.google.com/go/longrunning v0.5.7/go.mod h1:8GClkudohy1Fxm3owmBGid8W0pSgodEMwEAztp38Xng=
|
||||
cuelabs.dev/go/oci/ociregistry v0.0.0-20240314152124-224736b49f2e h1:GwCVItFUPxwdsEYnlUcJ6PJxOjTeFFCKOh6QWg4oAzQ=
|
||||
cuelabs.dev/go/oci/ociregistry v0.0.0-20240314152124-224736b49f2e/go.mod h1:ApHceQLLwcOkCEXM1+DyCXTHEJhNGDpJ2kmV6axsx24=
|
||||
cuelang.org/go v0.8.1 h1:VFYsxIFSPY5KgSaH1jQ2GxHOrbu6Ga3kEI70yCZwnOg=
|
||||
@@ -53,6 +59,7 @@ github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUM
|
||||
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0=
|
||||
github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
@@ -95,40 +102,40 @@ github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig
|
||||
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=
|
||||
github.com/aws/aws-sdk-go v1.51.6/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk=
|
||||
github.com/aws/aws-sdk-go-v2 v1.27.0 h1:7bZWKoXhzI+mMR/HjdMx8ZCC5+6fY0lS5tr0bbgiLlo=
|
||||
github.com/aws/aws-sdk-go-v2 v1.27.0/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.27.15 h1:uNnGLZ+DutuNEkuPh6fwqK7LpEiPmzb7MIMA1mNWEUc=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.27.15/go.mod h1:7j7Kxx9/7kTmL7z4LlhwQe63MYEE5vkVV6nWg4ZAI8M=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.15 h1:YDexlvDRCA8ems2T5IP1xkMtOZ1uLJOCJdTr0igs5zo=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.15/go.mod h1:vxHggqW6hFNaeNC0WyXS3VdyjcV0a4KMUY4dKJ96buU=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.3 h1:dQLK4TjtnlRGb0czOht2CevZ5l6RSyRWAnKeGd7VAFE=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.3/go.mod h1:TL79f2P6+8Q7dTsILpiVST+AL9lkF6PPGI167Ny0Cjw=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.7 h1:lf/8VTF2cM+N4SLzaYJERKEWAXq8MOMpZfU6wEPWsPk=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.7/go.mod h1:4SjkU7QiqK2M9oozyMzfZ/23LmUY+h3oFqhdeP5OMiI=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.7 h1:4OYVp0705xu8yjdyoWix0r9wPIRXnIzzOoUpQVHIJ/g=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.7/go.mod h1:vd7ESTEvI76T2Na050gODNmNU7+OyKrIKroYTu4ABiI=
|
||||
github.com/aws/aws-sdk-go v1.54.6 h1:HEYUib3yTt8E6vxjMWM3yAq5b+qjj/6aKA62mkgux9g=
|
||||
github.com/aws/aws-sdk-go v1.54.6/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
|
||||
github.com/aws/aws-sdk-go-v2 v1.30.1 h1:4y/5Dvfrhd1MxRDD77SrfsDaj8kUkkljU7XE83NPV+o=
|
||||
github.com/aws/aws-sdk-go-v2 v1.30.1/go.mod h1:nIQjQVp5sfpQcTc9mPSr1B0PaWK5ByX9MOoDadSN4lc=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.27.24 h1:NM9XicZ5o1CBU/MZaHwFtimRpWx9ohAUAqkG6AqSqPo=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.27.24/go.mod h1:aXzi6QJTuQRVVusAO8/NxpdTeTyr/wRcybdDtfUwJSs=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.24 h1:YclAsrnb1/GTQNt2nzv+756Iw4mF8AOzcDfweWwwm/M=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.24/go.mod h1:Hld7tmnAkoBQdTMNYZGzztzKRdA4fCdn9L83LOoigac=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.9 h1:Aznqksmd6Rfv2HQN9cpqIV/lQRMaIpJkLLaJ1ZI76no=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.9/go.mod h1:WQr3MY7AxGNxaqAtsDWn+fBxmd4XvLkzeqQ8P1VM0/w=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.13 h1:5SAoZ4jYpGH4721ZNoS1znQrhOfZinOhc4XuTXx/nVc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.13/go.mod h1:+rdA6ZLpaSeM7tSg/B0IEDinCIBJGmW8rKDFkYpP04g=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.13 h1:WIijqeaAO7TYFLbhsZmi2rgLEAtWOC1LhxCAVTJlSKw=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.13/go.mod h1:i+kbfa76PQbWw/ULoWnp51EYVWH4ENln76fLQE3lXT8=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY=
|
||||
github.com/aws/aws-sdk-go-v2/service/ecr v1.20.2 h1:y6LX9GUoEA3mO0qpFl1ZQHj1rFyPWVphlzebiSt2tKE=
|
||||
github.com/aws/aws-sdk-go-v2/service/ecr v1.20.2/go.mod h1:Q0LcmaN/Qr8+4aSBrdrXXePqoX0eOuYpJLbYpilmWnA=
|
||||
github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.18.2 h1:PpbXaecV3sLAS6rjQiaKw4/jyq3Z8gNzmoJupHAoBp0=
|
||||
github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.18.2/go.mod h1:fUHpGXr4DrXkEDpGAjClPsviWf+Bszeb0daKE0blxv8=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 h1:Ji0DY1xUsUr3I8cHps0G+XM3WWU16lP6yG8qu1GAZAs=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2/go.mod h1:5CsjAbs3NlGQyZNFACh+zztPDI7fU6eW9QsxjfnuBKg=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.9 h1:Wx0rlZoEJR7JwlSZcHnEa7CNjrSIyVxMFWGAaXy4fJY=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.9/go.mod h1:aVMHdE0aHO3v+f/iw01fmXV/5DbfQ3Bi9nN7nd9bE9Y=
|
||||
github.com/aws/aws-sdk-go-v2/service/kms v1.31.2 h1:z4NOTY1sm0Vb/+Kovnbf8TLPcH8P36bILR5hgXE1sOY=
|
||||
github.com/aws/aws-sdk-go-v2/service/kms v1.31.2/go.mod h1:6HNwTCo40yDvnmgT/NgRgWsx0/0bN2TV6RO5FfG8G60=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.20.8 h1:Kv1hwNG6jHC/sxMTe5saMjH6t6ZLkgfvVxyEjfWL1ks=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.20.8/go.mod h1:c1qtZUWtygI6ZdvKppzCSXsDOq5I4luJPZ0Ud3juFCA=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.2 h1:nWBZ1xHCF+A7vv9sDzJOq4NWIdzFYm0kH7Pr4OjHYsQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.2/go.mod h1:9lmoVDVLz/yUZwLaQ676TK02fhCu4+PgRSmMaKR1ozk=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.28.9 h1:Qp6Boy0cGDloOE3zI6XhNLNZgjNS8YmiFQFHe71SaW0=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.28.9/go.mod h1:0Aqn1MnEuitqfsCNyKsdKLhDUOr4txD/g19EfiUqgws=
|
||||
github.com/aws/smithy-go v1.20.2 h1:tbp628ireGtzcHDDmLT/6ADHidqnwgF57XOXZe6tp4Q=
|
||||
github.com/aws/smithy-go v1.20.2/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E=
|
||||
github.com/aws/aws-sdk-go-v2/service/ecr v1.29.1 h1:ywNLJrn/Qn4enDsz/XnKlvpnLqvJxFGQV2BltWltbis=
|
||||
github.com/aws/aws-sdk-go-v2/service/ecr v1.29.1/go.mod h1:WadVIk+UrTvWuAsCp6BKGX4i2snurpz8mPWhJQnS7Dg=
|
||||
github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.24.1 h1:Eq9i/mvOlGghiKe9NtsmeD9Wlwg8p4fbsqrMb3nWirM=
|
||||
github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.24.1/go.mod h1:VtOgEoLEPV1YADuq+Z2XOK6/wKkGW2YK6DjChZ/GvDs=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 h1:dT3MqvGhSoaIhRseqw2I0yH81l7wiR2vjs57O51EAm8=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3/go.mod h1:GlAeCkHwugxdHaueRr4nhPuY+WW+gR8UjlcqzPr1SPI=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.15 h1:I9zMeF107l0rJrpnHpjEiiTSCKYAIw8mALiXcPsGBiA=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.15/go.mod h1:9xWJ3Q/S6Ojusz1UIkfycgD1mGirJfLLKqq3LPT7WN8=
|
||||
github.com/aws/aws-sdk-go-v2/service/kms v1.34.1 h1:VsKBn6WADI3Nn3WjBMzeRww9WHXeVLi7zyuSrqjRCBQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/kms v1.34.1/go.mod h1:5F6kXrPBxv0l1t8EO44GuG4W82jGJwaRE0B+suEGnNY=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.22.1 h1:p1GahKIjyMDZtiKoIn0/jAj/TkMzfzndDv5+zi2Mhgc=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.22.1/go.mod h1:/vWdhoIoYA5hYoPZ6fm7Sv4d8701PiG5VKe8/pPJL60=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.2 h1:ORnrOK0C4WmYV/uYt3koHEWBLYsRDwk2Np+eEoyV4Z0=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.2/go.mod h1:xyFHA4zGxgYkdD73VeezHt3vSKEG9EmFnGwoKlP00u4=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.30.1 h1:+woJ607dllHJQtsnJLi52ycuqHMwlW+Wqm2Ppsfp4nQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.30.1/go.mod h1:jiNR3JqT15Dm+QWq2SRgh0x0bCNSRP2L25+CqPNpJlQ=
|
||||
github.com/aws/smithy-go v1.20.3 h1:ryHwveWzPV5BIof6fyDvor6V3iUL7nTfiTKXHiW05nE=
|
||||
github.com/aws/smithy-go v1.20.3/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E=
|
||||
github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20231024185945-8841054dbdb8 h1:SoFYaT9UyGkR0+nogNyD/Lj+bsixB+SNuAS4ABlEs6M=
|
||||
github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20231024185945-8841054dbdb8/go.mod h1:2JF49jcDOrLStIXN/j/K1EKRq8a8R2qRnlZA6/o/c7c=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
@@ -148,6 +155,7 @@ github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTx
|
||||
github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
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.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
@@ -159,26 +167,30 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME=
|
||||
github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
|
||||
github.com/cloudflare/circl v1.3.8 h1:j+V8jJt09PoeMFIu2uh5JUyEaIHTXVOHslFoLNAKqwI=
|
||||
github.com/cloudflare/circl v1.3.8/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZFnBQS5QU=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cockroachdb/apd/v3 v3.2.1 h1:U+8j7t0axsIgvQUqthuNm82HIrYXodOV2iWLWtEaIwg=
|
||||
github.com/cockroachdb/apd/v3 v3.2.1/go.mod h1:klXJcjp+FffLTHlhIG69tezTDvdP065naDsHzKhYSqc=
|
||||
github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE=
|
||||
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.17 h1:KjNnn0+tAVQHAoaWRjmdak9WlvnFR/8rU1CHHy8Rm2A=
|
||||
github.com/containerd/containerd v1.7.17/go.mod h1:vK+hhT4TIv2uejlcDlbVIc8+h/BqtKLIyNrtCZol8lI=
|
||||
github.com/containerd/containerd v1.7.19 h1:/xQ4XRJ0tamDkdzrrBAUy/LE5nCcxFKdBm4EcPrSMEE=
|
||||
github.com/containerd/containerd v1.7.19/go.mod h1:h4FtNYUUMB4Phr6v+xG89RYKj9XccvbNSCKjdufCrkc=
|
||||
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/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A=
|
||||
github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw=
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.15.1 h1:eXJjw9RbkLFgioVaTG+G/ZW/0kEe2oEKCdS/ZxIyoCU=
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.15.1/go.mod h1:gr2RNwukQ/S9Nv33Lt6UC7xEx58C+LHRdoqbEKjz1Kk=
|
||||
github.com/coreos/go-oidc/v3 v3.10.0 h1:tDnXHnLyiTVyT/2zLDGj09pFPkhND8Gl8lnTRhoEaJU=
|
||||
github.com/coreos/go-oidc/v3 v3.10.0/go.mod h1:5j11xcw0D3+SGxn6Z/WFADsgcWVMyNAlSQupk0KK3ac=
|
||||
github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E=
|
||||
github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
|
||||
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||
github.com/cyberphone/json-canonicalization v0.0.0-20231217050601-ba74d44ecf5f h1:eHnXnuK47UlSTOQexbzxAZfekVz6i+LKRdj1CU5DPaM=
|
||||
@@ -222,6 +234,10 @@ github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxER
|
||||
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
github.com/emicklei/proto v1.12.1 h1:6n/Z2pZAnBwuhU66Gs8160B8rrrYKo7h2F2sCOnNceE=
|
||||
github.com/emicklei/proto v1.12.1/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
@@ -246,8 +262,8 @@ github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQr
|
||||
github.com/go-jose/go-jose/v4 v4.0.1 h1:QVEPDE3OluqXBQZDcnNvQrInro2h0e4eqNbnZSWqS6U=
|
||||
github.com/go-jose/go-jose/v4 v4.0.1/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
||||
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
@@ -276,8 +292,8 @@ github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1
|
||||
github.com/go-piv/piv-go v1.11.0 h1:5vAaCdRTFSIW4PeqMbnsDlUZ7odMYWnHBDGdmtU/Zhg=
|
||||
github.com/go-piv/piv-go v1.11.0/go.mod h1:NZ2zmjVkfFaL/CF8cVQ/pXdXtuj110zEKGdJM6fJZZM=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg=
|
||||
github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
||||
github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U=
|
||||
github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
||||
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
|
||||
@@ -288,17 +304,23 @@ 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 v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
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-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
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/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
@@ -311,16 +333,19 @@ github.com/google/flatbuffers v2.0.8+incompatible h1:ivUb1cGomAB101ZM1T0nOiWz9pS
|
||||
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.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-containerregistry v0.19.1 h1:yMQ62Al6/V0Z7CqIrrS1iYoA5/oQCm88DeNujc7C1KY=
|
||||
github.com/google/go-containerregistry v0.19.1/go.mod h1:YCMFNQeeXeLF+dnhhWkqDItx/JSkH01j1Kis4PsjzFI=
|
||||
github.com/google/go-containerregistry v0.19.2 h1:TannFKE1QSajsP6hPWb5oJNgKe1IKjHukIKDUmvsV6w=
|
||||
github.com/google/go-containerregistry v0.19.2/go.mod h1:YCMFNQeeXeLF+dnhhWkqDItx/JSkH01j1Kis4PsjzFI=
|
||||
github.com/google/go-github/v55 v55.0.0 h1:4pp/1tNMB9X/LuAhs5i0KQAE40NmiR/y6prLNb9x9cg=
|
||||
github.com/google/go-github/v55 v55.0.0/go.mod h1:JLahOTA1DnXzhxEymmFF5PP2tSS9JVNj68mSZNDwskA=
|
||||
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||
@@ -334,17 +359,18 @@ github.com/google/tink/go v1.7.0 h1:6Eox8zONGebBFcCBqkVmt60LaWZa6xg1cl/DwAh/J1w=
|
||||
github.com/google/tink/go v1.7.0/go.mod h1:GAUOd+QE3pgj9q8VKIGTCP33c/B7eb4NhxLcgTJZStM=
|
||||
github.com/google/trillian v1.6.0 h1:jMBeDBIkINFvS2n6oV5maDqfRlxREAc6CW9QYWQ0qT4=
|
||||
github.com/google/trillian v1.6.0/go.mod h1:Yu3nIMITzNhhMJEHjAtp6xKiu+H/iHu2Oq5FjV2mCWI=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
|
||||
github.com/googleapis/gax-go/v2 v2.12.3 h1:5/zPPDvw8Q1SuXjrqrZslrqT7dL/uJT2CQii/cLCKqA=
|
||||
github.com/googleapis/gax-go/v2 v2.12.3/go.mod h1:AKloxT6GtNbaLm8QTNSidHUVsHYcBHwWRvkNFJUQcS4=
|
||||
github.com/googleapis/gax-go/v2 v2.12.5 h1:8gw9KZK8TiVKB6q3zHY3SBzLnrGp6HQjyfYBYGmXdxA=
|
||||
github.com/googleapis/gax-go/v2 v2.12.5/go.mod h1:BUDKcWo+RaKq5SC9vVYL0wLADa3VcfswbOMMRmB9H3E=
|
||||
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0 h1:RtRsiaGvWxcwd8y3BiRZxsylPT8hLWZ5SPcfI+3IDNk=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0/go.mod h1:TzP6duP4Py2pHLVPPQp42aoYI92+PCrVotyR5e8Vqlk=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k=
|
||||
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
|
||||
@@ -383,6 +409,8 @@ github.com/jellydator/ttlcache/v3 v3.2.0 h1:6lqVJ8X3ZaUwvzENqPAobDsXNExfUJd61u++
|
||||
github.com/jellydator/ttlcache/v3 v3.2.0/go.mod h1:hi7MGFdMAwZna5n2tuvh63DvFLzVKySzCVW6+0gA2n4=
|
||||
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
|
||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
||||
github.com/jmhodges/clock v1.2.0 h1:eq4kys+NI0PLngzaHEe7AmPT90XMGIEySD1JfV1PDIs=
|
||||
github.com/jmhodges/clock v1.2.0/go.mod h1:qKjhA7x7u/lQpPB1XAqX1b1lCI/w3/fNuYpI/ZjLynI=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
@@ -399,8 +427,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/letsencrypt/boulder v0.0.0-20240515153123-6ae6aa8e9055 h1:sl8s8GXv/oHUSid9gd4B+Rovu9DOW4PxnKT2rNRfmzM=
|
||||
github.com/letsencrypt/boulder v0.0.0-20240515153123-6ae6aa8e9055/go.mod h1:wGJPvcZTEexA3UpMx+4cZ19nk6gRrzrdW4jFEPsEqf0=
|
||||
github.com/letsencrypt/boulder v0.0.0-20240620165639-de9c06129bec h1:2tTW6cDth2TSgRbAhD7yjZzTQmcN25sDRPEeinR51yQ=
|
||||
github.com/letsencrypt/boulder v0.0.0-20240620165639-de9c06129bec/go.mod h1:TmwEoGCwIti7BCeJ9hescZgRtatxRE+A72pCoPfmcfk=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||
github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae h1:dIZY4ULFcto4tAFlj1FYZl8ztUZ13bdq+PLY+NOfbyI=
|
||||
github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k=
|
||||
@@ -463,8 +491,8 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y
|
||||
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||
github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw=
|
||||
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
|
||||
github.com/open-policy-agent/opa v0.64.1 h1:n8IJTYlFWzqiOYx+JiawbErVxiqAyXohovcZxYbskxQ=
|
||||
github.com/open-policy-agent/opa v0.64.1/go.mod h1:j4VeLorVpKipnkQ2TDjWshEuV3cvP/rHzQhYaraUXZY=
|
||||
github.com/open-policy-agent/opa v0.66.0 h1:DbrvfJQja0FBRcPOB3Z/BOckocN+M4ApNWyNhSRJt0w=
|
||||
github.com/open-policy-agent/opa v0.66.0/go.mod h1:EIgNnJcol7AvQR/IcWLwL13k64gHVbNAVG46b2G+/EY=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
|
||||
@@ -489,6 +517,7 @@ github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
|
||||
github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
||||
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
||||
github.com/prometheus/common v0.53.0 h1:U2pL9w9nmJwJDa4qqLQ3ZaePJ6ZTwt7cMD3AG3+aLCE=
|
||||
@@ -530,14 +559,14 @@ github.com/sigstore/fulcio v1.4.5 h1:WWNnrOknD0DbruuZWCbN+86WRROpEl3Xts+WT2Ek1yc
|
||||
github.com/sigstore/fulcio v1.4.5/go.mod h1:oz3Qwlma8dWcSS/IENR/6SjbW4ipN0cxpRVfgdsjMU8=
|
||||
github.com/sigstore/rekor v1.3.6 h1:QvpMMJVWAp69a3CHzdrLelqEqpTM3ByQRt5B5Kspbi8=
|
||||
github.com/sigstore/rekor v1.3.6/go.mod h1:JDTSNNMdQ/PxdsS49DJkJ+pRJCO/83nbR5p3aZQteXc=
|
||||
github.com/sigstore/sigstore v1.8.3 h1:G7LVXqL+ekgYtYdksBks9B38dPoIsbscjQJX/MGWkA4=
|
||||
github.com/sigstore/sigstore v1.8.3/go.mod h1:mqbTEariiGA94cn6G3xnDiV6BD8eSLdL/eA7bvJ0fVs=
|
||||
github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.3 h1:LTfPadUAo+PDRUbbdqbeSl2OuoFQwUFTnJ4stu+nwWw=
|
||||
github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.3/go.mod h1:QV/Lxlxm0POyhfyBtIbTWxNeF18clMlkkyL9mu45y18=
|
||||
github.com/sigstore/sigstore v1.8.6 h1:g066b/Nw5r5oxhNv4XqJUUzVcyf1b07itUueiQe7rZM=
|
||||
github.com/sigstore/sigstore v1.8.6/go.mod h1:UOBrJd9JBQ81DrkpGljzsIFXEtfC30raHvLWFWG857U=
|
||||
github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.6 h1:uVcT1JT4lLkmBQII25PvgP/nyvi4HvTMNXzoHqQqEHE=
|
||||
github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.6/go.mod h1:VJ/745ojKNQKbZ1ykO5Vebtnq9vGt8zcgKemQIibBIE=
|
||||
github.com/sigstore/sigstore/pkg/signature/kms/azure v1.8.3 h1:xgbPRCr2npmmsuVVteJqi/ERw9+I13Wou7kq0Yk4D8g=
|
||||
github.com/sigstore/sigstore/pkg/signature/kms/azure v1.8.3/go.mod h1:G4+I83FILPX6MtnoaUdmv/bRGEVtR3JdLeJa/kXdk/0=
|
||||
github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.8.3 h1:vDl2fqPT0h3D/k6NZPlqnKFd1tz3335wm39qjvpZNJc=
|
||||
github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.8.3/go.mod h1:9uOJXbXEXj+M6QjMKH5PaL5WDMu43rHfbIMgXzA8eKI=
|
||||
github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.8.6 h1:CFtW7RIQ4fOtBzl+1YAnAmcACL4B+Qr/S7PXPdJ+54s=
|
||||
github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.8.6/go.mod h1:rhX2eca5kAqUTwQxQLMnOLmvSxbqF9JZ3rFOoDpQX5w=
|
||||
github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.8.3 h1:h9G8j+Ds21zqqulDbA/R/ft64oQQIyp8S7wJYABYSlg=
|
||||
github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.8.3/go.mod h1:zgCeHOuqF6k7A7TTEvftcA9V3FRzB7mrPtHOhXAQBnc=
|
||||
github.com/sigstore/timestamp-authority v1.2.2 h1:X4qyutnCQqJ0apMewFyx+3t7Tws00JQ/JonBiu3QvLE=
|
||||
@@ -552,8 +581,8 @@ github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
|
||||
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
|
||||
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
|
||||
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
|
||||
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
|
||||
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
|
||||
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ=
|
||||
@@ -570,6 +599,7 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
@@ -624,26 +654,26 @@ go.mongodb.org/mongo-driver v1.15.0 h1:rJCKC8eEliewXjZGf0ddURtl7tTVy1TK3bfl0gkUS
|
||||
go.mongodb.org/mongo-driver v1.15.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c=
|
||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 h1:Xs2Ncz0gNihqu9iosIZ5SkBbWo5T8JhhLJFMQL1qmLI=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0/go.mod h1:vy+2G/6NvVMpwGX/NyLqcC41fxepnuKHk16E6IZUcJc=
|
||||
go.opentelemetry.io/otel v1.26.0 h1:LQwgL5s/1W7YiiRwxf03QGnWLb2HW4pLiAhaA5cZXBs=
|
||||
go.opentelemetry.io/otel v1.26.0/go.mod h1:UmLkJHUAidDval2EICqBMbnAd0/m2vmpf/dAM+fvFs4=
|
||||
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/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.52.0 h1:vS1Ao/R55RNV4O7TA2Qopok8yN+X0LIP6RVWLFkprck=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.52.0/go.mod h1:BMsdeOxN04K0L5FNUBfjFdvwWGNe/rkmSwH4Aelu/X0=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 h1:9l89oX4ba9kHbBol3Xin3leYJ+252h0zszDtBwyKe2A=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0/go.mod h1:XLZfZboOJWHNKUv7eH0inh0E9VV6eWDFB/9yJyTLPp0=
|
||||
go.opentelemetry.io/otel v1.27.0 h1:9BZoF3yMK/O1AafMiQTVu0YDj5Ea4hPhxCs7sGva+cg=
|
||||
go.opentelemetry.io/otel v1.27.0/go.mod h1:DMpAK8fzYRzs+bi3rS5REupisuqTheUlSZJ1WnZaPAQ=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0 h1:R9DE4kQ4k+YtfLI2ULwX82VtNQ2J8yZmA7ZIF/D+7Mc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0/go.mod h1:OQFyQVrDlbe+R7xrEyDr/2Wr67Ol0hRUgsfA+V5A95s=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 h1:qFffATk0X+HD+f1Z8lswGiOQYKHRlzfmdJm0wEaVrFA=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0/go.mod h1:MOiCmryaYtc+V0Ei+Tx9o5S1ZjA7kzLucuVuyzBZloQ=
|
||||
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.26.0 h1:7S39CLuY5Jgg9CrnA9HHiEjGMF/X2VHvoXGgSllRz30=
|
||||
go.opentelemetry.io/otel/metric v1.26.0/go.mod h1:SY+rHOI4cEawI9a7N1A4nIg/nTQXe1ccCNWYOJUrpX4=
|
||||
go.opentelemetry.io/otel/sdk v1.26.0 h1:Y7bumHf5tAiDlRYFmGqetNcLaVUZmh4iYfmGxtmz7F8=
|
||||
go.opentelemetry.io/otel/sdk v1.26.0/go.mod h1:0p8MXpqLeJ0pzcszQQN4F0S5FVjBLgypeGSngLsmirs=
|
||||
go.opentelemetry.io/otel/trace v1.26.0 h1:1ieeAUb4y0TE26jUFrCIXKpTuVK7uJGN9/Z/2LP5sQA=
|
||||
go.opentelemetry.io/otel/trace v1.26.0/go.mod h1:4iDxvGDQuUkHve82hJJ8UqrwswHYsZuWCBllGV2U2y0=
|
||||
go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
|
||||
go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
|
||||
go.opentelemetry.io/otel/metric v1.27.0 h1:hvj3vdEKyeCi4YaYfNjv2NUje8FqKqUY8IlF0FxV/ik=
|
||||
go.opentelemetry.io/otel/metric v1.27.0/go.mod h1:mVFgmRlhljgBiuk/MP/oKylr4hs85GZAylncepAX/ak=
|
||||
go.opentelemetry.io/otel/sdk v1.27.0 h1:mlk+/Y1gLPLn84U4tI8d3GNJmGT/eXe3ZuOXN9kTWmI=
|
||||
go.opentelemetry.io/otel/sdk v1.27.0/go.mod h1:Ha9vbLwJE6W86YstIywK2xFfPjbWlCuwPtMkKdz/Y4A=
|
||||
go.opentelemetry.io/otel/trace v1.27.0 h1:IqYb813p7cmbHk0a5y6pD5JPakbVfftRXABGt5/Rscw=
|
||||
go.opentelemetry.io/otel/trace v1.27.0/go.mod h1:6RiD1hkAprV4/q+yd2ln1HG9GoPx39SuvvstaLBl+l4=
|
||||
go.opentelemetry.io/proto/otlp v1.2.0 h1:pVeZGk7nXDC9O2hncA6nHldxEjm6LByfA2aN8IOkz94=
|
||||
go.opentelemetry.io/proto/otlp v1.2.0/go.mod h1:gGpR8txAl5M03pDhMC79G6SdqNV26naRm/KDsgaHD8A=
|
||||
go.step.sm/crypto v0.44.2 h1:t3p3uQ7raP2jp2ha9P6xkQF85TJZh+87xmjSLaib+jk=
|
||||
go.step.sm/crypto v0.44.2/go.mod h1:x1439EnFhadzhkuaGX7sz03LEMQ+jV4gRamf5LCZJQQ=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
@@ -658,22 +688,31 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
|
||||
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
@@ -682,11 +721,13 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
|
||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/oauth2 v0.19.0 h1:9+E/EZBCbTLNrbN35fHv/a/d/mOBatymz1zbtQrXpIg=
|
||||
golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8=
|
||||
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
|
||||
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs=
|
||||
golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -694,6 +735,7 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -723,15 +765,15 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
|
||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
|
||||
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
@@ -739,32 +781,46 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw=
|
||||
golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
|
||||
google.golang.org/api v0.172.0 h1:/1OcMZGPmW1rX2LCu2CmGUD1KXK1+pfzxotxyRUCCdk=
|
||||
google.golang.org/api v0.172.0/go.mod h1:+fJZq6QXWfa9pXhnIzsjx4yI22d4aI9ZpLb58gvXjis=
|
||||
google.golang.org/genproto v0.0.0-20240311173647-c811ad7063a7 h1:ImUcDPHjTrAqNhlOkSocDLfG9rrNHH7w7uoKWPaWZ8s=
|
||||
google.golang.org/genproto v0.0.0-20240311173647-c811ad7063a7/go.mod h1:/3XmxOjePkvmKrHuBy4zNFw7IzxJXtAgdpXi8Ll990U=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 h1:AgADTJarZTBqgjiUzRgfaBchgYB3/WFTC80GPwsMcRI=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0=
|
||||
google.golang.org/api v0.187.0 h1:Mxs7VATVC2v7CY+7Xwm4ndkX71hpElcvx0D1Ji/p1eo=
|
||||
google.golang.org/api v0.187.0/go.mod h1:KIHlTc4x7N7gKKuVsdmfBXN13yEEWXWFURWY6SBp2gk=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20240624140628-dc46fd24d27d h1:PksQg4dV6Sem3/HkBX+Ltq8T0ke0PKIRBNBatoDTVls=
|
||||
google.golang.org/genproto v0.0.0-20240624140628-dc46fd24d27d/go.mod h1:s7iA721uChleev562UJO2OYB0PPT9CMFjV+Ce7VJH5M=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240617180043-68d350f18fd4 h1:MuYw1wJzT+ZkybKfaOXKp5hJiZDn2iHaXRw0mRYdHSc=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240617180043-68d350f18fd4/go.mod h1:px9SlOOZBg1wM1zdnr8jEL4CNGUBZ+ZKYtNPApNQc4c=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240624140628-dc46fd24d27d h1:k3zyW3BYYR30e8v3x0bTDdE9vpYFjZHK+HcyqkrppWk=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240624140628-dc46fd24d27d/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=
|
||||
google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
@@ -772,11 +828,14 @@ google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
||||
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
@@ -789,6 +848,7 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkep
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
@@ -797,6 +857,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools/v3 v3.5.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY=
|
||||
gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
k8s.io/api v0.28.3 h1:Gj1HtbSdB4P08C8rs9AR94MfSGpRhJgsS+GF9V26xMM=
|
||||
k8s.io/api v0.28.3/go.mod h1:MRCV/jr1dW87/qJnZ57U5Pak65LGmQVkKTzf3AtKFHc=
|
||||
k8s.io/apimachinery v0.28.3 h1:B1wYx8txOaCQG0HmYF6nbpU8dg6HvA06x5tEffvOe7A=
|
||||
|
||||
152
internal/embed/embedded-roots/1.root.json
Normal file
152
internal/embed/embedded-roots/1.root.json
Normal file
@@ -0,0 +1,152 @@
|
||||
{
|
||||
"signatures": [
|
||||
{
|
||||
"keyid": "08d6f4ca1d0be93a6ceddca15051c0aeec6b98c73e29f3a714de301042d6eeee",
|
||||
"sig": "306502307ddba543fbd1b9e2ccbee604349024e62bbb1a37906bbd5605a7403fbdb51b701b52f5fcd1b0a0ebfaeef97fa9c344f8023100c37ab675fe96b3976469a5e0cc8a5ffb5d8d6de15020f493d7cf28b0c7e60f450b65c02bfbac0e40642863a1ae3bfa4a"
|
||||
},
|
||||
{
|
||||
"keyid": "3ebd40525193d7628d0b9eccd4771df7297bc87519ec6f312863bb4470966bea",
|
||||
"sig": "3065023100bc963925fb139dd65653b5e9640572876c5bcd0a3f8bb81e4b0cbd397c10ec4fa0aed7942d77ec78b865e14c72e20e76023043ce7ff39067f054d6d2eaca5dd5176b2c25e27bd763b4ef873aaf4c75762bfb085bb766613692b68206ea0df2863426"
|
||||
},
|
||||
{
|
||||
"keyid": "9c8e1be7d8d0e30656adc81ac201e05cb47a5a097d4d301fd121b77c320231c4",
|
||||
"sig": "306502307e82d7bc0c66074b06cfc13bac3761c8f677eef252c08448eb33c0249569500e8be2a1ae78c87b5888ed80d088f97fbb023100c358c6ebe18d237bae9a9daeaf2db82297cda8eca635fc22719142740fb23b32eac0341754dd2a85b684c46e3a087ada"
|
||||
},
|
||||
{
|
||||
"keyid": "373d0a38247919a78cf400cf9a90abb9aa23a3c3dce1deee995fdd6a81507117",
|
||||
"sig": "306402305d9b5fdf3b24240b266a7ae7e02bbcadce8e06f8c111dcef03282faa0baaffb8114653cecda3da115d7859f657508d4f02304b5939fc4404f9e1e8b9d3eb49e195a779b501bd4000cef6cff7a8e657020176dae99cce2a7300b88e549d427278309c"
|
||||
},
|
||||
{
|
||||
"keyid": "48a873aa6c4189804228590af4d48ee5ad3b76417592efdbcef2532401925669",
|
||||
"sig": "306402306bc5f44621c0d6e18ce16155ebc7890def8fb283859175f7a8425190f0f233e4270b2688df05b017cfc852dee30f9f5b023016572d059d6f27968976df2aaff8238ee0970cea229e5ef30350f2c91347b04e794683da69cf6afe6cf9206dcebc81f4"
|
||||
}
|
||||
],
|
||||
"signed": {
|
||||
"_type": "root",
|
||||
"consistent_snapshot": true,
|
||||
"expires": "2025-06-04T15:05:22Z",
|
||||
"keys": {
|
||||
"08d6f4ca1d0be93a6ceddca15051c0aeec6b98c73e29f3a714de301042d6eeee": {
|
||||
"keytype": "ecdsa",
|
||||
"keyval": {
|
||||
"public": "-----BEGIN PUBLIC KEY-----\nMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEC4ggHc/D9koyS1/AMNsMGiydM2jDzdsI\nrkC/nyZf8d4UtYJJRxuFRfmyKw9Mh0Ulw/IIyf8ZW2NsnkHgJwGre9/Ici6uomOX\n8yAOlX0Du/oAa7v4igCG7tsW0Z1ljAID\n-----END PUBLIC KEY-----\n"
|
||||
},
|
||||
"scheme": "ecdsa-sha2-nistp384",
|
||||
"x-tuf-on-ci-keyowner": "@jeanlaurent"
|
||||
},
|
||||
"2ff207ae7d7b595ef69589622067ef5b6668e1a43081377d942ed8749fa919b4": {
|
||||
"keytype": "ecdsa",
|
||||
"keyval": {
|
||||
"public": "-----BEGIN PUBLIC KEY-----\nMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE5pyJ/RXlRO/a2WBSAprikm+VVPqZGC1M\nqgVXE3avwqb9d9lPc9Cphfd4CIAzPCKgeUkGMzQWcC1OwVjOwiB+GRq2Owf7T8pa\nKUe/zRoLjAlUnzUITHP226L1DmQ6Swos\n-----END PUBLIC KEY-----\n"
|
||||
},
|
||||
"scheme": "ecdsa-sha2-nistp384",
|
||||
"x-tuf-on-ci-keyowner": "@kipz"
|
||||
},
|
||||
"373d0a38247919a78cf400cf9a90abb9aa23a3c3dce1deee995fdd6a81507117": {
|
||||
"keytype": "ecdsa",
|
||||
"keyval": {
|
||||
"public": "-----BEGIN PUBLIC KEY-----\nMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAER2zST05lNvybLsSe4UA/hiUrJbA6aFyz\nDimwewwbHvw+gt29EHYtHPqTlO/hSZD5vqZ94Cga9rDsOm3eI5bPkPHApUjw4W7u\n5lDnxuuFKluQ7EiUbswUN0ONTPnmY7Wo\n-----END PUBLIC KEY-----\n"
|
||||
},
|
||||
"scheme": "ecdsa-sha2-nistp384",
|
||||
"x-tuf-on-ci-keyowner": "@binman-docker"
|
||||
},
|
||||
"3ebd40525193d7628d0b9eccd4771df7297bc87519ec6f312863bb4470966bea": {
|
||||
"keytype": "ecdsa",
|
||||
"keyval": {
|
||||
"public": "-----BEGIN PUBLIC KEY-----\nMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE9C53JKQtD1RYLiSwmR4XRhI7jf28W9TK\nhV3aXW0Z87JyJ4wGNOFnGRE6PuEh7Bbu4ecH0PpsEoirWzzRIgBMR3yHVCSkFBDu\nqfycsInCTAS1jvzLiDHciKXENxAWARHj\n-----END PUBLIC KEY-----\n"
|
||||
},
|
||||
"scheme": "ecdsa-sha2-nistp384",
|
||||
"x-tuf-on-ci-keyowner": "@ingshtrom"
|
||||
},
|
||||
"48a873aa6c4189804228590af4d48ee5ad3b76417592efdbcef2532401925669": {
|
||||
"keytype": "ecdsa",
|
||||
"keyval": {
|
||||
"public": "-----BEGIN PUBLIC KEY-----\nMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEpQrE8o+fz6kBrs3TD6zqcDPwRZf3FxOX\n+SiT0k3SL1JHsMbxwFAKq+wJzqpqbhzFySuO1VVT93xNDd/rmjEU6HSY7wvT0m/l\nZ0S7yIwl3UnlplzKUYg/8wWJM0C2Qdpj\n-----END PUBLIC KEY-----\n"
|
||||
},
|
||||
"scheme": "ecdsa-sha2-nistp384",
|
||||
"x-tuf-on-ci-keyowner": "@cdupuis"
|
||||
},
|
||||
"6132f1f2dd14bf3e9ba1a8df4c8435a77d2fd57f4a99bbb699ae61f85907818e": {
|
||||
"keytype": "ecdsa",
|
||||
"keyval": {
|
||||
"public": "-----BEGIN PUBLIC KEY-----\nMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEkFPn3WTH/xVIEFhdP/TCqtnuiOqdgb/v\nEIBjng1TBCVmr7NnW4y4bdZG4Tf9OVTSqlJzuUFThJT/JQR3M7xEzW9WJqUfBTS1\nUuF980elHtMpRkS3NtRp/T0IrkH7+COa\n-----END PUBLIC KEY-----\n"
|
||||
},
|
||||
"scheme": "ecdsa-sha2-nistp384",
|
||||
"x-tuf-on-ci-keyowner": "@jonnystoten"
|
||||
},
|
||||
"9c8e1be7d8d0e30656adc81ac201e05cb47a5a097d4d301fd121b77c320231c4": {
|
||||
"keytype": "ecdsa",
|
||||
"keyval": {
|
||||
"public": "-----BEGIN PUBLIC KEY-----\nMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEWDreR+iXRtTStv5zmCLGoSmvvfV9/agY\nkx4O1XpRinBwAAA/IO4MI+YCoY0EQpKlSxl0DoVe6hmiXq2ezjTbebGDO66+fTZH\nkrr4KiCsZ8QcdPAR2cUvXkgyBp0WtYYS\n-----END PUBLIC KEY-----\n"
|
||||
},
|
||||
"scheme": "ecdsa-sha2-nistp384",
|
||||
"x-tuf-on-ci-keyowner": "@rachel-taylor-docker"
|
||||
},
|
||||
"aef160e03958d5346c903dda755c07e952127ef523df5ec33bd9b24d41fe1cf4": {
|
||||
"keytype": "ecdsa",
|
||||
"keyval": {
|
||||
"public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE5gH1kg/MZeiF/GO222hxMerv7MBC\nn91IJG8BbYWKmqZm2za+/QDyrMZExTguYlutu77jZqbkRZEFb/LbL4Ntuw==\n-----END PUBLIC KEY-----\n"
|
||||
},
|
||||
"scheme": "ecdsa-sha2-nistp256",
|
||||
"x-tuf-on-ci-online-uri": "awskms:arn:aws:kms:us-east-1:654654578585:key/751429f1-0aea-4bd8-b450-bb1bce6b058f"
|
||||
},
|
||||
"cda750ab29ce33e19ad2fdee4204ad0190b0a33f79e1c5c18a38992d576143d7": {
|
||||
"keytype": "ecdsa",
|
||||
"keyval": {
|
||||
"public": "-----BEGIN PUBLIC KEY-----\nMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEYTPARe9DPvvVVf7ch5fTVWXtS9FS97lh\nyZr3Pk33qRprnVB9u7BaEzvQtTYycPO7cmYW5yTOC5ZZa9p2B/v15bOK4NTU0WTT\nXTwSgKmJDh8CD/PBp386S8cwyyIp7NiR\n-----END PUBLIC KEY-----\n"
|
||||
},
|
||||
"scheme": "ecdsa-sha2-nistp384",
|
||||
"x-tuf-on-ci-keyowner": "@whalelines"
|
||||
},
|
||||
"f2149d8b7c1ece56d87d81f27fa68b745efc841892b3acfa382ad7f611e612ec": {
|
||||
"keytype": "ecdsa",
|
||||
"keyval": {
|
||||
"public": "-----BEGIN PUBLIC KEY-----\nMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEtWRLfl1pLhd5pn4gOmiCQwxE68U0+mIl\n1sU9ugeUz2aCZ9GcTjDNFE/7ZOat74ajeaFi9zmdeCi3UTYioLXNOXfbN6mxM9iQ\nGG3Z5OWYsZpeAv+5jhly2JeWUhFTuJpd\n-----END PUBLIC KEY-----\n"
|
||||
},
|
||||
"scheme": "ecdsa-sha2-nistp384",
|
||||
"x-tuf-on-ci-keyowner": "@mrjoelkamp"
|
||||
}
|
||||
},
|
||||
"roles": {
|
||||
"root": {
|
||||
"keyids": [
|
||||
"08d6f4ca1d0be93a6ceddca15051c0aeec6b98c73e29f3a714de301042d6eeee",
|
||||
"3ebd40525193d7628d0b9eccd4771df7297bc87519ec6f312863bb4470966bea",
|
||||
"9c8e1be7d8d0e30656adc81ac201e05cb47a5a097d4d301fd121b77c320231c4",
|
||||
"373d0a38247919a78cf400cf9a90abb9aa23a3c3dce1deee995fdd6a81507117",
|
||||
"48a873aa6c4189804228590af4d48ee5ad3b76417592efdbcef2532401925669"
|
||||
],
|
||||
"threshold": 3
|
||||
},
|
||||
"snapshot": {
|
||||
"keyids": [
|
||||
"aef160e03958d5346c903dda755c07e952127ef523df5ec33bd9b24d41fe1cf4"
|
||||
],
|
||||
"threshold": 1,
|
||||
"x-tuf-on-ci-expiry-period": 365,
|
||||
"x-tuf-on-ci-signing-period": 60
|
||||
},
|
||||
"targets": {
|
||||
"keyids": [
|
||||
"f2149d8b7c1ece56d87d81f27fa68b745efc841892b3acfa382ad7f611e612ec",
|
||||
"2ff207ae7d7b595ef69589622067ef5b6668e1a43081377d942ed8749fa919b4",
|
||||
"6132f1f2dd14bf3e9ba1a8df4c8435a77d2fd57f4a99bbb699ae61f85907818e",
|
||||
"cda750ab29ce33e19ad2fdee4204ad0190b0a33f79e1c5c18a38992d576143d7"
|
||||
],
|
||||
"threshold": 2
|
||||
},
|
||||
"timestamp": {
|
||||
"keyids": [
|
||||
"aef160e03958d5346c903dda755c07e952127ef523df5ec33bd9b24d41fe1cf4"
|
||||
],
|
||||
"threshold": 1,
|
||||
"x-tuf-on-ci-expiry-period": 2,
|
||||
"x-tuf-on-ci-signing-period": 1
|
||||
}
|
||||
},
|
||||
"spec_version": "1.0.31",
|
||||
"version": 1,
|
||||
"x-tuf-on-ci-expiry-period": 365,
|
||||
"x-tuf-on-ci-signing-period": 60
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,44 @@ package embed
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
//go:embed embedded-roots/1.root-dev.json
|
||||
var DevRoot []byte
|
||||
var devRoot []byte
|
||||
|
||||
//go:embed embedded-roots/1.root-staging.json
|
||||
var StagingRoot []byte
|
||||
var stagingRoot []byte
|
||||
|
||||
var DefaultRoot = StagingRoot
|
||||
//go:embed embedded-roots/1.root.json
|
||||
var prodRoot []byte
|
||||
|
||||
var defaultRoot = prodRoot
|
||||
|
||||
type RootName string
|
||||
type EmbeddedRoot struct {
|
||||
Data []byte
|
||||
Name RootName
|
||||
}
|
||||
|
||||
var (
|
||||
RootDev = EmbeddedRoot{Data: devRoot, Name: "dev"}
|
||||
RootStaging = EmbeddedRoot{Data: stagingRoot, Name: "staging"}
|
||||
RootProd = EmbeddedRoot{Data: prodRoot, Name: "prod"}
|
||||
RootDefault = EmbeddedRoot{Data: defaultRoot, Name: ""}
|
||||
)
|
||||
|
||||
func GetRootFromName(root string) (*EmbeddedRoot, error) {
|
||||
switch root {
|
||||
case string(RootDev.Name):
|
||||
return &RootDev, nil
|
||||
case string(RootStaging.Name):
|
||||
return &RootStaging, nil
|
||||
case string(RootProd.Name):
|
||||
return &RootProd, nil
|
||||
case string(RootDefault.Name):
|
||||
return &RootDefault, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid tuf root: %s", root)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,23 +96,8 @@ type AnnotatedStatement struct {
|
||||
Annotations map[string]string
|
||||
}
|
||||
|
||||
func ExtractAnnotatedStatements(path string, mediaType string) ([]*AnnotatedStatement, 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)
|
||||
}
|
||||
idxDigest := idxm.Manifests[0].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()
|
||||
func ExtractStatementsFromIndex(idx v1.ImageIndex, mediaType string) ([]*AnnotatedStatement, error) {
|
||||
mfs2, err := idx.IndexManifest()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to extract IndexManifest from ImageIndex: %w", err)
|
||||
}
|
||||
@@ -124,7 +109,7 @@ func ExtractAnnotatedStatements(path string, mediaType string) ([]*AnnotatedStat
|
||||
continue
|
||||
}
|
||||
|
||||
attestationImage, err := mfs.Image(mf.Digest)
|
||||
attestationImage, err := idx.Image(mf.Digest)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to extract attestation image with digest %s: %w", mf.Digest.String(), err)
|
||||
}
|
||||
@@ -189,3 +174,22 @@ func ExtractAnnotatedStatements(path string, mediaType string) ([]*AnnotatedStat
|
||||
}
|
||||
return statements, nil
|
||||
}
|
||||
|
||||
func ExtractAnnotatedStatements(path string, mediaType string) ([]*AnnotatedStatement, 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)
|
||||
}
|
||||
idxDigest := idxm.Manifests[0].Digest
|
||||
|
||||
mfs, err := idx.ImageIndex(idxDigest)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to extract ImageIndex for digest %s: %w", idxDigest.String(), err)
|
||||
}
|
||||
return ExtractStatementsFromIndex(mfs, mediaType)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/docker/attest/pkg/attest"
|
||||
"github.com/docker/attest/pkg/attestation"
|
||||
"github.com/docker/attest/pkg/mirror"
|
||||
"github.com/docker/attest/pkg/oci"
|
||||
"github.com/docker/attest/pkg/signerverifier"
|
||||
@@ -25,28 +26,36 @@ func ExampleSign_remote() {
|
||||
// signer, err := signerverifier.GetAWSSigner(cmd.Context(), aws_arn, aws_region)
|
||||
|
||||
// configure signing options
|
||||
opts := &attest.SigningOptions{
|
||||
opts := &attestation.SigningOptions{
|
||||
Replace: true, // replace unsigned intoto statements with signed intoto attestations, otherwise leave in place
|
||||
}
|
||||
|
||||
// load image index with unsigned attestation-manifests
|
||||
ref := "docker/image-signer-verifier:latest"
|
||||
att, err := oci.AttestationIndexFromRemote(ref)
|
||||
attIdx, err := oci.IndexFromRemote(ref)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// example for local image index
|
||||
// path := "/myimage"
|
||||
// att, err := oci.AttestationIndexFromLocal(path)
|
||||
// attIdx, err = oci.IndexFromPath(path)
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
|
||||
// sign attestations
|
||||
signedImageIndex, err := attest.Sign(context.Background(), att.Index, signer, opts)
|
||||
// sign all attestations in an image index
|
||||
signedManifests, err := attest.SignStatements(context.Background(), attIdx.Index, signer, opts)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
signedIndex := attIdx.Index
|
||||
signedIndex, err = attestation.AddImagesToIndex(signedIndex, signedManifests)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// push image index with signed attestation-manifests
|
||||
err = mirror.PushToRegistry(signedImageIndex, ref)
|
||||
err = mirror.PushIndexToRegistry(signedIndex, ref)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -54,14 +63,14 @@ func ExampleSign_remote() {
|
||||
path := "/myimage"
|
||||
idx := v1.ImageIndex(empty.Index)
|
||||
idx = mutate.AppendManifests(idx, mutate.IndexAddendum{
|
||||
Add: signedImageIndex,
|
||||
Add: signedIndex,
|
||||
Descriptor: v1.Descriptor{
|
||||
Annotations: map[string]string{
|
||||
oci.OciReferenceTarget: att.Name,
|
||||
oci.OciReferenceTarget: attIdx.Name,
|
||||
},
|
||||
},
|
||||
})
|
||||
err = mirror.SaveAsOCILayout(idx, path)
|
||||
err = mirror.SaveIndexAsOCILayout(idx, path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ func createTufClient(outputPath string) (*tuf.TufClient, error) {
|
||||
// metadataURI := "https://docker.github.io/tuf-staging/metadata"
|
||||
// targetsURI := "https://docker.github.io/tuf-staging/targets"
|
||||
|
||||
return tuf.NewTufClient(embed.StagingRoot, outputPath, metadataURI, targetsURI, tuf.NewMockVersionChecker())
|
||||
return tuf.NewTufClient(embed.RootStaging.Data, outputPath, metadataURI, targetsURI, tuf.NewMockVersionChecker())
|
||||
}
|
||||
|
||||
func ExampleVerify_remote() {
|
||||
@@ -39,27 +39,21 @@ func ExampleVerify_remote() {
|
||||
// create a resolver for remote attestations
|
||||
image := "registry-1.docker.io/library/notary:server"
|
||||
platform := "linux/amd64"
|
||||
resolver := &oci.RegistryResolver{
|
||||
Image: image, // path to image index in OCI registry containing image attestations
|
||||
Platform: platform, // platform of subject image (image that attestations are being verified against)
|
||||
}
|
||||
// example using a local resolver
|
||||
// path := "/myimage"
|
||||
// platform := "linux/amd64"
|
||||
// resolver := &oci.OCILayoutResolver{
|
||||
// Path: path, // file path to OCI layout containing image attestations
|
||||
// Platform: platform, // platform of subject image (image that attestations are being verified against)
|
||||
// }
|
||||
|
||||
// configure policy options
|
||||
opts := &policy.PolicyOptions{
|
||||
TufClient: tufClient,
|
||||
LocalTargetsDir: filepath.Join(home, ".docker", "policy"), // location to store policy files downloaded from TUF
|
||||
LocalPolicyDir: "", // overrides TUF policy for local policy files if set
|
||||
PolicyId: "", // set to ignore policy mapping and select a policy by id
|
||||
}
|
||||
|
||||
// verify attestations
|
||||
result, err := attest.Verify(context.Background(), opts, resolver)
|
||||
src, err := oci.ParseImageSpec(image, oci.WithPlatform(platform))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
result, err := attest.Verify(context.Background(), src, opts)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
@@ -2,200 +2,25 @@ package attest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/docker/attest/pkg/attestation"
|
||||
"github.com/docker/attest/pkg/oci"
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/google/go-containerregistry/pkg/v1/empty"
|
||||
"github.com/google/go-containerregistry/pkg/v1/match"
|
||||
"github.com/google/go-containerregistry/pkg/v1/mutate"
|
||||
"github.com/google/go-containerregistry/pkg/v1/partial"
|
||||
"github.com/google/go-containerregistry/pkg/v1/static"
|
||||
"github.com/google/go-containerregistry/pkg/v1/types"
|
||||
intoto "github.com/in-toto/in-toto-golang/in_toto"
|
||||
"github.com/secure-systems-lab/go-securesystemslib/dsse"
|
||||
)
|
||||
|
||||
func Sign(ctx context.Context, idx v1.ImageIndex, signer dsse.SignerVerifier, opts *SigningOptions) (v1.ImageIndex, error) {
|
||||
// this is only relevant if there are (unsigned) in-toto statements
|
||||
func SignStatements(ctx context.Context, idx v1.ImageIndex, signer dsse.SignerVerifier, opts *attestation.SigningOptions) ([]*attestation.AttestationManifest, error) {
|
||||
// extract attestation manifests from index
|
||||
attestationManifests, err := attestation.GetAttestationManifestsFromIndex(idx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get attestation manifests: %w", err)
|
||||
return nil, fmt.Errorf("failed to load attestation manifests from index: %w", err)
|
||||
}
|
||||
|
||||
// sign every attestation layer in each manifest
|
||||
for _, manifest := range attestationManifests {
|
||||
idx, err = signLayersAndAddToIndex(ctx, idx, manifest.Attestation.Layers, manifest, signer, opts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to add signed layers: %w", err)
|
||||
}
|
||||
}
|
||||
return idx, nil
|
||||
}
|
||||
|
||||
func AddAttestation(ctx context.Context, idx v1.ImageIndex, statement *intoto.Statement, signer dsse.SignerVerifier) (v1.ImageIndex, error) {
|
||||
if len(statement.Subject) == 0 {
|
||||
return nil, fmt.Errorf("statement has no subjects")
|
||||
}
|
||||
|
||||
subjectDigests := make(map[string]bool)
|
||||
for _, subject := range statement.Subject {
|
||||
subjectDigest := fmt.Sprintf("sha256:%s", subject.Digest["sha256"])
|
||||
subjectDigests[subjectDigest] = true
|
||||
}
|
||||
|
||||
attestationManifests, err := attestation.GetAttestationManifestsFromIndex(idx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get attestation manifests: %w", err)
|
||||
}
|
||||
updatedIndex := false
|
||||
for _, manifest := range attestationManifests {
|
||||
if subjectDigests[manifest.Annotations[oci.DockerReferenceDigest]] {
|
||||
attestationLayers := []attestation.AttestationLayer{
|
||||
{
|
||||
Statement: statement,
|
||||
MediaType: types.MediaType(intoto.PayloadType),
|
||||
Annotations: map[string]string{
|
||||
oci.InTotoPredicateType: statement.PredicateType,
|
||||
},
|
||||
},
|
||||
}
|
||||
// hard-coding replace to false here, because if it's true we will remove any unsigned statements, even unrelated ones
|
||||
idx, err = signLayersAndAddToIndex(ctx, idx, attestationLayers, manifest, signer, &SigningOptions{Replace: false})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to add signed layers: %w", err)
|
||||
}
|
||||
updatedIndex = true
|
||||
}
|
||||
}
|
||||
if !updatedIndex {
|
||||
return nil, fmt.Errorf("no attestation manifest found for statement")
|
||||
}
|
||||
return idx, nil
|
||||
|
||||
}
|
||||
|
||||
func signLayersAndAddToIndex(
|
||||
ctx context.Context,
|
||||
idx v1.ImageIndex,
|
||||
attestationLayers []attestation.AttestationLayer,
|
||||
manifest attestation.AttestationManifest,
|
||||
signer dsse.SignerVerifier,
|
||||
opts *SigningOptions) (v1.ImageIndex, error) {
|
||||
|
||||
signedLayers, err := signLayers(ctx, attestationLayers, signer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to sign attestations: %w", err)
|
||||
}
|
||||
|
||||
newImg, err := addSignedLayers(signedLayers, manifest, opts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to add signed layers: %w", err)
|
||||
}
|
||||
newDesc, err := partial.Descriptor(newImg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get descriptor: %w", err)
|
||||
}
|
||||
cf, err := manifest.Attestation.Image.ConfigFile()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get config file: %w", err)
|
||||
}
|
||||
newDesc.Platform = cf.Platform()
|
||||
if newDesc.Platform == nil {
|
||||
newDesc.Platform = &v1.Platform{
|
||||
Architecture: "unknown",
|
||||
OS: "unknown",
|
||||
}
|
||||
}
|
||||
newDesc.MediaType = manifest.MediaType
|
||||
newDesc.Annotations = manifest.Annotations
|
||||
idx = mutate.RemoveManifests(idx, match.Digests(manifest.Digest))
|
||||
idx = mutate.AppendManifests(idx, mutate.IndexAddendum{
|
||||
Add: newImg,
|
||||
Descriptor: *newDesc,
|
||||
})
|
||||
return idx, nil
|
||||
}
|
||||
|
||||
// signLayers signs each intoto attestation layer with the given signer
|
||||
func signLayers(ctx context.Context, layers []attestation.AttestationLayer, signer dsse.SignerVerifier) ([]mutate.Addendum, error) {
|
||||
var signedLayers []mutate.Addendum
|
||||
for _, layer := range layers {
|
||||
// only sign intoto layers
|
||||
if layer.MediaType != types.MediaType(intoto.PayloadType) {
|
||||
continue
|
||||
}
|
||||
// mark attestation as experimental
|
||||
layer.Annotations[InTotoReferenceLifecycleStage] = LifecycleStageExperimental
|
||||
|
||||
// sign the statement
|
||||
env, err := signInTotoStatement(ctx, layer.Statement, signer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to sign statement: %w", err)
|
||||
}
|
||||
mediaType, err := attestation.DSSEMediaType(layer.Statement.PredicateType)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get DSSE media type: %w", err)
|
||||
}
|
||||
data, err := json.Marshal(env)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal envelope: %w", err)
|
||||
}
|
||||
newLayer := static.NewLayer(data, types.MediaType(mediaType))
|
||||
withAnnotations := mutate.Addendum{
|
||||
Layer: newLayer,
|
||||
Annotations: layer.Annotations,
|
||||
}
|
||||
signedLayers = append(signedLayers, withAnnotations)
|
||||
}
|
||||
return signedLayers, nil
|
||||
}
|
||||
|
||||
func signInTotoStatement(ctx context.Context, statement *intoto.Statement, signer dsse.SignerVerifier) (*attestation.Envelope, error) {
|
||||
payload, err := json.Marshal(statement)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal statement: %w", err)
|
||||
}
|
||||
env, err := attestation.SignDSSE(ctx, payload, intoto.PayloadType, signer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to sign statement: %w", err)
|
||||
}
|
||||
return env, nil
|
||||
}
|
||||
|
||||
// addSignedLayers adds signed layers to a new or existing attestation image
|
||||
func addSignedLayers(signedLayers []mutate.Addendum, manifest attestation.AttestationManifest, opts *SigningOptions) (v1.Image, error) {
|
||||
var err error
|
||||
if opts.Replace {
|
||||
// create a new attestation image with only signed layers
|
||||
newImg := empty.Image
|
||||
newImg = mutate.MediaType(newImg, manifest.MediaType)
|
||||
newImg = mutate.ConfigMediaType(newImg, "application/vnd.oci.image.config.v1+json")
|
||||
for _, layer := range signedLayers {
|
||||
newImg, err = mutate.Append(newImg, layer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to append signed layer: %w", err)
|
||||
}
|
||||
}
|
||||
// add any existing unsigned (non-intoto) layers to the new image
|
||||
for _, layer := range manifest.Attestation.Layers {
|
||||
if layer.MediaType != types.MediaType(intoto.PayloadType) {
|
||||
newImg, err = mutate.AppendLayers(newImg, layer.Layer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to append unsigned layer: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return newImg, nil
|
||||
}
|
||||
// Add signed layers to the existing image
|
||||
for _, layer := range signedLayers {
|
||||
manifest.Attestation.Image, err = mutate.Append(manifest.Attestation.Image, layer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to append layer: %w", err)
|
||||
manifest.AddAttestation(ctx, signer, layer.Statement, opts)
|
||||
}
|
||||
}
|
||||
return manifest.Attestation.Image, nil
|
||||
return attestationManifests, nil
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package attest
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
@@ -26,6 +25,7 @@ var (
|
||||
UnsignedTestImage = filepath.Join("..", "..", "test", "testdata", "unsigned-test-image")
|
||||
NoProvenanceImage = filepath.Join("..", "..", "test", "testdata", "no-provenance-image")
|
||||
PassPolicyDir = filepath.Join("..", "..", "test", "testdata", "local-policy-pass")
|
||||
PassNoTLPolicyDir = filepath.Join("..", "..", "test", "testdata", "local-policy-no-tl")
|
||||
FailPolicyDir = filepath.Join("..", "..", "test", "testdata", "local-policy-fail")
|
||||
TestTempDir = "attest-sign-test"
|
||||
)
|
||||
@@ -40,27 +40,28 @@ func TestSignVerifyOCILayout(t *testing.T) {
|
||||
expectedAttestations int
|
||||
replace bool
|
||||
}{
|
||||
|
||||
{"signed replaced (does nothing)", UnsignedTestImage, 0, 4, true},
|
||||
{"signed replaced", UnsignedTestImage, 0, 4, true},
|
||||
{"without replace", UnsignedTestImage, 4, 4, false},
|
||||
// image without provenance doesn't fail
|
||||
{"no provenance (replace)", NoProvenanceImage, 0, 2, true},
|
||||
{"no provenance (no replace)", NoProvenanceImage, 2, 2, false},
|
||||
}
|
||||
policyResolver := &policy.PolicyOptions{
|
||||
policyOpts := &policy.PolicyOptions{
|
||||
LocalPolicyDir: PassPolicyDir,
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
outputLayout := test.CreateTempDir(t, "", TestTempDir)
|
||||
opts := &SigningOptions{
|
||||
opts := &attestation.SigningOptions{
|
||||
Replace: tc.replace,
|
||||
}
|
||||
attIdx, err := oci.AttestationIndexFromPath(tc.TestImage)
|
||||
attIdx, err := oci.IndexFromPath(tc.TestImage)
|
||||
require.NoError(t, err)
|
||||
signedIndex, err := Sign(ctx, attIdx.Index, signer, opts)
|
||||
signedManifests, err := SignStatements(ctx, attIdx.Index, signer, opts)
|
||||
require.NoError(t, err)
|
||||
signedIndex := attIdx.Index
|
||||
signedIndex, err = attestation.AddImagesToIndex(signedIndex, signedManifests)
|
||||
require.NoError(t, err)
|
||||
|
||||
// output signed attestations
|
||||
idx := v1.ImageIndex(empty.Index)
|
||||
idx = mutate.AppendManifests(idx, mutate.IndexAddendum{
|
||||
@@ -73,12 +74,9 @@ func TestSignVerifyOCILayout(t *testing.T) {
|
||||
})
|
||||
_, err = layout.Write(outputLayout, idx)
|
||||
require.NoError(t, err)
|
||||
|
||||
resolver := &oci.OCILayoutResolver{
|
||||
Path: outputLayout,
|
||||
Platform: "",
|
||||
}
|
||||
policy, err := Verify(ctx, policyResolver, resolver)
|
||||
src, err := oci.ParseImageSpec("oci://" + outputLayout)
|
||||
require.NoError(t, err)
|
||||
policy, err := Verify(ctx, src, policyOpts)
|
||||
require.NoError(t, err)
|
||||
assert.Equalf(t, OutcomeSuccess, policy.Outcome, "Policy should have been found")
|
||||
|
||||
@@ -90,8 +88,8 @@ func TestSignVerifyOCILayout(t *testing.T) {
|
||||
allEnvelopes = append(allEnvelopes, statements...)
|
||||
|
||||
for _, stmt := range statements {
|
||||
assert.Equalf(t, predicate, stmt.Annotations[oci.InTotoPredicateType], "expected predicate-type annotation to be set to %s, got %s", predicate, stmt.Annotations[oci.InTotoPredicateType])
|
||||
assert.Equalf(t, LifecycleStageExperimental, stmt.Annotations[InTotoReferenceLifecycleStage], "expected reference lifecycle stage annotation to be set to %s, got %s", LifecycleStageExperimental, stmt.Annotations[InTotoReferenceLifecycleStage])
|
||||
assert.Equalf(t, predicate, stmt.Annotations[attestation.InTotoPredicateType], "expected predicate-type annotation to be set to %s, got %s", predicate, stmt.Annotations[attestation.InTotoPredicateType])
|
||||
assert.Equalf(t, attestation.LifecycleStageExperimental, stmt.Annotations[attestation.InTotoReferenceLifecycleStage], "expected reference lifecycle stage annotation to be set to %s, got %s", attestation.LifecycleStageExperimental, stmt.Annotations[attestation.InTotoReferenceLifecycleStage])
|
||||
}
|
||||
}
|
||||
assert.Equalf(t, tc.expectedAttestations, len(allEnvelopes), "expected %d attestations, got %d", tc.expectedAttestations, len(allEnvelopes))
|
||||
@@ -102,70 +100,6 @@ func TestSignVerifyOCILayout(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddAttestation(t *testing.T) {
|
||||
ctx, signer := test.Setup(t)
|
||||
|
||||
expectedAttestations := 2
|
||||
expectedStatements := 4
|
||||
|
||||
outputLayout := test.CreateTempDir(t, "", TestTempDir)
|
||||
attIdx, err := oci.AttestationIndexFromPath(UnsignedTestImage)
|
||||
require.NoError(t, err)
|
||||
|
||||
statementToAdd := &intoto.Statement{
|
||||
StatementHeader: intoto.StatementHeader{
|
||||
PredicateType: attestation.VSAPredicateType,
|
||||
Type: intoto.StatementInTotoV01,
|
||||
Subject: []intoto.Subject{
|
||||
{
|
||||
Name: attIdx.Name,
|
||||
Digest: map[string]string{
|
||||
"sha256": "da8b190665956ea07890a0273e2a9c96bfe291662f08e2860e868eef69c34620",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: attIdx.Name,
|
||||
Digest: map[string]string{
|
||||
"sha256": "7a76cec943853f9f7105b1976afa1bf7cd5bb6afc4e9d5852dd8da7cf81ae86e",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
signedIndex, err := AddAttestation(ctx, attIdx.Index, statementToAdd, signer)
|
||||
require.NoError(t, err)
|
||||
|
||||
// output signed attestations
|
||||
idx := v1.ImageIndex(empty.Index)
|
||||
idx = mutate.AppendManifests(idx, mutate.IndexAddendum{
|
||||
Add: signedIndex,
|
||||
Descriptor: v1.Descriptor{
|
||||
Annotations: map[string]string{
|
||||
oci.OciReferenceTarget: attIdx.Name,
|
||||
},
|
||||
},
|
||||
})
|
||||
_, err = layout.Write(outputLayout, idx)
|
||||
require.NoError(t, err)
|
||||
|
||||
var allEnvelopes []*test.AnnotatedStatement
|
||||
mt, _ := attestation.DSSEMediaType(attestation.VSAPredicateType)
|
||||
statements, err := test.ExtractAnnotatedStatements(outputLayout, mt)
|
||||
require.NoError(t, err)
|
||||
allEnvelopes = append(allEnvelopes, statements...)
|
||||
|
||||
for _, stmt := range statements {
|
||||
assert.Equalf(t, attestation.VSAPredicateType, stmt.Annotations[oci.InTotoPredicateType], "expected predicate-type annotation to be set to %s, got %s", attestation.VSAPredicateType, stmt.Annotations[oci.InTotoPredicateType])
|
||||
assert.Equalf(t, LifecycleStageExperimental, stmt.Annotations[InTotoReferenceLifecycleStage], "expected reference lifecycle stage annotation to be set to %s, got %s", LifecycleStageExperimental, stmt.Annotations[InTotoReferenceLifecycleStage])
|
||||
}
|
||||
assert.Equalf(t, expectedAttestations, len(allEnvelopes), "expected %d attestations, got %d", expectedAttestations, len(allEnvelopes))
|
||||
statements, err = test.ExtractAnnotatedStatements(outputLayout, intoto.PayloadType)
|
||||
fmt.Printf("statements: %+v\n", statements)
|
||||
require.NoError(t, err)
|
||||
assert.Equalf(t, expectedStatements, len(statements), "expected %d statement, got %d", expectedStatements, len(statements))
|
||||
}
|
||||
|
||||
func TestAddSignedLayerAnnotations(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
@@ -178,32 +112,29 @@ func TestAddSignedLayerAnnotations(t *testing.T) {
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
data := []byte("signed")
|
||||
signedLayer := static.NewLayer(data, types.MediaType(intoto.PayloadType))
|
||||
signedLayers := []mutate.Addendum{
|
||||
{
|
||||
Layer: signedLayer,
|
||||
Annotations: map[string]string{"test": "test"},
|
||||
},
|
||||
}
|
||||
data = []byte("test")
|
||||
testLayer := static.NewLayer(data, types.MediaType(intoto.PayloadType))
|
||||
mediaType := types.OCIManifestSchema1
|
||||
opts := &SigningOptions{
|
||||
opts := &attestation.SigningOptions{
|
||||
Replace: tc.replace,
|
||||
}
|
||||
manifest := attestation.AttestationManifest{
|
||||
originalLayer := &attestation.AttestationLayer{
|
||||
Layer: testLayer,
|
||||
Statement: &intoto.Statement{},
|
||||
Annotations: map[string]string{"test": "test"},
|
||||
}
|
||||
|
||||
manifest := &attestation.AttestationManifest{
|
||||
MediaType: mediaType,
|
||||
Attestation: attestation.AttestationImage{
|
||||
Attestation: &attestation.AttestationImage{
|
||||
Image: empty.Image,
|
||||
Layers: []attestation.AttestationLayer{
|
||||
{
|
||||
Layer: testLayer,
|
||||
Statement: &intoto.Statement{},
|
||||
},
|
||||
Layers: []*attestation.AttestationLayer{
|
||||
originalLayer,
|
||||
},
|
||||
},
|
||||
SubjectDescriptor: &v1.Descriptor{},
|
||||
}
|
||||
newImg, err := addSignedLayers(signedLayers, manifest, opts)
|
||||
err := manifest.AddOrReplaceLayer(originalLayer, opts)
|
||||
newImg := manifest.Attestation.Image
|
||||
require.NoError(t, err)
|
||||
mf, _ := newImg.RawManifest()
|
||||
type Annotations struct {
|
||||
|
||||
@@ -7,15 +7,6 @@ import (
|
||||
intoto "github.com/in-toto/in-toto-golang/in_toto"
|
||||
)
|
||||
|
||||
const (
|
||||
InTotoReferenceLifecycleStage = "vnd.docker.lifecycle-stage"
|
||||
LifecycleStageExperimental = "experimental"
|
||||
)
|
||||
|
||||
type SigningOptions struct {
|
||||
Replace bool
|
||||
}
|
||||
|
||||
type Outcome string
|
||||
|
||||
const (
|
||||
|
||||
@@ -6,13 +6,25 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/docker/attest/pkg/attestation"
|
||||
"github.com/docker/attest/pkg/config"
|
||||
"github.com/docker/attest/pkg/oci"
|
||||
"github.com/docker/attest/pkg/policy"
|
||||
intoto "github.com/in-toto/in-toto-golang/in_toto"
|
||||
)
|
||||
|
||||
func Verify(ctx context.Context, opts *policy.PolicyOptions, resolver oci.AttestationResolver) (result *VerificationResult, err error) {
|
||||
pctx, err := policy.ResolvePolicy(ctx, resolver, opts)
|
||||
func Verify(ctx context.Context, src *oci.ImageSpec, opts *policy.PolicyOptions) (result *VerificationResult, err error) {
|
||||
// so that we can resolve mapping from the image name earlier
|
||||
detailsResolver, err := policy.CreateImageDetailsResolver(src)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create image details resolver: %w", err)
|
||||
}
|
||||
if opts.AttestationStyle == "" {
|
||||
opts.AttestationStyle = config.AttestationStyleReferrers
|
||||
}
|
||||
if opts.ReferrersRepo != "" && opts.AttestationStyle != config.AttestationStyleReferrers {
|
||||
return nil, fmt.Errorf("referrers repo specified but attestation source not set to referrers")
|
||||
}
|
||||
pctx, err := policy.ResolvePolicy(ctx, detailsResolver, opts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to resolve policy: %w", err)
|
||||
}
|
||||
@@ -22,7 +34,23 @@ func Verify(ctx context.Context, opts *policy.PolicyOptions, resolver oci.Attest
|
||||
Outcome: OutcomeNoPolicy,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// this is overriding the mapping with a referrers config. Useful for testing if nothing else
|
||||
if opts.ReferrersRepo != "" {
|
||||
pctx.Mapping.Attestations = &config.ReferrersConfig{
|
||||
Repo: opts.ReferrersRepo,
|
||||
Style: config.AttestationStyleReferrers,
|
||||
}
|
||||
} else if opts.AttestationStyle == config.AttestationStyleAttached {
|
||||
pctx.Mapping.Attestations = &config.ReferrersConfig{
|
||||
Repo: opts.ReferrersRepo,
|
||||
Style: config.AttestationStyleAttached,
|
||||
}
|
||||
}
|
||||
// because we have a mapping now, we can select a resolver based on its contents (ie. referrers or attached)
|
||||
resolver, err := policy.CreateAttestationResolver(detailsResolver, pctx.Mapping)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create attestation resolver: %w", err)
|
||||
}
|
||||
result, err = VerifyAttestations(ctx, resolver, pctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to evaluate policy: %w", err)
|
||||
@@ -37,7 +65,7 @@ func ToPolicyResult(p *policy.Policy, input *policy.PolicyInput, result *policy.
|
||||
}
|
||||
subject := intoto.Subject{
|
||||
Name: input.Purl,
|
||||
Digest: *dgst,
|
||||
Digest: dgst,
|
||||
}
|
||||
resourceUri, err := attestation.ToVSAResourceURI(subject)
|
||||
if err != nil {
|
||||
@@ -60,6 +88,7 @@ func ToPolicyResult(p *policy.Policy, input *policy.PolicyInput, result *policy.
|
||||
Policy: p,
|
||||
Outcome: outcome,
|
||||
Violations: result.Violations,
|
||||
Input: input,
|
||||
VSA: &intoto.Statement{
|
||||
StatementHeader: intoto.StatementHeader{
|
||||
PredicateType: attestation.VSAPredicateType,
|
||||
@@ -89,7 +118,11 @@ func VerifyAttestations(ctx context.Context, resolver oci.AttestationResolver, p
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get image name: %w", err)
|
||||
}
|
||||
purl, canonical, err := oci.RefToPURL(name, resolver.ImagePlatformStr())
|
||||
platform, err := resolver.ImagePlatform(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
purl, canonical, err := oci.RefToPURL(name, platform)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert ref to purl: %w", err)
|
||||
}
|
||||
|
||||
@@ -25,6 +25,10 @@ var (
|
||||
ExampleAttestation = filepath.Join("..", "..", "test", "testdata", "example_attestation.json")
|
||||
)
|
||||
|
||||
const (
|
||||
LinuxAMD64 = "linux/amd64"
|
||||
)
|
||||
|
||||
func TestVerifyAttestations(t *testing.T) {
|
||||
ex, err := os.ReadFile(ExampleAttestation)
|
||||
assert.NoError(t, err)
|
||||
@@ -73,13 +77,16 @@ func TestVSA(t *testing.T) {
|
||||
// setup an image with signed attestations
|
||||
outputLayout := test.CreateTempDir(t, "", TestTempDir)
|
||||
|
||||
opts := &SigningOptions{
|
||||
opts := &attestation.SigningOptions{
|
||||
Replace: true,
|
||||
}
|
||||
attIdx, err := oci.AttestationIndexFromPath(UnsignedTestImage)
|
||||
assert.NoError(t, err)
|
||||
signedIndex, err := Sign(ctx, attIdx.Index, signer, opts)
|
||||
attIdx, err := oci.IndexFromPath(UnsignedTestImage)
|
||||
assert.NoError(t, err)
|
||||
signedManifests, err := SignStatements(ctx, attIdx.Index, signer, opts)
|
||||
require.NoError(t, err)
|
||||
signedIndex := attIdx.Index
|
||||
signedIndex, err = attestation.AddImagesToIndex(signedIndex, signedManifests)
|
||||
require.NoError(t, err)
|
||||
|
||||
// output signed attestations
|
||||
idx := v1.ImageIndex(empty.Index)
|
||||
@@ -94,21 +101,22 @@ func TestVSA(t *testing.T) {
|
||||
_, err = layout.Write(outputLayout, idx)
|
||||
assert.NoError(t, err)
|
||||
|
||||
//verify (without vsa should fail)
|
||||
resolver := &oci.OCILayoutResolver{
|
||||
Path: outputLayout,
|
||||
Platform: "linux/amd64",
|
||||
}
|
||||
|
||||
// mocked vsa query should pass
|
||||
policyOpts := &policy.PolicyOptions{
|
||||
LocalPolicyDir: PassPolicyDir,
|
||||
}
|
||||
results, err := Verify(ctx, policyOpts, resolver)
|
||||
src, err := oci.ParseImageSpec("oci://"+outputLayout, oci.WithPlatform(LinuxAMD64))
|
||||
require.NoError(t, err)
|
||||
results, err := Verify(ctx, src, policyOpts)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, OutcomeSuccess, results.Outcome)
|
||||
assert.Empty(t, results.Violations)
|
||||
|
||||
if assert.NotNil(t, results.Input) {
|
||||
assert.Equal(t, "sha256:da8b190665956ea07890a0273e2a9c96bfe291662f08e2860e868eef69c34620", results.Input.Digest)
|
||||
assert.False(t, results.Input.IsCanonical)
|
||||
}
|
||||
|
||||
assert.Equal(t, intoto.StatementInTotoV01, results.VSA.Type)
|
||||
assert.Equal(t, attestation.VSAPredicateType, results.VSA.PredicateType)
|
||||
assert.Len(t, results.VSA.Subject, 1)
|
||||
@@ -128,13 +136,16 @@ func TestVerificationFailure(t *testing.T) {
|
||||
// setup an image with signed attestations
|
||||
outputLayout := test.CreateTempDir(t, "", TestTempDir)
|
||||
|
||||
opts := &SigningOptions{
|
||||
opts := &attestation.SigningOptions{
|
||||
Replace: true,
|
||||
}
|
||||
attIdx, err := oci.AttestationIndexFromPath(UnsignedTestImage)
|
||||
assert.NoError(t, err)
|
||||
signedIndex, err := Sign(ctx, attIdx.Index, signer, opts)
|
||||
attIdx, err := oci.IndexFromPath(UnsignedTestImage)
|
||||
assert.NoError(t, err)
|
||||
signedManifests, err := SignStatements(ctx, attIdx.Index, signer, opts)
|
||||
require.NoError(t, err)
|
||||
signedIndex := attIdx.Index
|
||||
signedIndex, err = attestation.AddImagesToIndex(signedIndex, signedManifests)
|
||||
require.NoError(t, err)
|
||||
|
||||
// output signed attestations
|
||||
idx := v1.ImageIndex(empty.Index)
|
||||
@@ -149,17 +160,13 @@ func TestVerificationFailure(t *testing.T) {
|
||||
_, err = layout.Write(outputLayout, idx)
|
||||
assert.NoError(t, err)
|
||||
|
||||
//verify (without vsa should fail)
|
||||
resolver := &oci.OCILayoutResolver{
|
||||
Path: outputLayout,
|
||||
Platform: "linux/amd64",
|
||||
}
|
||||
|
||||
// mocked vsa query should pass
|
||||
// mocked vsa query should fail
|
||||
policyOpts := &policy.PolicyOptions{
|
||||
LocalPolicyDir: FailPolicyDir,
|
||||
}
|
||||
results, err := Verify(ctx, policyOpts, resolver)
|
||||
src, err := oci.ParseImageSpec("oci://"+outputLayout, oci.WithPlatform(LinuxAMD64))
|
||||
require.NoError(t, err)
|
||||
results, err := Verify(ctx, src, policyOpts)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, OutcomeFailure, results.Outcome)
|
||||
assert.Len(t, results.Violations, 1)
|
||||
@@ -181,3 +188,61 @@ func TestVerificationFailure(t *testing.T) {
|
||||
assert.Equal(t, []string{"SLSA_BUILD_LEVEL_3"}, attestationPredicate.VerifiedLevels)
|
||||
assert.Equal(t, "https://docker.com/official/policy/v0.1", attestationPredicate.Policy.URI)
|
||||
}
|
||||
|
||||
// test signing without a TL entry
|
||||
func TestSignVerifyNoTL(t *testing.T) {
|
||||
ctx, signer := test.Setup(t)
|
||||
ctx = policy.WithPolicyEvaluator(ctx, policy.NewRegoEvaluator(true))
|
||||
// setup an image with signed attestations
|
||||
outputLayout := test.CreateTempDir(t, "", TestTempDir)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
signTL bool
|
||||
policyDir string
|
||||
success bool
|
||||
}{
|
||||
{name: "happy path", signTL: true, policyDir: PassNoTLPolicyDir, success: true},
|
||||
{name: "sign tl, verify no tl", signTL: true, policyDir: PassPolicyDir, success: false},
|
||||
{name: "no tl", signTL: false, policyDir: PassPolicyDir, success: false},
|
||||
}
|
||||
|
||||
attIdx, err := oci.IndexFromPath(UnsignedTestImage)
|
||||
assert.NoError(t, err)
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
opts := &attestation.SigningOptions{
|
||||
Replace: true,
|
||||
SkipTL: tc.signTL,
|
||||
}
|
||||
|
||||
signedManifests, err := SignStatements(ctx, attIdx.Index, signer, opts)
|
||||
require.NoError(t, err)
|
||||
signedIndex := attIdx.Index
|
||||
signedIndex, err = attestation.AddImagesToIndex(signedIndex, signedManifests)
|
||||
require.NoError(t, err)
|
||||
// output signed attestations
|
||||
idx := v1.ImageIndex(empty.Index)
|
||||
idx = mutate.AppendManifests(idx, mutate.IndexAddendum{
|
||||
Add: signedIndex,
|
||||
Descriptor: v1.Descriptor{
|
||||
Annotations: map[string]string{
|
||||
oci.OciReferenceTarget: attIdx.Name,
|
||||
},
|
||||
},
|
||||
})
|
||||
_, err = layout.Write(outputLayout, idx)
|
||||
assert.NoError(t, err)
|
||||
|
||||
policyOpts := &policy.PolicyOptions{
|
||||
LocalPolicyDir: tc.policyDir,
|
||||
}
|
||||
src, err := oci.ParseImageSpec("oci://"+outputLayout, oci.WithPlatform(LinuxAMD64))
|
||||
require.NoError(t, err)
|
||||
results, err := Verify(ctx, src, policyOpts)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, OutcomeSuccess, results.Outcome)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,40 @@
|
||||
package attestation
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"maps"
|
||||
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/google/go-containerregistry/pkg/v1/empty"
|
||||
"github.com/google/go-containerregistry/pkg/v1/match"
|
||||
"github.com/google/go-containerregistry/pkg/v1/mutate"
|
||||
"github.com/google/go-containerregistry/pkg/v1/partial"
|
||||
"github.com/google/go-containerregistry/pkg/v1/static"
|
||||
"github.com/google/go-containerregistry/pkg/v1/types"
|
||||
intoto "github.com/in-toto/in-toto-golang/in_toto"
|
||||
"github.com/secure-systems-lab/go-securesystemslib/dsse"
|
||||
)
|
||||
|
||||
// GetAttestationManifestsFromIndex extracts all attestation manifests from an index
|
||||
func GetAttestationManifestsFromIndex(index v1.ImageIndex) ([]AttestationManifest, error) {
|
||||
func GetAttestationManifestsFromIndex(index v1.ImageIndex) ([]*AttestationManifest, error) {
|
||||
idx, err := index.IndexManifest()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to extract IndexManifest from ImageIndex: %w", err)
|
||||
}
|
||||
var attestationManifests []AttestationManifest
|
||||
subjects := make(map[string]*v1.Descriptor)
|
||||
for _, subject := range idx.Manifests {
|
||||
subjects[subject.Digest.String()] = &subject
|
||||
}
|
||||
|
||||
var attestationManifests []*AttestationManifest
|
||||
for _, manifest := range idx.Manifests {
|
||||
if manifest.Annotations[DockerReferenceType] == AttestationManifestType {
|
||||
subject := subjects[manifest.Annotations[DockerReferenceDigest]]
|
||||
if subject == nil {
|
||||
return nil, fmt.Errorf("failed to find subject for attestation manifest: %w", err)
|
||||
}
|
||||
attestationImage, err := index.Image(manifest.Digest)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to extract attestation image with digest %s: %w", manifest.Digest.String(), err)
|
||||
@@ -29,9 +44,10 @@ func GetAttestationManifestsFromIndex(index v1.ImageIndex) ([]AttestationManifes
|
||||
return nil, fmt.Errorf("failed to get attestations from image: %w", err)
|
||||
}
|
||||
attestationManifests = append(attestationManifests,
|
||||
AttestationManifest{
|
||||
Manifest: manifest,
|
||||
Attestation: AttestationImage{
|
||||
&AttestationManifest{
|
||||
Descriptor: &manifest,
|
||||
SubjectDescriptor: subject,
|
||||
Attestation: &AttestationImage{
|
||||
Layers: attestationLayers,
|
||||
Image: attestationImage},
|
||||
MediaType: manifest.MediaType,
|
||||
@@ -43,12 +59,12 @@ func GetAttestationManifestsFromIndex(index v1.ImageIndex) ([]AttestationManifes
|
||||
}
|
||||
|
||||
// GetAttestationsFromImage extracts all attestation layers from an image
|
||||
func GetAttestationsFromImage(image v1.Image) ([]AttestationLayer, error) {
|
||||
func GetAttestationsFromImage(image v1.Image) ([]*AttestationLayer, error) {
|
||||
layers, err := image.Layers()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to extract layers from image: %w", err)
|
||||
}
|
||||
var attestationLayers []AttestationLayer
|
||||
var attestationLayers []*AttestationLayer
|
||||
for _, layer := range layers {
|
||||
// parse layer blob as json
|
||||
r, err := layer.Uncompressed()
|
||||
@@ -74,7 +90,162 @@ func GetAttestationsFromImage(image v1.Image) ([]AttestationLayer, error) {
|
||||
return nil, fmt.Errorf("failed to decode statement layer contents: %w", err)
|
||||
}
|
||||
}
|
||||
attestationLayers = append(attestationLayers, AttestationLayer{Layer: layer, MediaType: mt, Statement: stmt, Annotations: ann})
|
||||
attestationLayers = append(attestationLayers, &AttestationLayer{Layer: layer, MediaType: mt, Statement: stmt, Annotations: ann})
|
||||
}
|
||||
return attestationLayers, nil
|
||||
}
|
||||
|
||||
func (manifest *AttestationManifest) AddAttestation(ctx context.Context, signer dsse.SignerVerifier, statement *intoto.Statement, opts *SigningOptions) error {
|
||||
layer, err := createSignedImageLayer(ctx, statement, signer, opts)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create signed layer: %w", err)
|
||||
}
|
||||
newImg, newDesc, err := addLayerToImage(manifest, layer, opts)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to add signed layers to image: %w", err)
|
||||
}
|
||||
manifest.Attestation.Image = newImg
|
||||
manifest.Descriptor = newDesc
|
||||
return nil
|
||||
}
|
||||
|
||||
func createSignedImageLayer(ctx context.Context, statement *intoto.Statement, signer dsse.SignerVerifier, opts *SigningOptions) (*AttestationLayer, error) {
|
||||
|
||||
// sign the statement
|
||||
env, err := SignInTotoStatement(ctx, statement, signer, opts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to sign statement: %w", err)
|
||||
}
|
||||
|
||||
mediaType, err := DSSEMediaType(statement.PredicateType)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get DSSE media type: %w", err)
|
||||
}
|
||||
data, err := json.Marshal(env)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal envelope: %w", err)
|
||||
}
|
||||
return &AttestationLayer{
|
||||
Statement: statement,
|
||||
MediaType: types.MediaType(intoto.PayloadType),
|
||||
Annotations: map[string]string{
|
||||
InTotoPredicateType: statement.PredicateType,
|
||||
InTotoReferenceLifecycleStage: LifecycleStageExperimental,
|
||||
},
|
||||
Layer: static.NewLayer(data, types.MediaType(mediaType)),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func SignInTotoStatement(ctx context.Context, statement *intoto.Statement, signer dsse.SignerVerifier, opts *SigningOptions) (*Envelope, error) {
|
||||
payload, err := json.Marshal(statement)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal statement: %w", err)
|
||||
}
|
||||
env, err := SignDSSE(ctx, payload, signer, opts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to sign statement: %w", err)
|
||||
}
|
||||
return env, nil
|
||||
}
|
||||
|
||||
func addLayerToImage(
|
||||
manifest *AttestationManifest,
|
||||
layer *AttestationLayer,
|
||||
opts *SigningOptions) (v1.Image, *v1.Descriptor, error) {
|
||||
|
||||
err := manifest.AddOrReplaceLayer(layer, opts)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to add signed layers: %w", err)
|
||||
}
|
||||
newImg := manifest.Attestation.Image
|
||||
if !opts.SkipSubject {
|
||||
newImg = mutate.Subject(newImg, *manifest.SubjectDescriptor).(v1.Image)
|
||||
}
|
||||
newDesc, err := partial.Descriptor(newImg)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to get descriptor: %w", err)
|
||||
}
|
||||
cf, err := manifest.Attestation.Image.ConfigFile()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to get config file: %w", err)
|
||||
}
|
||||
newDesc.Platform = cf.Platform()
|
||||
if newDesc.Platform == nil {
|
||||
newDesc.Platform = &v1.Platform{
|
||||
Architecture: "unknown",
|
||||
OS: "unknown",
|
||||
}
|
||||
}
|
||||
newDesc.MediaType = manifest.MediaType
|
||||
newDesc.Annotations = manifest.Annotations
|
||||
return newImg, newDesc, nil
|
||||
}
|
||||
|
||||
// AddOrReplaceLayer adds signed layers to a new or existing attestation image
|
||||
// NOTE: the pointers attestation.AttestationLayer.Statement are compared when replacing,
|
||||
// so make sure you are signing a layer extracted from the original attestation-manifest image!
|
||||
func (manifest *AttestationManifest) AddOrReplaceLayer(signedLayer *AttestationLayer, opts *SigningOptions) error {
|
||||
var err error
|
||||
// always create a new image from all the layers
|
||||
newImg := empty.Image
|
||||
newImg = mutate.Annotations(newImg, map[string]string{
|
||||
DockerReferenceType: AttestationManifestType,
|
||||
DockerReferenceDigest: manifest.SubjectDescriptor.Digest.String(),
|
||||
}).(v1.Image)
|
||||
|
||||
newImg = mutate.MediaType(newImg, manifest.MediaType)
|
||||
newImg = mutate.ConfigMediaType(newImg, "application/vnd.oci.image.config.v1+json")
|
||||
add := mutate.Addendum{
|
||||
Layer: signedLayer.Layer,
|
||||
Annotations: signedLayer.Annotations,
|
||||
}
|
||||
newImg, err = mutate.Append(newImg, add)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to add signed layer to image: %w", err)
|
||||
}
|
||||
layers := make([]*AttestationLayer, 0)
|
||||
for _, layer := range manifest.Attestation.Layers {
|
||||
if layer.Statement == signedLayer.Statement && opts.Replace {
|
||||
continue
|
||||
}
|
||||
add := mutate.Addendum{
|
||||
Layer: layer.Layer,
|
||||
Annotations: layer.Annotations,
|
||||
}
|
||||
newImg, err = mutate.Append(newImg, add)
|
||||
layers = append(layers, layer)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to add layer to image: %w", err)
|
||||
}
|
||||
}
|
||||
manifest.Attestation.Layers = append(layers, signedLayer)
|
||||
manifest.Attestation.Image = newImg
|
||||
return nil
|
||||
}
|
||||
|
||||
func AddImageToIndex(
|
||||
idx v1.ImageIndex,
|
||||
manifest *AttestationManifest,
|
||||
) (v1.ImageIndex, error) {
|
||||
idx = mutate.RemoveManifests(idx, match.Digests(manifest.Digest))
|
||||
idx = mutate.AppendManifests(idx, mutate.IndexAddendum{
|
||||
Add: manifest.Attestation.Image,
|
||||
Descriptor: *manifest.Descriptor,
|
||||
})
|
||||
return idx, nil
|
||||
}
|
||||
|
||||
func AddImagesToIndex(
|
||||
idx v1.ImageIndex,
|
||||
manifests []*AttestationManifest,
|
||||
) (v1.ImageIndex, error) {
|
||||
for _, manifest := range manifests {
|
||||
var err error
|
||||
idx, err = AddImageToIndex(idx, manifest)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to add image to index: %w", err)
|
||||
}
|
||||
}
|
||||
return idx, nil
|
||||
}
|
||||
|
||||
256
pkg/attestation/referrers_test.go
Normal file
256
pkg/attestation/referrers_test.go
Normal file
@@ -0,0 +1,256 @@
|
||||
package attestation_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/attest/internal/test"
|
||||
"github.com/docker/attest/pkg/attest"
|
||||
"github.com/docker/attest/pkg/attestation"
|
||||
"github.com/docker/attest/pkg/config"
|
||||
"github.com/docker/attest/pkg/mirror"
|
||||
"github.com/docker/attest/pkg/oci"
|
||||
"github.com/docker/attest/pkg/policy"
|
||||
"github.com/google/go-containerregistry/pkg/name"
|
||||
"github.com/google/go-containerregistry/pkg/registry"
|
||||
"github.com/google/go-containerregistry/pkg/v1/remote"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var (
|
||||
UnsignedTestImage = filepath.Join("..", "..", "test", "testdata", "unsigned-test-image")
|
||||
NoProvenanceImage = filepath.Join("..", "..", "test", "testdata", "no-provenance-image")
|
||||
PassPolicyDir = filepath.Join("..", "..", "test", "testdata", "local-policy-pass")
|
||||
LocalPolicy = filepath.Join("..", "..", "test", "testdata", "local-policy")
|
||||
LocalPolicyAttached = filepath.Join("..", "..", "test", "testdata", "local-policy-attached")
|
||||
PassNoTLPolicyDir = filepath.Join("..", "..", "test", "testdata", "local-policy-no-tl")
|
||||
FailPolicyDir = filepath.Join("..", "..", "test", "testdata", "local-policy-fail")
|
||||
TestTempDir = "attest-sign-test"
|
||||
)
|
||||
|
||||
func TestAttestationReferenceTypes(t *testing.T) {
|
||||
ctx, signer := test.Setup(t)
|
||||
ctx = policy.WithPolicyEvaluator(ctx, policy.NewRegoEvaluator(true))
|
||||
platforms := []string{"linux/amd64", "linux/arm64"}
|
||||
for _, tc := range []struct {
|
||||
server *httptest.Server
|
||||
referrersServer *httptest.Server
|
||||
skipSubject bool
|
||||
useDigest bool
|
||||
referrersRepo string
|
||||
attestationSource config.AttestationStyle
|
||||
expectFailure bool
|
||||
policyDir string
|
||||
}{
|
||||
{
|
||||
server: httptest.NewServer(registry.New(registry.WithReferrersSupport(true))),
|
||||
},
|
||||
{
|
||||
server: httptest.NewServer(registry.New()),
|
||||
},
|
||||
{
|
||||
server: httptest.NewServer(registry.New(registry.WithReferrersSupport(true))),
|
||||
skipSubject: true,
|
||||
attestationSource: config.AttestationStyleAttached,
|
||||
},
|
||||
{
|
||||
server: httptest.NewServer(registry.New(registry.WithReferrersSupport(true))),
|
||||
useDigest: true,
|
||||
},
|
||||
{
|
||||
server: httptest.NewServer(registry.New(registry.WithReferrersSupport(true))),
|
||||
expectFailure: true, //mismatched args
|
||||
attestationSource: config.AttestationStyleAttached,
|
||||
referrersRepo: "referrers",
|
||||
},
|
||||
{
|
||||
server: httptest.NewServer(registry.New(registry.WithReferrersSupport(true))),
|
||||
expectFailure: true, // no policy
|
||||
attestationSource: config.AttestationStyleReferrers,
|
||||
referrersRepo: "referrers",
|
||||
},
|
||||
{
|
||||
server: httptest.NewServer(registry.New(registry.WithReferrersSupport(true))),
|
||||
attestationSource: config.AttestationStyleReferrers,
|
||||
},
|
||||
{
|
||||
server: httptest.NewServer(registry.New(registry.WithReferrersSupport(false))),
|
||||
attestationSource: config.AttestationStyleReferrers,
|
||||
referrersServer: httptest.NewServer(registry.New(registry.WithReferrersSupport(true))),
|
||||
},
|
||||
} {
|
||||
t.Run(fmt.Sprint(tc), func(t *testing.T) {
|
||||
s := tc.server
|
||||
defer s.Close()
|
||||
|
||||
if tc.referrersServer != nil {
|
||||
defer tc.referrersServer.Close()
|
||||
}
|
||||
u, err := url.Parse(s.URL)
|
||||
require.NoError(t, err)
|
||||
|
||||
opts := &attestation.SigningOptions{
|
||||
Replace: true,
|
||||
SkipSubject: tc.skipSubject,
|
||||
}
|
||||
attIdx, err := oci.IndexFromPath(UnsignedTestImage)
|
||||
require.NoError(t, err)
|
||||
|
||||
indexName := fmt.Sprintf("%s/repo:root", u.Host)
|
||||
require.NoError(t, err)
|
||||
|
||||
if tc.referrersServer != nil {
|
||||
ru, err := url.Parse(s.URL)
|
||||
require.NoError(t, err)
|
||||
repo := fmt.Sprintf("%s/referrers", ru.Host)
|
||||
tc.referrersRepo = repo
|
||||
signedManifests, err := attest.SignStatements(ctx, attIdx.Index, signer, opts)
|
||||
require.NoError(t, err)
|
||||
err = mirror.PushIndexToRegistry(attIdx.Index, indexName)
|
||||
for _, img := range signedManifests {
|
||||
err = mirror.PushImageToRegistry(img.Attestation.Image, fmt.Sprintf("%s:tag-does-not-matter", repo))
|
||||
require.NoError(t, err)
|
||||
}
|
||||
} else {
|
||||
signedManifests, err := attest.SignStatements(ctx, attIdx.Index, signer, opts)
|
||||
require.NoError(t, err)
|
||||
signedIndex := attIdx.Index
|
||||
signedIndex, err = attestation.AddImagesToIndex(signedIndex, signedManifests)
|
||||
require.NoError(t, err)
|
||||
err = mirror.PushIndexToRegistry(signedIndex, indexName)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
for _, platform := range platforms {
|
||||
// can eval policy in the normal way
|
||||
ref := indexName
|
||||
if tc.useDigest {
|
||||
options := oci.WithOptions(ctx, nil)
|
||||
subjectRef, err := name.ParseReference(indexName)
|
||||
require.NoError(t, err)
|
||||
desc, err := remote.Index(subjectRef, options...)
|
||||
require.NoError(t, err)
|
||||
idxDigest, err := desc.Digest()
|
||||
require.NoError(t, err)
|
||||
ref = fmt.Sprintf("%s/repo@%s", u.Host, idxDigest.String())
|
||||
}
|
||||
|
||||
policyOpts := &policy.PolicyOptions{
|
||||
LocalPolicyDir: LocalPolicy,
|
||||
}
|
||||
if tc.policyDir != "" {
|
||||
policyOpts.LocalPolicyDir = tc.policyDir
|
||||
}
|
||||
|
||||
if tc.referrersRepo != "" {
|
||||
policyOpts.ReferrersRepo = tc.referrersRepo
|
||||
}
|
||||
|
||||
if tc.attestationSource != "" {
|
||||
policyOpts.AttestationStyle = tc.attestationSource
|
||||
}
|
||||
src, err := oci.ParseImageSpec(ref, oci.WithPlatform(platform))
|
||||
require.NoError(t, err)
|
||||
results, err := attest.Verify(ctx, src, policyOpts)
|
||||
if tc.expectFailure {
|
||||
require.Error(t, err)
|
||||
continue
|
||||
}
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, attest.OutcomeSuccess, results.Outcome)
|
||||
|
||||
if !tc.skipSubject {
|
||||
// can evaluate policy using referrers
|
||||
if tc.useDigest {
|
||||
p, err := oci.ParsePlatform(platform)
|
||||
require.NoError(t, err)
|
||||
options := oci.WithOptions(ctx, p)
|
||||
subjectRef, err := name.ParseReference(indexName)
|
||||
require.NoError(t, err)
|
||||
desc, err := remote.Image(subjectRef, options...)
|
||||
require.NoError(t, err)
|
||||
subjectDigest, err := desc.Digest()
|
||||
require.NoError(t, err)
|
||||
ref = fmt.Sprintf("%s/repo@%s", u.Host, subjectDigest.String())
|
||||
}
|
||||
src, err := oci.ParseImageSpec(ref, oci.WithPlatform(platform))
|
||||
require.NoError(t, err)
|
||||
results, err = attest.Verify(ctx, src, policyOpts)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, attest.OutcomeSuccess, results.Outcome)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestReferencesInDifferentRepo(t *testing.T) {
|
||||
ctx, signer := test.Setup(t)
|
||||
repoName := "repo"
|
||||
for _, tc := range []struct {
|
||||
server *httptest.Server
|
||||
refServer *httptest.Server
|
||||
}{
|
||||
{
|
||||
server: httptest.NewServer(registry.New(registry.WithReferrersSupport(true))),
|
||||
refServer: httptest.NewServer(registry.New(registry.WithReferrersSupport(true))),
|
||||
},
|
||||
{
|
||||
server: httptest.NewServer(registry.New()),
|
||||
refServer: httptest.NewServer(registry.New(registry.WithReferrersSupport(true))),
|
||||
},
|
||||
} {
|
||||
server := tc.server
|
||||
defer server.Close()
|
||||
serverUrl, err := url.Parse(server.URL)
|
||||
require.NoError(t, err)
|
||||
|
||||
refServer := tc.refServer
|
||||
defer refServer.Close()
|
||||
refServerUrl, err := url.Parse(refServer.URL)
|
||||
require.NoError(t, err)
|
||||
|
||||
opts := &attestation.SigningOptions{
|
||||
Replace: true,
|
||||
SkipTL: true,
|
||||
}
|
||||
attIdx, err := oci.IndexFromPath(UnsignedTestImage)
|
||||
require.NoError(t, err)
|
||||
|
||||
indexName := fmt.Sprintf("%s/%s:latest", serverUrl.Host, repoName)
|
||||
err = mirror.PushIndexToRegistry(attIdx.Index, indexName)
|
||||
require.NoError(t, err)
|
||||
|
||||
signedManifests, err := attest.SignStatements(ctx, attIdx.Index, signer, opts)
|
||||
require.NoError(t, err)
|
||||
|
||||
// push signed attestation image to the ref server
|
||||
for _, img := range signedManifests {
|
||||
// push references using subject-digest.att convention
|
||||
err = mirror.PushImageToRegistry(img.Attestation.Image, fmt.Sprintf("%s/%s:tag-does-not-matter", refServerUrl.Host, repoName))
|
||||
require.NoError(t, err)
|
||||
}
|
||||
mfs2, err := attIdx.Index.IndexManifest()
|
||||
require.NoError(t, err)
|
||||
for _, mf := range mfs2.Manifests {
|
||||
//skip signed/unsigned attestations
|
||||
if mf.Annotations[attestation.DockerReferenceType] == attestation.AttestationManifestType {
|
||||
continue
|
||||
}
|
||||
// can evaluate policy using referrers in a different repo
|
||||
referencedImage := fmt.Sprintf("%s@%s", indexName, mf.Digest.String())
|
||||
policyOpts := &policy.PolicyOptions{
|
||||
LocalPolicyDir: PassPolicyDir,
|
||||
}
|
||||
src, err := oci.ParseImageSpec(referencedImage)
|
||||
require.NoError(t, err)
|
||||
results, err := attest.Verify(ctx, src, policyOpts)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, attest.OutcomeSuccess, results.Outcome)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,13 +6,13 @@ import (
|
||||
|
||||
"github.com/docker/attest/internal/util"
|
||||
"github.com/docker/attest/pkg/tlog"
|
||||
intoto "github.com/in-toto/in-toto-golang/in_toto"
|
||||
"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)
|
||||
|
||||
func SignDSSE(ctx context.Context, payload []byte, signer dsse.SignerVerifier, opts *SigningOptions) (*Envelope, error) {
|
||||
payloadType := intoto.PayloadType
|
||||
env := new(Envelope)
|
||||
env.Payload = base64Encoding.EncodeToString(payload)
|
||||
env.PayloadType = payloadType
|
||||
@@ -33,8 +33,31 @@ func SignDSSE(ctx context.Context, payload []byte, payloadType string, signer ds
|
||||
return nil, fmt.Errorf("error getting public key ID: %w", err)
|
||||
}
|
||||
|
||||
// upload to TL
|
||||
entry, err := t.UploadLogEntry(ctx, keyId, encPayload, sig, signer)
|
||||
dsseSig := Signature{
|
||||
KeyID: keyId,
|
||||
Sig: base64Encoding.EncodeToString(sig),
|
||||
}
|
||||
if !opts.SkipTL {
|
||||
ext, err := logSignature(ctx, tlog.GetTL(ctx), &sig, &encPayload, signer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to log to rekor: %w", err)
|
||||
}
|
||||
dsseSig.Extension = *ext
|
||||
}
|
||||
// add signature to dsse envelope
|
||||
env.Signatures = []Signature{dsseSig}
|
||||
|
||||
return env, nil
|
||||
}
|
||||
|
||||
// returns a new envelope with the transparency log entry added to the signature extension
|
||||
func logSignature(ctx context.Context, t tlog.TL, sig *[]byte, encPayload *[]byte, signer dsse.SignerVerifier) (*Extension, error) {
|
||||
// get Key ID from signer
|
||||
keyId, err := signer.KeyID()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting public key ID: %w", err)
|
||||
}
|
||||
entry, err := t.UploadLogEntry(ctx, keyId, *encPayload, *sig, signer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error uploading TL entry: %w", err)
|
||||
}
|
||||
@@ -42,21 +65,13 @@ func SignDSSE(ctx context.Context, payload []byte, payloadType string, signer ds
|
||||
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 &Extension{
|
||||
Kind: DockerDsseExtKind,
|
||||
Ext: DockerDsseExtension{
|
||||
Tl: DockerTlExtension{
|
||||
Kind: RekorTlExtKind,
|
||||
Data: entryObj, // transparency log entry metadata
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
return env, nil
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/docker/attest/pkg/signerverifier"
|
||||
intoto "github.com/in-toto/in-toto-golang/in_toto"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestSignVerifyAttestation(t *testing.T) {
|
||||
@@ -27,17 +28,17 @@ func TestSignVerifyAttestation(t *testing.T) {
|
||||
}
|
||||
|
||||
payload, err := json.Marshal(stmt)
|
||||
assert.NoError(t, err)
|
||||
|
||||
env, err := attestation.SignDSSE(ctx, payload, intoto.PayloadType, signer)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
opts := &attestation.SigningOptions{}
|
||||
env, err := attestation.SignDSSE(ctx, payload, signer, opts)
|
||||
require.NoError(t, err)
|
||||
|
||||
// marshal envelope to json to test for bugs when marshaling envelope data
|
||||
serializedEnv, err := json.Marshal(env)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
deserializedEnv := new(attestation.Envelope)
|
||||
err = json.Unmarshal(serializedEnv, deserializedEnv)
|
||||
assert.NoError(t, err)
|
||||
require.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
|
||||
@@ -49,10 +50,10 @@ func TestSignVerifyAttestation(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
|
||||
badKeyPriv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
badKey := &badKeyPriv.PublicKey
|
||||
badPEM, err := signerverifier.ToPEM(badKey)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
@@ -136,7 +137,10 @@ func TestSignVerifyAttestation(t *testing.T) {
|
||||
To: tc.to,
|
||||
Status: tc.status,
|
||||
}
|
||||
_, err = attestation.VerifyDSSE(ctx, deserializedEnv, attestation.KeysMap{tc.keyId: keyMeta})
|
||||
opts := &attestation.VerifyOptions{
|
||||
Keys: attestation.Keys{keyMeta},
|
||||
}
|
||||
_, err = attestation.VerifyDSSE(ctx, deserializedEnv, opts)
|
||||
if tc.expectedError != "" {
|
||||
assert.Contains(t, err.Error(), tc.expectedError)
|
||||
} else {
|
||||
|
||||
@@ -12,11 +12,15 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
DockerReferenceType = "vnd.docker.reference.type"
|
||||
AttestationManifestType = "attestation-manifest"
|
||||
DockerDsseExtKind = "application/vnd.docker.attestation-verification.v1+json"
|
||||
RekorTlExtKind = "Rekor"
|
||||
OCIDescriptorDSSEMediaType = ociv1.MediaTypeDescriptor + "+dsse"
|
||||
DockerReferenceType = "vnd.docker.reference.type"
|
||||
AttestationManifestType = "attestation-manifest"
|
||||
InTotoPredicateType = "in-toto.io/predicate-type"
|
||||
DockerReferenceDigest = "vnd.docker.reference.digest"
|
||||
DockerDsseExtKind = "application/vnd.docker.attestation-verification.v1+json"
|
||||
RekorTlExtKind = "Rekor"
|
||||
OCIDescriptorDSSEMediaType = ociv1.MediaTypeDescriptor + "+dsse"
|
||||
InTotoReferenceLifecycleStage = "vnd.docker.lifecycle-stage"
|
||||
LifecycleStageExperimental = "experimental"
|
||||
)
|
||||
|
||||
var base64Encoding = base64.StdEncoding.Strict()
|
||||
@@ -29,16 +33,23 @@ type AttestationLayer struct {
|
||||
}
|
||||
|
||||
type AttestationImage struct {
|
||||
Layers []AttestationLayer
|
||||
Layers []*AttestationLayer
|
||||
Image v1.Image
|
||||
}
|
||||
|
||||
type SignedAttestationImage struct {
|
||||
Image v1.Image
|
||||
Descriptor *v1.Descriptor
|
||||
AttestationManifest *AttestationManifest
|
||||
}
|
||||
|
||||
type AttestationManifest struct {
|
||||
Manifest v1.Descriptor
|
||||
Attestation AttestationImage
|
||||
MediaType types.MediaType
|
||||
Annotations map[string]string
|
||||
Digest v1.Hash
|
||||
Descriptor *v1.Descriptor
|
||||
Attestation *AttestationImage
|
||||
MediaType types.MediaType
|
||||
Annotations map[string]string
|
||||
Digest v1.Hash
|
||||
SubjectDescriptor *v1.Descriptor
|
||||
}
|
||||
|
||||
// the following types are needed until https://github.com/secure-systems-lab/dsse/pull/61 is merged
|
||||
@@ -66,6 +77,20 @@ type DockerTlExtension struct {
|
||||
Data any `json:"data"`
|
||||
}
|
||||
|
||||
type VerifyOptions struct {
|
||||
Keys []KeyMetadata `json:"keys"`
|
||||
SkipTL bool `json:"skip_tl"`
|
||||
}
|
||||
|
||||
type SigningOptions struct {
|
||||
// replace unsigned statements with signed attestations
|
||||
Replace bool
|
||||
// don't log to the configured transparency log
|
||||
SkipTL bool
|
||||
// don't add OCI subject field to attestation image
|
||||
SkipSubject bool
|
||||
}
|
||||
|
||||
func DSSEMediaType(predicateType string) (string, error) {
|
||||
var predicateName string
|
||||
switch predicateType {
|
||||
|
||||
@@ -30,7 +30,7 @@ type KeyMetadata struct {
|
||||
type Keys []KeyMetadata
|
||||
type KeysMap map[string]KeyMetadata
|
||||
|
||||
func VerifyDSSE(ctx context.Context, env *Envelope, keys KeysMap) ([]byte, error) {
|
||||
func VerifyDSSE(ctx context.Context, env *Envelope, opts *VerifyOptions) ([]byte, error) {
|
||||
// enforce payload type
|
||||
if !ValidPayloadType(env.PayloadType) {
|
||||
return nil, fmt.Errorf("unsupported payload type %s", env.PayloadType)
|
||||
@@ -49,7 +49,7 @@ func VerifyDSSE(ctx context.Context, env *Envelope, keys KeysMap) ([]byte, error
|
||||
|
||||
// verify signatures and transparency log entry
|
||||
for _, sig := range env.Signatures {
|
||||
err := verifySignature(ctx, sig, encPayload, keys)
|
||||
err := verifySignature(ctx, sig, encPayload, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -58,31 +58,11 @@ func VerifyDSSE(ctx context.Context, env *Envelope, keys KeysMap) ([]byte, error
|
||||
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")
|
||||
func verifySignature(ctx context.Context, sig Signature, payload []byte, opts *VerifyOptions) error {
|
||||
keys := make(map[string]KeyMetadata, len(opts.Keys))
|
||||
for _, key := range opts.Keys {
|
||||
keys[key.ID] = key
|
||||
}
|
||||
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)
|
||||
@@ -91,30 +71,53 @@ func verifySignature(ctx context.Context, sig Signature, payload []byte, keys Ke
|
||||
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)
|
||||
if !opts.SkipTL {
|
||||
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)
|
||||
}
|
||||
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)
|
||||
}
|
||||
// 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 {
|
||||
|
||||
@@ -39,8 +39,11 @@ func TestVerifyUnsignedAttestation(t *testing.T) {
|
||||
Payload: base64.StdEncoding.EncodeToString(payload),
|
||||
PayloadType: intoto.PayloadType,
|
||||
}
|
||||
opts := &attestation.VerifyOptions{
|
||||
Keys: attestation.Keys{},
|
||||
}
|
||||
|
||||
_, err := attestation.VerifyDSSE(ctx, env, attestation.KeysMap{})
|
||||
_, err := attestation.VerifyDSSE(ctx, env, opts)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "no signatures")
|
||||
}
|
||||
|
||||
49
pkg/config/config.go
Normal file
49
pkg/config/config.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/docker/attest/pkg/tuf"
|
||||
goyaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
const (
|
||||
MappingFilename = "mapping.yaml"
|
||||
)
|
||||
|
||||
func LoadLocalMappings(configDir string) (*PolicyMappings, error) {
|
||||
if configDir == "" {
|
||||
return nil, nil
|
||||
}
|
||||
mappings := &PolicyMappings{}
|
||||
path := filepath.Join(configDir, MappingFilename)
|
||||
mappingFile, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read local 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 LoadTufMappings(tufClient tuf.TUFClient, localTargetsDir string) (*PolicyMappings, error) {
|
||||
if tufClient == nil {
|
||||
return nil, fmt.Errorf("tuf client not set")
|
||||
}
|
||||
filename := MappingFilename
|
||||
_, fileContents, err := tufClient.DownloadTarget(filename, filepath.Join(localTargetsDir, filename))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to download policy mapping 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
|
||||
}
|
||||
48
pkg/config/types.go
Normal file
48
pkg/config/types.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package config
|
||||
|
||||
type PolicyMappings struct {
|
||||
Version string `json:"version"`
|
||||
Kind string `json:"kind"`
|
||||
Policies []*PolicyMapping `json:"policies"`
|
||||
Mirrors []*PolicyMirror `json:"mirrors"`
|
||||
}
|
||||
|
||||
type AttestationStyle string
|
||||
|
||||
const (
|
||||
AttestationStyleAttached AttestationStyle = "attached"
|
||||
AttestationStyleReferrers AttestationStyle = "referrers"
|
||||
)
|
||||
|
||||
type PolicyMapping struct {
|
||||
Id string `json:"id"`
|
||||
Description string `json:"description"`
|
||||
Origin *PolicyOrigin `json:"origin"`
|
||||
Files []PolicyMappingFile `json:"files"`
|
||||
Attestations *ReferrersConfig `json:"attestations"`
|
||||
}
|
||||
|
||||
type ReferrersConfig struct {
|
||||
Style AttestationStyle `json:"style"`
|
||||
Repo string `json:"repo"`
|
||||
}
|
||||
|
||||
type PolicyMappingFile struct {
|
||||
Path string `json:"path"`
|
||||
}
|
||||
|
||||
type PolicyMirror struct {
|
||||
PolicyId string `yaml:"policy-id"`
|
||||
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"`
|
||||
}
|
||||
34
pkg/mirror/authn_test.go
Normal file
34
pkg/mirror/authn_test.go
Normal file
@@ -0,0 +1,34 @@
|
||||
//go:build e2e
|
||||
|
||||
package mirror_test
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/attest/pkg/mirror"
|
||||
"github.com/docker/attest/pkg/oci"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestRegistryAuth(t *testing.T) {
|
||||
UnsignedTestImage := filepath.Join("..", "..", "test", "testdata", "unsigned-test-image")
|
||||
|
||||
attIdx, err := oci.IndexFromPath(UnsignedTestImage)
|
||||
require.NoError(t, err)
|
||||
// test cases for ecr, gcr and dockerhub
|
||||
testCases := []struct {
|
||||
Image string
|
||||
}{
|
||||
{Image: "175142243308.dkr.ecr.us-east-1.amazonaws.com/e2e-test-image:latest"},
|
||||
{Image: "docker/image-signer-verifier-test:latest"},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.Image, func(t *testing.T) {
|
||||
err := mirror.PushIndexToRegistry(attIdx.Index, tc.Image)
|
||||
require.NoError(t, err)
|
||||
_, err = oci.IndexFromRemote(tc.Image)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
)
|
||||
|
||||
type TufMirrorOutput struct {
|
||||
metadata *v1.Image
|
||||
metadata v1.Image
|
||||
delegatedMetadata []*mirror.MirrorImage
|
||||
targets []*mirror.MirrorImage
|
||||
delegatedTargets []*mirror.MirrorIndex
|
||||
@@ -29,7 +29,7 @@ func ExampleNewTufMirror() {
|
||||
// configure TUF mirror
|
||||
metadataURI := "https://docker.github.io/tuf-staging/metadata"
|
||||
targetsURI := "https://docker.github.io/tuf-staging/targets"
|
||||
m, err := mirror.NewTufMirror(embed.StagingRoot, tufOutputPath, metadataURI, targetsURI, tuf.NewMockVersionChecker())
|
||||
m, err := mirror.NewTufMirror(embed.RootStaging.Data, tufOutputPath, metadataURI, targetsURI, tuf.NewMockVersionChecker())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -80,7 +80,7 @@ func ExampleNewTufMirror() {
|
||||
func mirrorToRegistry(o *TufMirrorOutput) error {
|
||||
// push metadata to registry
|
||||
metadataRepo := "registry-1.docker.io/docker/tuf-metadata:latest"
|
||||
err := mirror.PushToRegistry(o.metadata, metadataRepo)
|
||||
err := mirror.PushImageToRegistry(o.metadata, metadataRepo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -91,7 +91,7 @@ func mirrorToRegistry(o *TufMirrorOutput) error {
|
||||
return fmt.Errorf("failed to get repo without tag: %s", metadataRepo)
|
||||
}
|
||||
imageName := fmt.Sprintf("%s:%s", repo, metadata.Tag)
|
||||
err = mirror.PushToRegistry(metadata.Image, imageName)
|
||||
err = mirror.PushImageToRegistry(metadata.Image, imageName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -101,7 +101,7 @@ func mirrorToRegistry(o *TufMirrorOutput) error {
|
||||
targetsRepo := "registry-1.docker.io/docker/tuf-targets"
|
||||
for _, target := range o.targets {
|
||||
imageName := fmt.Sprintf("%s:%s", targetsRepo, target.Tag)
|
||||
err = mirror.PushToRegistry(target.Image, imageName)
|
||||
err = mirror.PushImageToRegistry(target.Image, imageName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -109,7 +109,7 @@ func mirrorToRegistry(o *TufMirrorOutput) error {
|
||||
// push delegated targets to registry
|
||||
for _, target := range o.delegatedTargets {
|
||||
imageName := fmt.Sprintf("%s:%s", targetsRepo, target.Tag)
|
||||
err = mirror.PushToRegistry(target.Index, imageName)
|
||||
err = mirror.PushIndexToRegistry(target.Index, imageName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -119,14 +119,14 @@ func mirrorToRegistry(o *TufMirrorOutput) error {
|
||||
|
||||
func mirrorToLocal(o *TufMirrorOutput, outputPath string) error {
|
||||
// output metadata to local directory
|
||||
err := mirror.SaveAsOCILayout(o.metadata, outputPath)
|
||||
err := mirror.SaveImageAsOCILayout(o.metadata, outputPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// output delegated metadata to local directory
|
||||
for _, metadata := range o.delegatedMetadata {
|
||||
path := filepath.Join(outputPath, metadata.Tag)
|
||||
err = mirror.SaveAsOCILayout(metadata.Image, path)
|
||||
err = mirror.SaveImageAsOCILayout(metadata.Image, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -135,7 +135,7 @@ func mirrorToLocal(o *TufMirrorOutput, outputPath string) error {
|
||||
// output top-level targets to local directory
|
||||
for _, target := range o.targets {
|
||||
path := filepath.Join(outputPath, target.Tag)
|
||||
err = mirror.SaveAsOCILayout(target.Image, path)
|
||||
err = mirror.SaveImageAsOCILayout(target.Image, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -143,7 +143,7 @@ func mirrorToLocal(o *TufMirrorOutput, outputPath string) error {
|
||||
// output delegated targets to local directory
|
||||
for _, target := range o.delegatedTargets {
|
||||
path := filepath.Join(outputPath, target.Tag)
|
||||
err = mirror.SaveAsOCILayout(target.Index, path)
|
||||
err = mirror.SaveIndexAsOCILayout(target.Index, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
// -----------------
|
||||
|
||||
// GetMetadataManifest returns an image with TUF root metadata as layers
|
||||
func (m *TufMirror) GetMetadataManifest(metadataURL string) (*v1.Image, error) {
|
||||
func (m *TufMirror) GetMetadataManifest(metadataURL string) (v1.Image, error) {
|
||||
metadata, err := m.getTufMetadataMirror(metadataURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get metadata: %w", err)
|
||||
@@ -78,7 +78,7 @@ func (m *TufMirror) getTufMetadataMirror(metadataURL string) (*TufMetadata, erro
|
||||
}
|
||||
|
||||
// buildMetadataManifest returns an OCI image with TUF metadata as layers with annotations
|
||||
func (m *TufMirror) buildMetadataManifest(metadata *TufMetadata) (*v1.Image, error) {
|
||||
func (m *TufMirror) buildMetadataManifest(metadata *TufMetadata) (v1.Image, error) {
|
||||
img := empty.Image
|
||||
img = mutate.MediaType(img, types.OCIManifestSchema1)
|
||||
img = mutate.ConfigMediaType(img, types.OCIConfigJSON)
|
||||
@@ -87,17 +87,17 @@ func (m *TufMirror) buildMetadataManifest(metadata *TufMetadata) (*v1.Image, err
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to make role layer: %w", err)
|
||||
}
|
||||
img, err = mutate.Append(img, *layers...)
|
||||
img, err = mutate.Append(img, layers...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to append role layer to image: %w", err)
|
||||
}
|
||||
}
|
||||
return &img, nil
|
||||
return img, nil
|
||||
}
|
||||
|
||||
// makeRoleLayers returns a list of layers for a given TUF role
|
||||
func (m *TufMirror) makeRoleLayers(role TufRole, tufMetadata *TufMetadata) (*[]mutate.Addendum, error) {
|
||||
layers := new([]mutate.Addendum)
|
||||
func (m *TufMirror) makeRoleLayers(role TufRole, tufMetadata *TufMetadata) ([]mutate.Addendum, error) {
|
||||
var layers []mutate.Addendum
|
||||
ann := map[string]string{tufFileAnnotation: ""}
|
||||
switch role {
|
||||
case metadata.ROOT:
|
||||
@@ -108,7 +108,7 @@ func (m *TufMirror) makeRoleLayers(role TufRole, tufMetadata *TufMetadata) (*[]m
|
||||
layers = m.annotatedMetaLayers(tufMetadata.Targets)
|
||||
case metadata.TIMESTAMP:
|
||||
ann[tufFileAnnotation] = fmt.Sprintf("%s.json", role)
|
||||
*layers = append(*layers, mutate.Addendum{Layer: static.NewLayer(tufMetadata.Timestamp, tufMetadataMediaType), Annotations: ann})
|
||||
layers = append(layers, mutate.Addendum{Layer: static.NewLayer(tufMetadata.Timestamp, tufMetadataMediaType), Annotations: ann})
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported TUF role: %s", role)
|
||||
}
|
||||
@@ -116,11 +116,11 @@ func (m *TufMirror) makeRoleLayers(role TufRole, tufMetadata *TufMetadata) (*[]m
|
||||
}
|
||||
|
||||
// annotatedMetaLayers returns a list of layers with annotations for each TUF metadata file
|
||||
func (m *TufMirror) annotatedMetaLayers(meta map[string][]byte) *[]mutate.Addendum {
|
||||
layers := new([]mutate.Addendum)
|
||||
func (m *TufMirror) annotatedMetaLayers(meta map[string][]byte) []mutate.Addendum {
|
||||
var layers []mutate.Addendum
|
||||
for name, data := range meta {
|
||||
ann := map[string]string{tufFileAnnotation: name}
|
||||
*layers = append(*layers, mutate.Addendum{Layer: static.NewLayer(data, tufMetadataMediaType), Annotations: ann})
|
||||
layers = append(layers, mutate.Addendum{Layer: static.NewLayer(data, tufMetadataMediaType), Annotations: ann})
|
||||
}
|
||||
return layers
|
||||
}
|
||||
@@ -144,8 +144,8 @@ func (m *TufMirror) GetDelegatedMetadataMirrors() ([]*MirrorImage, error) {
|
||||
}
|
||||
|
||||
// getDelegatedTargetsMetadata returns delegated targets metadata as a list of DelegatedTargetMetadata (role name and data)
|
||||
func (m *TufMirror) getDelegatedTargetsMetadata() (*[]DelegatedTargetMetadata, error) {
|
||||
delegatedTargets := new([]DelegatedTargetMetadata)
|
||||
func (m *TufMirror) getDelegatedTargetsMetadata() ([]DelegatedTargetMetadata, error) {
|
||||
var delegatedTargets []DelegatedTargetMetadata
|
||||
md := m.TufClient.GetMetadata()
|
||||
for _, role := range md.Targets[metadata.TARGETS].Signed.Delegations.Roles {
|
||||
roleMetadata, err := m.TufClient.LoadDelegatedTargets(role.Name, metadata.TARGETS)
|
||||
@@ -165,15 +165,15 @@ func (m *TufMirror) getDelegatedTargetsMetadata() (*[]DelegatedTargetMetadata, e
|
||||
if md.Root.Signed.ConsistentSnapshot {
|
||||
version = strconv.FormatInt(meta.Version, 10)
|
||||
}
|
||||
*delegatedTargets = append(*delegatedTargets, DelegatedTargetMetadata{Name: role.Name, Version: version, Data: roleBytes})
|
||||
delegatedTargets = append(delegatedTargets, DelegatedTargetMetadata{Name: role.Name, Version: version, Data: roleBytes})
|
||||
}
|
||||
return delegatedTargets, nil
|
||||
}
|
||||
|
||||
// buildDelegatedMetadataManifests returns a list of mirrors (image/tag pairs) for each delegated target role metadata
|
||||
func (m *TufMirror) buildDelegatedMetadataManifests(delegated *[]DelegatedTargetMetadata) ([]*MirrorImage, error) {
|
||||
func (m *TufMirror) buildDelegatedMetadataManifests(delegated []DelegatedTargetMetadata) ([]*MirrorImage, error) {
|
||||
manifests := []*MirrorImage{}
|
||||
for _, role := range *delegated {
|
||||
for _, role := range delegated {
|
||||
img := empty.Image
|
||||
img = mutate.MediaType(img, types.OCIManifestSchema1)
|
||||
img = mutate.ConfigMediaType(img, types.OCIConfigJSON)
|
||||
@@ -183,7 +183,7 @@ func (m *TufMirror) buildDelegatedMetadataManifests(delegated *[]DelegatedTarget
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to append delegated targets layer to image: %w", err)
|
||||
}
|
||||
manifests = append(manifests, &MirrorImage{Image: &img, Tag: role.Name})
|
||||
manifests = append(manifests, &MirrorImage{Image: img, Tag: role.Name})
|
||||
}
|
||||
return manifests, nil
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ func TestGetTufMetadataMirror(t *testing.T) {
|
||||
defer server.Close()
|
||||
|
||||
path := test.CreateTempDir(t, "", "tuf_temp")
|
||||
m, err := NewTufMirror(embed.DevRoot, path, server.URL+"/metadata", server.URL+"/targets", tuf.NewMockVersionChecker())
|
||||
m, err := NewTufMirror(embed.RootDev.Data, path, server.URL+"/metadata", server.URL+"/targets", tuf.NewMockVersionChecker())
|
||||
assert.NoError(t, err)
|
||||
|
||||
tufMetadata, err := m.getTufMetadataMirror(server.URL + "/metadata")
|
||||
@@ -39,15 +39,14 @@ func TestGetMetadataManifest(t *testing.T) {
|
||||
defer server.Close()
|
||||
|
||||
path := test.CreateTempDir(t, "", "tuf_temp")
|
||||
m, err := NewTufMirror(embed.DevRoot, path, server.URL+"/metadata", server.URL+"/targets", tuf.NewMockVersionChecker())
|
||||
m, err := NewTufMirror(embed.RootDev.Data, path, server.URL+"/metadata", server.URL+"/targets", tuf.NewMockVersionChecker())
|
||||
assert.NoError(t, err)
|
||||
|
||||
img, err := m.GetMetadataManifest(server.URL + "/metadata")
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, img)
|
||||
|
||||
image := *img
|
||||
mf, err := image.RawManifest()
|
||||
mf, err := img.RawManifest()
|
||||
assert.NoError(t, err)
|
||||
|
||||
type Annotations struct {
|
||||
@@ -79,7 +78,7 @@ func TestGetDelegatedMetadataMirrors(t *testing.T) {
|
||||
defer server.Close()
|
||||
|
||||
path := test.CreateTempDir(t, "", "tuf_temp")
|
||||
m, err := NewTufMirror(embed.DevRoot, path, server.URL+"/metadata", server.URL+"/targets", tuf.NewMockVersionChecker())
|
||||
m, err := NewTufMirror(embed.RootDev.Data, path, server.URL+"/metadata", server.URL+"/targets", tuf.NewMockVersionChecker())
|
||||
assert.NoError(t, err)
|
||||
|
||||
delegations, err := m.GetDelegatedMetadataMirrors()
|
||||
|
||||
@@ -2,12 +2,11 @@ package mirror
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/docker/attest/internal/embed"
|
||||
"github.com/docker/attest/pkg/oci"
|
||||
"github.com/docker/attest/pkg/tuf"
|
||||
"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/empty"
|
||||
@@ -17,7 +16,7 @@ import (
|
||||
|
||||
func NewTufMirror(root []byte, tufPath, metadataURL, targetsURL string, versionChecker tuf.VersionChecker) (*TufMirror, error) {
|
||||
if root == nil {
|
||||
root = embed.DefaultRoot
|
||||
root = embed.RootDefault.Data
|
||||
}
|
||||
tufClient, err := tuf.NewTufClient(root, tufPath, metadataURL, targetsURL, versionChecker)
|
||||
if err != nil {
|
||||
@@ -26,62 +25,51 @@ func NewTufMirror(root []byte, tufPath, metadataURL, targetsURL string, versionC
|
||||
return &TufMirror{TufClient: tufClient, tufPath: tufPath, metadataURL: metadataURL, targetsURL: targetsURL}, nil
|
||||
}
|
||||
|
||||
func PushToRegistry(image any, imageName string) error {
|
||||
// Parse the image name
|
||||
func PushImageToRegistry(image v1.Image, imageName string) error {
|
||||
ref, err := name.ParseReference(imageName)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to parse image name: %v", err)
|
||||
}
|
||||
// Get the authenticator from the default Docker keychain
|
||||
auth, err := authn.DefaultKeychain.Resolve(ref.Context())
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to get authenticator: %v", err)
|
||||
return fmt.Errorf("Failed to parse image name '%s': %w", imageName, err)
|
||||
}
|
||||
|
||||
// Push the image to the registry
|
||||
switch image := image.(type) {
|
||||
case *v1.Image:
|
||||
if err := remote.Write(ref, *image, remote.WithAuth(auth)); err != nil {
|
||||
return fmt.Errorf("failed to push image %s: %w", imageName, err)
|
||||
}
|
||||
case *v1.ImageIndex:
|
||||
if err := remote.WriteIndex(ref, *image, remote.WithAuth(auth)); err != nil {
|
||||
return fmt.Errorf("failed to push image index %s: %w", imageName, err)
|
||||
}
|
||||
default:
|
||||
if err := remote.WriteIndex(ref, image.(v1.ImageIndex), remote.WithAuth(auth)); err != nil {
|
||||
return fmt.Errorf("failed to push image index %s: %w", imageName, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return remote.Write(ref, image, oci.MultiKeychainOption())
|
||||
}
|
||||
|
||||
func SaveAsOCILayout(image any, path string) error {
|
||||
func PushIndexToRegistry(image v1.ImageIndex, imageName string) error {
|
||||
// Parse the index name
|
||||
ref, err := name.ParseReference(imageName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to parse image name: %w", err)
|
||||
}
|
||||
|
||||
// Push the index to the registry
|
||||
return remote.WriteIndex(ref, image, oci.MultiKeychainOption())
|
||||
}
|
||||
|
||||
func SaveImageAsOCILayout(image v1.Image, path string) error {
|
||||
// Save the image to the local filesystem
|
||||
err := os.MkdirAll(path, os.FileMode(0744))
|
||||
err := os.MkdirAll(path, os.ModePerm)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create directory: %w", err)
|
||||
}
|
||||
switch image := image.(type) {
|
||||
case *v1.Image:
|
||||
index := empty.Index
|
||||
l, err := layout.Write(path, index)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create index: %w", err)
|
||||
}
|
||||
err = l.AppendImage(*image)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to append image to index: %w", err)
|
||||
}
|
||||
case *v1.ImageIndex:
|
||||
_, err := layout.Write(path, *image)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create index: %w", err)
|
||||
}
|
||||
default:
|
||||
_, err := layout.Write(path, image.(v1.ImageIndex))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create index: %w", err)
|
||||
}
|
||||
index := empty.Index
|
||||
l, err := layout.Write(path, index)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create index: %w", err)
|
||||
}
|
||||
return l.AppendImage(image)
|
||||
}
|
||||
|
||||
func SaveIndexAsOCILayout(image v1.ImageIndex, path string) error {
|
||||
// Save the index to the local filesystem
|
||||
err := os.MkdirAll(path, os.ModePerm)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create directory: %w", err)
|
||||
}
|
||||
|
||||
_, err = layout.Write(path, image)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create index: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ func (m *TufMirror) GetTufTargetMirrors() ([]*MirrorImage, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to append role layer to image: %w", err)
|
||||
}
|
||||
targetMirrors = append(targetMirrors, &MirrorImage{Image: &img, Tag: name})
|
||||
targetMirrors = append(targetMirrors, &MirrorImage{Image: img, Tag: name})
|
||||
}
|
||||
return targetMirrors, nil
|
||||
}
|
||||
@@ -103,7 +103,7 @@ func (m *TufMirror) GetDelegatedTargetMirrors() ([]*MirrorIndex, error) {
|
||||
},
|
||||
})
|
||||
}
|
||||
mirror = append(mirror, &MirrorIndex{Index: &index, Tag: role.Name})
|
||||
mirror = append(mirror, &MirrorIndex{Index: index, Tag: role.Name})
|
||||
}
|
||||
return mirror, nil
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ func TestGetTufTargetsMirror(t *testing.T) {
|
||||
defer server.Close()
|
||||
|
||||
path := test.CreateTempDir(t, "", "tuf_temp")
|
||||
m, err := NewTufMirror(embed.DevRoot, path, server.URL+"/metadata", server.URL+"/targets", tuf.NewMockVersionChecker())
|
||||
m, err := NewTufMirror(embed.RootDev.Data, path, server.URL+"/metadata", server.URL+"/targets", tuf.NewMockVersionChecker())
|
||||
assert.NoError(t, err)
|
||||
|
||||
targets, err := m.GetTufTargetMirrors()
|
||||
@@ -36,7 +36,7 @@ func TestGetTufTargetsMirror(t *testing.T) {
|
||||
|
||||
// check for image layer annotations
|
||||
for _, target := range targets {
|
||||
img := *target.Image
|
||||
img := target.Image
|
||||
mf, err := img.RawManifest()
|
||||
assert.NoError(t, err)
|
||||
|
||||
@@ -61,7 +61,7 @@ func TestTargetDelegationMetadata(t *testing.T) {
|
||||
defer server.Close()
|
||||
|
||||
path := test.CreateTempDir(t, "", "tuf_temp")
|
||||
tm, err := NewTufMirror(embed.DevRoot, path, server.URL+"/metadata", server.URL+"/targets", tuf.NewMockVersionChecker())
|
||||
tm, err := NewTufMirror(embed.RootDev.Data, path, server.URL+"/metadata", server.URL+"/targets", tuf.NewMockVersionChecker())
|
||||
assert.NoError(t, err)
|
||||
|
||||
targets, err := tm.TufClient.LoadDelegatedTargets("test-role", "targets")
|
||||
@@ -74,7 +74,7 @@ func TestGetDelegatedTargetMirrors(t *testing.T) {
|
||||
defer server.Close()
|
||||
|
||||
path := test.CreateTempDir(t, "", "tuf_temp")
|
||||
m, err := NewTufMirror(embed.DevRoot, path, server.URL+"/metadata", server.URL+"/targets", tuf.NewMockVersionChecker())
|
||||
m, err := NewTufMirror(embed.RootDev.Data, path, server.URL+"/metadata", server.URL+"/targets", tuf.NewMockVersionChecker())
|
||||
assert.NoError(t, err)
|
||||
|
||||
mirrors, err := m.GetDelegatedTargetMirrors()
|
||||
@@ -83,7 +83,7 @@ func TestGetDelegatedTargetMirrors(t *testing.T) {
|
||||
|
||||
// check for index image annotations
|
||||
for _, mirror := range mirrors {
|
||||
idx := *mirror.Index
|
||||
idx := mirror.Index
|
||||
mf, err := idx.RawManifest()
|
||||
assert.NoError(t, err)
|
||||
|
||||
|
||||
@@ -32,12 +32,12 @@ type DelegatedTargetMetadata struct {
|
||||
}
|
||||
|
||||
type MirrorImage struct {
|
||||
Image *v1.Image
|
||||
Image v1.Image
|
||||
Tag string
|
||||
}
|
||||
|
||||
type MirrorIndex struct {
|
||||
Index *v1.ImageIndex
|
||||
Index v1.ImageIndex
|
||||
Tag string
|
||||
}
|
||||
|
||||
|
||||
21
pkg/oci/authn.go
Normal file
21
pkg/oci/authn.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package oci
|
||||
|
||||
import (
|
||||
ecr "github.com/awslabs/amazon-ecr-credential-helper/ecr-login"
|
||||
"github.com/google/go-containerregistry/pkg/authn"
|
||||
"github.com/google/go-containerregistry/pkg/v1/google"
|
||||
"github.com/google/go-containerregistry/pkg/v1/remote"
|
||||
)
|
||||
|
||||
func MultiKeychainOption() remote.Option {
|
||||
return remote.WithAuthFromKeychain(MultiKeychainAll())
|
||||
}
|
||||
|
||||
func MultiKeychainAll() authn.Keychain {
|
||||
// Create a multi-keychain that will use the default Docker, Google, or ECR keychain
|
||||
return authn.NewMultiKeychain(
|
||||
authn.DefaultKeychain,
|
||||
google.Keychain,
|
||||
authn.NewKeychainFromHelper(ecr.NewECRHelper()),
|
||||
)
|
||||
}
|
||||
152
pkg/oci/layout.go
Normal file
152
pkg/oci/layout.go
Normal file
@@ -0,0 +1,152 @@
|
||||
package oci
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/attest/pkg/attestation"
|
||||
att "github.com/docker/attest/pkg/attestation"
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/google/go-containerregistry/pkg/v1/layout"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// implementation of AttestationResolver that closes over attestations from an oci layout
|
||||
type OCILayoutResolver struct {
|
||||
*AttestationManifest
|
||||
*ImageSpec
|
||||
}
|
||||
|
||||
func NewOCILayoutAttestationResolver(src *ImageSpec) (*OCILayoutResolver, error) {
|
||||
r := &OCILayoutResolver{
|
||||
ImageSpec: src,
|
||||
}
|
||||
_, err := r.fetchAttestationManifest()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (r *OCILayoutResolver) fetchAttestationManifest() (*AttestationManifest, error) {
|
||||
if r.AttestationManifest == nil {
|
||||
m, err := attestationManifestFromOCILayout(r.Identifier, r.ImageSpec.Platform)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.AttestationManifest = m
|
||||
}
|
||||
|
||||
return r.AttestationManifest, nil
|
||||
}
|
||||
|
||||
func (r *OCILayoutResolver) Attestations(ctx context.Context, predicateType string) ([]*att.Envelope, error) {
|
||||
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[attestation.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) {
|
||||
return r.Name, nil
|
||||
}
|
||||
|
||||
func (r *OCILayoutResolver) ImageDigest(ctx context.Context) (string, error) {
|
||||
return r.Digest, nil
|
||||
}
|
||||
|
||||
func (r *OCILayoutResolver) ImagePlatform(ctx context.Context) (*v1.Platform, error) {
|
||||
return r.ImageSpec.Platform, nil
|
||||
}
|
||||
|
||||
func attestationManifestFromOCILayout(path string, platform *v1.Platform) (*AttestationManifest, error) {
|
||||
idx, err := layout.ImageIndexFromPath(path)
|
||||
if err != nil {
|
||||
return nil, 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)
|
||||
}
|
||||
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[att.DockerReferenceType] != attestation.AttestationManifestType {
|
||||
continue
|
||||
}
|
||||
|
||||
if mf.Annotations[att.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")
|
||||
}
|
||||
285
pkg/oci/oci.go
285
pkg/oci/oci.go
@@ -9,10 +9,7 @@ import (
|
||||
"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"
|
||||
"github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/common"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
@@ -20,9 +17,9 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// parsePlatform parses the provided platform string or attempts to obtain
|
||||
// ParsePlatform parses the provided platform string or attempts to obtain
|
||||
// the platform of the current host system
|
||||
func parsePlatform(platformStr string) (*v1.Platform, error) {
|
||||
func ParsePlatform(platformStr string) (*v1.Platform, error) {
|
||||
if platformStr == "" {
|
||||
cdp := platforms.Normalize(platforms.DefaultSpec())
|
||||
if cdp.OS != "windows" {
|
||||
@@ -38,257 +35,9 @@ func parsePlatform(platformStr string) (*v1.Platform, error) {
|
||||
}
|
||||
}
|
||||
|
||||
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[att.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 {
|
||||
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)}
|
||||
options := []remote.Option{MultiKeychainOption(), 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 {
|
||||
@@ -299,19 +48,17 @@ func withOptions(ctx context.Context, platform *v1.Platform) []remote.Option {
|
||||
|
||||
func ExtractEnvelopes(ia *AttestationManifest, predicateType string) ([]*att.Envelope, error) {
|
||||
manifest := ia.Manifest
|
||||
im := ia.Image
|
||||
|
||||
image := ia.Image
|
||||
var envs []*att.Envelope
|
||||
|
||||
ls, err := im.Layers()
|
||||
layers, err := image.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()
|
||||
l.Annotations[att.InTotoPredicateType] == predicateType {
|
||||
reader, err := layers[i].Uncompressed()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get layer contents: %w", err)
|
||||
}
|
||||
@@ -340,7 +87,7 @@ func imageDigestForPlatform(ix *v1.IndexManifest, platform *v1.Platform) (string
|
||||
func attestationDigestForDigest(ix *v1.IndexManifest, imageDigest string, attestType string) (string, error) {
|
||||
for _, m := range ix.Manifests {
|
||||
if v, ok := m.Annotations[att.DockerReferenceType]; ok && v == attestType {
|
||||
if d, ok := m.Annotations[DockerReferenceDigest]; ok && d == imageDigest {
|
||||
if d, ok := m.Annotations[att.DockerReferenceDigest]; ok && d == imageDigest {
|
||||
return m.Digest.String(), nil
|
||||
}
|
||||
}
|
||||
@@ -348,7 +95,7 @@ func attestationDigestForDigest(ix *v1.IndexManifest, imageDigest string, attest
|
||||
return "", errors.New(fmt.Sprintf("no attestation found for image %s", imageDigest))
|
||||
}
|
||||
|
||||
func RefToPURL(ref string, platform string) (string, bool, error) {
|
||||
func RefToPURL(ref string, platform *v1.Platform) (string, bool, error) {
|
||||
var isCanonical bool
|
||||
named, err := reference.ParseNormalizedNamed(ref)
|
||||
if err != nil {
|
||||
@@ -380,14 +127,10 @@ func RefToPURL(ref string, platform string) (string, bool, error) {
|
||||
}
|
||||
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 {
|
||||
if platform != nil {
|
||||
qualifiers = append(qualifiers, packageurl.Qualifier{
|
||||
Key: "platform",
|
||||
Value: pf.String(),
|
||||
Value: platform.String(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -395,12 +138,12 @@ func RefToPURL(ref string, platform string) (string, bool, error) {
|
||||
return p.ToString(), isCanonical, nil
|
||||
}
|
||||
|
||||
func SplitDigest(digest string) (*common.DigestSet, error) {
|
||||
func SplitDigest(digest string) (common.DigestSet, error) {
|
||||
parts := strings.SplitN(digest, ":", 2)
|
||||
if len(parts) != 2 {
|
||||
return nil, fmt.Errorf("invalid digest %q", digest)
|
||||
}
|
||||
return &common.DigestSet{
|
||||
return common.DigestSet{
|
||||
parts[0]: parts[1],
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -6,45 +6,48 @@ import (
|
||||
|
||||
"github.com/google/go-containerregistry/pkg/v1/layout"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestRefToPurl(t *testing.T) {
|
||||
purl, canonical, err := RefToPURL("alpine", "arm64/linux")
|
||||
arm, err := ParsePlatform("arm64/linux")
|
||||
require.NoError(t, err)
|
||||
purl, canonical, err := RefToPURL("alpine", arm)
|
||||
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")
|
||||
purl, canonical, err = RefToPURL("alpine:123", arm)
|
||||
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")
|
||||
purl, canonical, err = RefToPURL("google/alpine:123", arm)
|
||||
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")
|
||||
purl, canonical, err = RefToPURL("library/alpine:123", arm)
|
||||
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")
|
||||
purl, canonical, err = RefToPURL("docker.io/library/alpine:123", arm)
|
||||
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")
|
||||
purl, canonical, err = RefToPURL("localhost:5001/library/alpine:123", arm)
|
||||
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")
|
||||
purl, canonical, err = RefToPURL("localhost:5001/alpine:123", arm)
|
||||
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")
|
||||
purl, canonical, err = RefToPURL("localhost:5001/alpine@sha256:c5b1261d6d3e43071626931fc004f70149baeba2c8ec672bd4f27761f8e1ad6b", arm)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "pkg:docker/localhost%3A5001/alpine?digest=sha256%3Ac5b1261d6d3e43071626931fc004f70149baeba2c8ec672bd4f27761f8e1ad6b&platform=arm64%2Flinux", purl)
|
||||
assert.True(t, canonical)
|
||||
@@ -70,15 +73,36 @@ func TestImageDigestForPlatform(t *testing.T) {
|
||||
mfs2, err := mfs.IndexManifest()
|
||||
assert.NoError(t, err)
|
||||
|
||||
p, err := parsePlatform("linux/amd64")
|
||||
p, err := ParsePlatform("linux/amd64")
|
||||
assert.NoError(t, err)
|
||||
digest, err := imageDigestForPlatform(mfs2, p)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "sha256:da8b190665956ea07890a0273e2a9c96bfe291662f08e2860e868eef69c34620", digest)
|
||||
|
||||
p, err = parsePlatform("linux/arm64")
|
||||
p, err = ParsePlatform("linux/arm64")
|
||||
assert.NoError(t, err)
|
||||
digest, err = imageDigestForPlatform(mfs2, p)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "sha256:7a76cec943853f9f7105b1976afa1bf7cd5bb6afc4e9d5852dd8da7cf81ae86e", digest)
|
||||
}
|
||||
|
||||
func TestWithoutTag(t *testing.T) {
|
||||
tc := []struct {
|
||||
name string
|
||||
expected string
|
||||
}{
|
||||
{name: "image:tag", expected: "index.docker.io/library/image"},
|
||||
{name: "image", expected: "index.docker.io/library/image"},
|
||||
{name: "image:sha256-digest.att", expected: "index.docker.io/library/image"},
|
||||
{name: "docker://image:tag", expected: "docker://index.docker.io/library/image"},
|
||||
{name: "image@sha256:166710df254975d4a6c4c407c315951c22753dcaa829e020a3fd5d18fff70dd2", expected: "index.docker.io/library/image"},
|
||||
{name: "docker://image@sha256:166710df254975d4a6c4c407c315951c22753dcaa829e020a3fd5d18fff70dd2", expected: "docker://index.docker.io/library/image"},
|
||||
{name: "docker://127.0.0.1:36555/repo:latest", expected: "docker://127.0.0.1:36555/repo"},
|
||||
}
|
||||
for _, c := range tc {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
notag, _ := WithoutTag(c.name)
|
||||
assert.Equal(t, c.expected, notag)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
121
pkg/oci/referrers.go
Normal file
121
pkg/oci/referrers.go
Normal file
@@ -0,0 +1,121 @@
|
||||
package oci
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
att "github.com/docker/attest/pkg/attestation"
|
||||
"github.com/google/go-containerregistry/pkg/name"
|
||||
"github.com/google/go-containerregistry/pkg/v1/remote"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type ReferrersResolver struct {
|
||||
digest string
|
||||
referrersRepo string
|
||||
manifests []*AttestationManifest
|
||||
*RegistryImageDetailsResolver
|
||||
}
|
||||
|
||||
func NewReferrersAttestationResolver(src *RegistryImageDetailsResolver, options ...func(*ReferrersResolver) error) (*ReferrersResolver, error) {
|
||||
res := &ReferrersResolver{
|
||||
RegistryImageDetailsResolver: src,
|
||||
}
|
||||
for _, opt := range options {
|
||||
err := opt(res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func WithReferrersRepo(repo string) func(*ReferrersResolver) error {
|
||||
return func(r *ReferrersResolver) error {
|
||||
r.referrersRepo = repo
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (r *ReferrersResolver) resolveAttestations(ctx context.Context) error {
|
||||
if r.manifests == nil {
|
||||
subjectRef, err := name.ParseReference(r.Identifier)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse reference: %w", err)
|
||||
}
|
||||
subjectDigest, err := r.ImageDigest(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get digest: %w", err)
|
||||
}
|
||||
var referrersSubjectRef name.Digest
|
||||
if r.referrersRepo != "" {
|
||||
referrersSubjectRef, err = name.NewDigest(fmt.Sprintf("%s@%s", r.referrersRepo, subjectDigest))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create referrers reference: %w", err)
|
||||
}
|
||||
} else {
|
||||
referrersSubjectRef = subjectRef.Context().Digest(subjectDigest)
|
||||
}
|
||||
referrersIndex, err := remote.Referrers(referrersSubjectRef)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get referrers: %w", err)
|
||||
}
|
||||
referrersIndexManifest, err := referrersIndex.IndexManifest()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get index manifest: %w", err)
|
||||
}
|
||||
if len(referrersIndexManifest.Manifests) == 0 {
|
||||
return errors.New("no referrers found")
|
||||
}
|
||||
aManifests := make([]*AttestationManifest, 0)
|
||||
for _, m := range referrersIndexManifest.Manifests {
|
||||
|
||||
remoteRef := referrersSubjectRef.Context().Digest(m.Digest.String())
|
||||
attestationImage, err := remote.Image(remoteRef)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get referred image: %w", err)
|
||||
}
|
||||
manifest, err := attestationImage.Manifest()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get manifest: %w", err)
|
||||
}
|
||||
if manifest.Annotations[att.DockerReferenceType] != att.AttestationManifestType {
|
||||
continue
|
||||
}
|
||||
if manifest.Annotations[att.DockerReferenceDigest] != subjectDigest {
|
||||
continue
|
||||
}
|
||||
attest := &AttestationManifest{
|
||||
Name: r.Identifier,
|
||||
Image: attestationImage,
|
||||
Manifest: manifest,
|
||||
Descriptor: &m,
|
||||
Digest: subjectDigest,
|
||||
Platform: r.Platform,
|
||||
}
|
||||
aManifests = append(aManifests, attest)
|
||||
}
|
||||
|
||||
if len(aManifests) == 0 {
|
||||
return errors.New("no attestation manifests found")
|
||||
}
|
||||
r.manifests = aManifests
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *ReferrersResolver) Attestations(ctx context.Context, predicateType string) ([]*att.Envelope, error) {
|
||||
err := r.resolveAttestations(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to resolve attestations: %w", err)
|
||||
}
|
||||
var envs []*att.Envelope
|
||||
for _, attest := range r.manifests {
|
||||
es, err := ExtractEnvelopes(attest, predicateType)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to extract envelopes: %w", err)
|
||||
}
|
||||
envs = append(envs, es...)
|
||||
}
|
||||
return envs, nil
|
||||
}
|
||||
129
pkg/oci/registry.go
Normal file
129
pkg/oci/registry.go
Normal file
@@ -0,0 +1,129 @@
|
||||
package oci
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
att "github.com/docker/attest/pkg/attestation"
|
||||
"github.com/google/go-containerregistry/pkg/name"
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/google/go-containerregistry/pkg/v1/remote"
|
||||
)
|
||||
|
||||
type RegistryResolver struct {
|
||||
*RegistryImageDetailsResolver
|
||||
*AttestationManifest
|
||||
}
|
||||
|
||||
type RegistryImageDetailsResolver struct {
|
||||
*ImageSpec
|
||||
digest string
|
||||
}
|
||||
|
||||
func NewRegistryImageDetailsResolver(src *ImageSpec) (*RegistryImageDetailsResolver, error) {
|
||||
return &RegistryImageDetailsResolver{
|
||||
ImageSpec: src,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewRegistryAttestationResolver(src *RegistryImageDetailsResolver) (*RegistryResolver, error) {
|
||||
return &RegistryResolver{
|
||||
RegistryImageDetailsResolver: src,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *RegistryImageDetailsResolver) ImageName(ctx context.Context) (string, error) {
|
||||
return r.Identifier, nil
|
||||
}
|
||||
|
||||
func (r *RegistryImageDetailsResolver) ImagePlatform(ctx context.Context) (*v1.Platform, error) {
|
||||
return r.Platform, nil
|
||||
}
|
||||
|
||||
func (r *RegistryImageDetailsResolver) ImageDigest(ctx context.Context) (string, error) {
|
||||
if r.digest == "" {
|
||||
subjectRef, err := name.ParseReference(r.Identifier)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to parse reference: %w", err)
|
||||
}
|
||||
options := WithOptions(ctx, r.Platform)
|
||||
desc, err := remote.Image(subjectRef, options...)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get image manifest: %w", err)
|
||||
}
|
||||
subjectDigest, err := desc.Digest()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get image digest: %w", err)
|
||||
}
|
||||
r.digest = subjectDigest.String()
|
||||
}
|
||||
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.Identifier, r.ImageSpec.Platform)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.AttestationManifest = attest
|
||||
}
|
||||
return ExtractEnvelopes(r.AttestationManifest, predicateType)
|
||||
}
|
||||
|
||||
func FetchAttestationManifest(ctx context.Context, image string, platform *v1.Platform) (*AttestationManifest, error) {
|
||||
// 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)
|
||||
}
|
||||
index, err := remote.Index(ref, options...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get index: %w", err)
|
||||
}
|
||||
indexManifest, err := index.IndexManifest()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get index manifest: %w", err)
|
||||
}
|
||||
digest, err := imageDigestForPlatform(indexManifest, 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(indexManifest, 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
|
||||
}
|
||||
53
pkg/oci/registry_test.go
Normal file
53
pkg/oci/registry_test.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package oci_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/attest/internal/test"
|
||||
"github.com/docker/attest/pkg/attest"
|
||||
"github.com/docker/attest/pkg/attestation"
|
||||
"github.com/docker/attest/pkg/mirror"
|
||||
"github.com/docker/attest/pkg/oci"
|
||||
"github.com/docker/attest/pkg/policy"
|
||||
"github.com/google/go-containerregistry/pkg/registry"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestRegistry(t *testing.T) {
|
||||
ctx, signer := test.Setup(t)
|
||||
server := httptest.NewServer(registry.New(registry.WithReferrersSupport(false)))
|
||||
defer server.Close()
|
||||
u, err := url.Parse(server.URL)
|
||||
require.NoError(t, err)
|
||||
|
||||
opts := &attestation.SigningOptions{
|
||||
Replace: true,
|
||||
SkipSubject: true,
|
||||
}
|
||||
attIdx, err := oci.IndexFromPath(oci.UnsignedTestImage)
|
||||
require.NoError(t, err)
|
||||
signedManifests, err := attest.SignStatements(ctx, attIdx.Index, signer, opts)
|
||||
require.NoError(t, err)
|
||||
signedIndex := attIdx.Index
|
||||
signedIndex, err = attestation.AddImagesToIndex(signedIndex, signedManifests)
|
||||
require.NoError(t, err)
|
||||
|
||||
indexName := fmt.Sprintf("%s/repo:root", u.Host)
|
||||
require.NoError(t, err)
|
||||
err = mirror.PushIndexToRegistry(signedIndex, indexName)
|
||||
require.NoError(t, err)
|
||||
|
||||
spec, err := oci.ParseImageSpec(indexName)
|
||||
require.NoError(t, err)
|
||||
|
||||
resolver, err := policy.CreateImageDetailsResolver(spec)
|
||||
require.NoError(t, err)
|
||||
digest, err := resolver.ImageDigest(ctx)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, strings.Contains(digest, "sha256:"))
|
||||
}
|
||||
@@ -7,6 +7,10 @@ import (
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
)
|
||||
|
||||
type AttestationManifests struct {
|
||||
Manifests []*AttestationManifest
|
||||
}
|
||||
|
||||
type AttestationManifest struct {
|
||||
// attestation image details
|
||||
Image v1.Image
|
||||
@@ -19,12 +23,16 @@ type AttestationManifest struct {
|
||||
}
|
||||
|
||||
type AttestationResolver interface {
|
||||
ImageName(ctx context.Context) (string, error)
|
||||
ImagePlatformStr() string
|
||||
ImageDigest(ctx context.Context) (string, error)
|
||||
ImageDetailsResolver
|
||||
Attestations(ctx context.Context, mediaType string) ([]*att.Envelope, error)
|
||||
}
|
||||
|
||||
type ImageDetailsResolver interface {
|
||||
ImageName(ctx context.Context) (string, error)
|
||||
ImagePlatform(ctx context.Context) (*v1.Platform, error)
|
||||
ImageDigest(ctx context.Context) (string, error)
|
||||
}
|
||||
|
||||
type MockResolver struct {
|
||||
Envs []*att.Envelope
|
||||
}
|
||||
@@ -41,6 +49,6 @@ func (r MockResolver) ImageDigest(ctx context.Context) (string, error) {
|
||||
return "sha256:test-digest", nil
|
||||
}
|
||||
|
||||
func (r MockResolver) ImagePlatformStr() string {
|
||||
return "linux/amd64"
|
||||
func (r MockResolver) ImagePlatform(ctx context.Context) (*v1.Platform, error) {
|
||||
return ParsePlatform("linux/amd64")
|
||||
}
|
||||
|
||||
149
pkg/oci/types.go
149
pkg/oci/types.go
@@ -2,9 +2,8 @@ package oci
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"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"
|
||||
@@ -12,18 +11,36 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
DockerReferenceDigest = "vnd.docker.reference.digest"
|
||||
AttestationManifestType = "attestation-manifest"
|
||||
InTotoPredicateType = "in-toto.io/predicate-type"
|
||||
OciReferenceTarget = "org.opencontainers.image.ref.name"
|
||||
OciReferenceTarget = "org.opencontainers.image.ref.name"
|
||||
LocalPrefix = "oci://"
|
||||
RegistryPrefix = "docker://"
|
||||
OCI SourceType = "OCI"
|
||||
Docker SourceType = "Docker"
|
||||
)
|
||||
|
||||
type AttestationIndex struct {
|
||||
type SourceType string
|
||||
type NamedIndex struct {
|
||||
Index v1.ImageIndex
|
||||
Name string
|
||||
}
|
||||
|
||||
func AttestationIndexFromPath(path string) (*AttestationIndex, error) {
|
||||
type AttestationOptions struct {
|
||||
NoReferrers bool
|
||||
Attach bool
|
||||
ReferrersRepo string
|
||||
}
|
||||
|
||||
type ImageSpecOption func(*ImageSpec) error
|
||||
|
||||
type ImageSpec struct {
|
||||
// OCI or Docker
|
||||
Type SourceType
|
||||
// without oci:// or docker:// (name or path)
|
||||
Identifier string
|
||||
Platform *v1.Platform
|
||||
}
|
||||
|
||||
func IndexFromPath(path string) (*NamedIndex, error) {
|
||||
wrapperIdx, err := layout.ImageIndexFromPath(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load image index: %w", err)
|
||||
@@ -40,29 +57,125 @@ func AttestationIndexFromPath(path string) (*AttestationIndex, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to extract ImageIndex for digest %s: %w", idxDigest.String(), err)
|
||||
}
|
||||
return &AttestationIndex{
|
||||
return &NamedIndex{
|
||||
Index: idx,
|
||||
Name: imageName,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func AttestationIndexFromRemote(image string) (*AttestationIndex, error) {
|
||||
func IndexFromRemote(image string) (*NamedIndex, error) {
|
||||
ref, err := name.ParseReference(image)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to parse image name: %v", err)
|
||||
}
|
||||
// Get the authenticator from the default Docker keychain
|
||||
auth, err := authn.DefaultKeychain.Resolve(ref.Context())
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to get authenticator: %v", err)
|
||||
return nil, fmt.Errorf("failed to parse image reference %s: %w", image, err)
|
||||
}
|
||||
|
||||
// Pull the image from the registry
|
||||
idx, err := remote.Index(ref, remote.WithAuth(auth))
|
||||
idx, err := remote.Index(ref, MultiKeychainOption())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to pull image %s: %w", image, err)
|
||||
}
|
||||
return &AttestationIndex{
|
||||
return &NamedIndex{
|
||||
Index: idx,
|
||||
Name: image,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func LoadIndex(input *ImageSpec) (*NamedIndex, error) {
|
||||
if input.Type == OCI {
|
||||
return IndexFromPath(input.Identifier)
|
||||
} else {
|
||||
return IndexFromRemote(input.Identifier)
|
||||
}
|
||||
}
|
||||
|
||||
func (i *ImageSpec) ForPlatforms(platform string) ([]*ImageSpec, error) {
|
||||
platforms := strings.Split(platform, ",")
|
||||
var specs []*ImageSpec
|
||||
for _, pStr := range platforms {
|
||||
p, err := ParsePlatform(pStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
spec := &ImageSpec{
|
||||
Type: i.Type,
|
||||
Identifier: i.Identifier,
|
||||
Platform: p,
|
||||
}
|
||||
specs = append(specs, spec)
|
||||
}
|
||||
return specs, nil
|
||||
}
|
||||
|
||||
func ParseImageSpec(img string, options ...ImageSpecOption) (*ImageSpec, error) {
|
||||
img = strings.TrimSpace(img)
|
||||
if strings.Contains(img, ",") {
|
||||
return nil, fmt.Errorf("only one image is supported")
|
||||
}
|
||||
withoutPrefix := strings.TrimPrefix(strings.TrimPrefix(img, LocalPrefix), RegistryPrefix)
|
||||
src := &ImageSpec{
|
||||
Identifier: withoutPrefix,
|
||||
}
|
||||
if strings.HasPrefix(img, LocalPrefix) {
|
||||
src.Type = OCI
|
||||
} else {
|
||||
src.Type = Docker
|
||||
}
|
||||
for _, option := range options {
|
||||
err := option(src)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if src.Platform == nil {
|
||||
platform, err := ParsePlatform("")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
src.Platform = platform
|
||||
}
|
||||
return src, nil
|
||||
}
|
||||
|
||||
func WithPlatform(platform string) ImageSpecOption {
|
||||
return func(i *ImageSpec) error {
|
||||
if strings.Contains(platform, ",") {
|
||||
return fmt.Errorf("only one platform is supported")
|
||||
}
|
||||
p, err := ParsePlatform(platform)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
i.Platform = p
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func ParseImageSpecs(img string) ([]*ImageSpec, error) {
|
||||
outputs := strings.Split(img, ",")
|
||||
var sources []*ImageSpec
|
||||
for _, output := range outputs {
|
||||
src, err := ParseImageSpec(output)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sources = append(sources, src)
|
||||
}
|
||||
return sources, nil
|
||||
}
|
||||
|
||||
func WithoutTag(image string) (string, error) {
|
||||
if strings.HasPrefix(image, LocalPrefix) {
|
||||
return image, nil
|
||||
}
|
||||
prefix := ""
|
||||
if strings.HasPrefix(image, RegistryPrefix) {
|
||||
image = strings.TrimPrefix(image, RegistryPrefix)
|
||||
prefix = RegistryPrefix
|
||||
}
|
||||
ref, err := name.ParseReference(image)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
repo := ref.Context().Name()
|
||||
return prefix + repo, nil
|
||||
}
|
||||
|
||||
@@ -10,94 +10,11 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/attest/pkg/config"
|
||||
"github.com/docker/attest/pkg/oci"
|
||||
"github.com/docker/attest/pkg/tuf"
|
||||
intoto "github.com/in-toto/in-toto-golang/in_toto"
|
||||
|
||||
goyaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
const (
|
||||
PolicyMappingFileName = "mapping.yaml"
|
||||
)
|
||||
|
||||
type Summary struct {
|
||||
Subjects []intoto.Subject `json:"subjects"`
|
||||
SLSALevels []string `json:"slsa_levels"`
|
||||
Verifier string `json:"verifier"`
|
||||
PolicyURI string `json:"policy_uri"`
|
||||
}
|
||||
|
||||
type Violation struct {
|
||||
Type string `json:"type"`
|
||||
Description string `json:"description"`
|
||||
Attestation *intoto.Statement `json:"attestation"`
|
||||
Details map[string]any `json:"details"`
|
||||
}
|
||||
|
||||
type Result struct {
|
||||
Success bool `json:"success"`
|
||||
Violations []Violation `json:"violations"`
|
||||
Summary Summary `json:"summary"`
|
||||
}
|
||||
|
||||
type PolicyMappings struct {
|
||||
Version string `json:"version"`
|
||||
Kind string `json:"kind"`
|
||||
Policies []PolicyMapping `json:"policies"`
|
||||
Mirrors []PolicyMirror `json:"mirrors"`
|
||||
}
|
||||
|
||||
type PolicyMapping struct {
|
||||
Id string `json:"id"`
|
||||
Description string `json:"description"`
|
||||
Origin PolicyOrigin `json:"origin"`
|
||||
Files []PolicyMappingFile `json:"files"`
|
||||
}
|
||||
|
||||
type PolicyMappingFile struct {
|
||||
Path string `json:"path"`
|
||||
}
|
||||
|
||||
type PolicyMirror struct {
|
||||
PolicyId string `yaml:"policy-id"`
|
||||
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 Policy struct {
|
||||
InputFiles []*PolicyFile
|
||||
Query 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) (*Policy, error) {
|
||||
func resolveLocalPolicy(opts *PolicyOptions, mapping *config.PolicyMapping) (*Policy, error) {
|
||||
if opts.LocalPolicyDir == "" {
|
||||
return nil, fmt.Errorf("local policy dir not set")
|
||||
}
|
||||
@@ -116,28 +33,12 @@ func resolveLocalPolicy(opts *PolicyOptions, mapping *PolicyMapping) (*Policy, e
|
||||
}
|
||||
policy := &Policy{
|
||||
InputFiles: files,
|
||||
Mapping: mapping,
|
||||
}
|
||||
return policy, 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) (*Policy, error) {
|
||||
func resolveTufPolicy(opts *PolicyOptions, mapping *config.PolicyMapping) (*Policy, error) {
|
||||
files := make([]*PolicyFile, 0, len(mapping.Files))
|
||||
for _, f := range mapping.Files {
|
||||
filename := f.Path
|
||||
@@ -152,51 +53,74 @@ func resolveTufPolicy(opts *PolicyOptions, mapping *PolicyMapping) (*Policy, err
|
||||
}
|
||||
policy := &Policy{
|
||||
InputFiles: files,
|
||||
Mapping: mapping,
|
||||
}
|
||||
return policy, 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) {
|
||||
func findPolicyMatch(named reference.Named, mappings *config.PolicyMappings) (*config.PolicyMapping, *config.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
|
||||
return mapping, nil
|
||||
}
|
||||
}
|
||||
// now search mirrors
|
||||
for _, mirror := range mappings.Mirrors {
|
||||
if slices.Contains(mirror.Mirror.Domains, reference.Domain(named)) &&
|
||||
if (slices.Contains(mirror.Mirror.Domains, reference.Domain(named)) ||
|
||||
slices.Contains(mirror.Mirror.Domains, "*")) &&
|
||||
strings.HasPrefix(reference.Path(named), mirror.Mirror.Prefix) {
|
||||
for _, mapping := range mappings.Policies {
|
||||
if mapping.Id == mirror.PolicyId {
|
||||
return &mapping, nil
|
||||
return mapping, nil
|
||||
}
|
||||
}
|
||||
return nil, &mirror
|
||||
return nil, mirror
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func ResolvePolicy(ctx context.Context, resolver oci.AttestationResolver, opts *PolicyOptions) (*Policy, error) {
|
||||
imageName, err := resolver.ImageName(ctx)
|
||||
func resolvePolicyById(opts *PolicyOptions) (*Policy, error) {
|
||||
if opts.PolicyId != "" {
|
||||
localMappings, err := config.LoadLocalMappings(opts.LocalPolicyDir)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load local policy mappings: %w", err)
|
||||
}
|
||||
if localMappings != nil {
|
||||
for _, mapping := range localMappings.Policies {
|
||||
if mapping.Id == opts.PolicyId {
|
||||
return resolveLocalPolicy(opts, mapping)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// must check tuf
|
||||
tufMappings, err := config.LoadTufMappings(opts.TufClient, opts.LocalTargetsDir)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load tuf policy mappings by id: %w", err)
|
||||
}
|
||||
for _, mapping := range tufMappings.Policies {
|
||||
if mapping.Id == opts.PolicyId {
|
||||
return resolveTufPolicy(opts, mapping)
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("policy with id %s not found", opts.PolicyId)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func ResolvePolicy(ctx context.Context, detailsResolver oci.ImageDetailsResolver, opts *PolicyOptions) (*Policy, error) {
|
||||
p, err := resolvePolicyById(opts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to resolve policy by id: %w", err)
|
||||
}
|
||||
if p != nil {
|
||||
return p, nil
|
||||
}
|
||||
imageName, err := detailsResolver.ImageName(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get image name: %w", err)
|
||||
}
|
||||
@@ -204,7 +128,7 @@ func ResolvePolicy(ctx context.Context, resolver oci.AttestationResolver, opts *
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse image name: %w", err)
|
||||
}
|
||||
localMappings, err := LoadLocalMappings(opts)
|
||||
localMappings, err := config.LoadLocalMappings(opts.LocalPolicyDir)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load local policy mappings: %w", err)
|
||||
}
|
||||
@@ -213,16 +137,16 @@ func ResolvePolicy(ctx context.Context, resolver oci.AttestationResolver, opts *
|
||||
return resolveLocalPolicy(opts, mapping)
|
||||
}
|
||||
// must check tuf
|
||||
tufMappings, err := loadTufMappings(opts.TufClient, opts.LocalTargetsDir)
|
||||
tufMappings, err := config.LoadTufMappings(opts.TufClient, opts.LocalTargetsDir)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load tuf policy mappings: %w", err)
|
||||
return nil, fmt.Errorf("failed to load tuf policy mappings as fallback: %w", err)
|
||||
}
|
||||
|
||||
// it's a mirror of a tuf policy
|
||||
if mirror != nil {
|
||||
for _, mapping := range tufMappings.Policies {
|
||||
if mapping.Id == mirror.PolicyId {
|
||||
return resolveTufPolicy(opts, &mapping)
|
||||
return resolveTufPolicy(opts, mapping)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -234,3 +158,32 @@ func ResolvePolicy(ctx context.Context, resolver oci.AttestationResolver, opts *
|
||||
}
|
||||
return resolveTufPolicy(opts, mapping)
|
||||
}
|
||||
|
||||
func CreateImageDetailsResolver(imageSource *oci.ImageSpec) (oci.ImageDetailsResolver, error) {
|
||||
switch imageSource.Type {
|
||||
case oci.OCI:
|
||||
return oci.NewOCILayoutAttestationResolver(imageSource)
|
||||
case oci.Docker:
|
||||
return oci.NewRegistryImageDetailsResolver(imageSource)
|
||||
}
|
||||
return nil, fmt.Errorf("unsupported image source type: %s", imageSource.Type)
|
||||
}
|
||||
|
||||
func CreateAttestationResolver(resolver oci.ImageDetailsResolver, mapping *config.PolicyMapping) (oci.AttestationResolver, error) {
|
||||
switch resolver := resolver.(type) {
|
||||
case *oci.RegistryImageDetailsResolver:
|
||||
if mapping.Attestations != nil && mapping.Attestations.Style == config.AttestationStyleAttached {
|
||||
return oci.NewRegistryAttestationResolver(resolver)
|
||||
} else {
|
||||
if mapping.Attestations != nil && mapping.Attestations.Repo != "" {
|
||||
return oci.NewReferrersAttestationResolver(resolver, oci.WithReferrersRepo(mapping.Attestations.Repo))
|
||||
} else {
|
||||
return oci.NewReferrersAttestationResolver(resolver)
|
||||
}
|
||||
}
|
||||
case *oci.OCILayoutResolver:
|
||||
return resolver, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported image details resolver type: %T", resolver)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"github.com/docker/attest/internal/test"
|
||||
"github.com/docker/attest/pkg/attestation"
|
||||
"github.com/docker/attest/pkg/config"
|
||||
"github.com/docker/attest/pkg/oci"
|
||||
"github.com/docker/attest/pkg/policy"
|
||||
"github.com/docker/attest/pkg/tuf"
|
||||
@@ -31,7 +32,7 @@ func loadAttestation(t *testing.T, path string) *attestation.Envelope {
|
||||
|
||||
func TestRegoEvaluator_Evaluate(t *testing.T) {
|
||||
ctx, _ := test.Setup(t)
|
||||
|
||||
errorStr := "failed to resolve policy by id: policy with id non-existent-policy-id not found"
|
||||
TestDataPath := filepath.Join("..", "..", "test", "testdata")
|
||||
ExampleAttestation := filepath.Join(TestDataPath, "example_attestation.json")
|
||||
|
||||
@@ -47,8 +48,12 @@ func TestRegoEvaluator_Evaluate(t *testing.T) {
|
||||
isCanonical bool
|
||||
resolver oci.AttestationResolver
|
||||
policy *policy.PolicyOptions
|
||||
policyId string
|
||||
errorStr string
|
||||
}{
|
||||
{repo: "testdata/mock-tuf-allow", expectSuccess: true, isCanonical: false, resolver: defaultResolver},
|
||||
{repo: "testdata/mock-tuf-allow", expectSuccess: true, isCanonical: false, resolver: defaultResolver, policyId: "docker-official-images"},
|
||||
{repo: "testdata/mock-tuf-allow", expectSuccess: false, isCanonical: false, resolver: defaultResolver, policyId: "non-existent-policy-id", errorStr: errorStr},
|
||||
{repo: "testdata/mock-tuf-deny", expectSuccess: false, isCanonical: false, resolver: defaultResolver},
|
||||
{repo: "testdata/mock-tuf-verify-sig", expectSuccess: true, isCanonical: false, resolver: defaultResolver},
|
||||
{repo: "testdata/mock-tuf-wrong-key", expectSuccess: false, isCanonical: false, resolver: defaultResolver},
|
||||
@@ -69,11 +74,23 @@ func TestRegoEvaluator_Evaluate(t *testing.T) {
|
||||
tc.policy = &policy.PolicyOptions{
|
||||
TufClient: tufClient,
|
||||
LocalTargetsDir: test.CreateTempDir(t, "", "tuf-targets"),
|
||||
PolicyId: tc.policyId,
|
||||
}
|
||||
}
|
||||
|
||||
policy, err := policy.ResolvePolicy(ctx, tc.resolver, tc.policy)
|
||||
assert.NoErrorf(t, err, "failed to resolve policy")
|
||||
imageName, err := tc.resolver.ImageName(ctx)
|
||||
require.NoError(t, err)
|
||||
platform, err := tc.resolver.ImagePlatform(ctx)
|
||||
require.NoError(t, err)
|
||||
src, err := oci.ParseImageSpec(imageName, oci.WithPlatform(platform.String()))
|
||||
require.NoError(t, err)
|
||||
resolver, err := policy.CreateImageDetailsResolver(src)
|
||||
policy, err := policy.ResolvePolicy(ctx, resolver, tc.policy)
|
||||
if tc.errorStr != "" {
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), tc.errorStr)
|
||||
return
|
||||
}
|
||||
require.NoErrorf(t, err, "failed to resolve policy")
|
||||
result, err := re.Evaluate(ctx, tc.resolver, policy, input)
|
||||
require.NoErrorf(t, err, "Evaluate failed")
|
||||
|
||||
@@ -88,10 +105,7 @@ func TestRegoEvaluator_Evaluate(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestLoadingMappings(t *testing.T) {
|
||||
opts := &policy.PolicyOptions{
|
||||
LocalPolicyDir: filepath.Join("testdata", "mock-tuf-allow"),
|
||||
}
|
||||
policyMappings, err := policy.LoadLocalMappings(opts)
|
||||
policyMappings, err := config.LoadLocalMappings(filepath.Join("testdata", "mock-tuf-allow"))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, len(policyMappings.Mirrors), 1)
|
||||
for _, mirror := range policyMappings.Mirrors {
|
||||
|
||||
@@ -133,21 +133,42 @@ func jsonGenerator[T any]() func(t *ast.Term, ec *rego.EvalContext) (any, error)
|
||||
}
|
||||
}
|
||||
|
||||
var dynamicObj = types.NewObject(nil, types.NewDynamicProperty(types.S, types.A))
|
||||
var arrayObj = types.NewArray(nil, dynamicObj)
|
||||
var setObj = types.NewSet(dynamicObj)
|
||||
var dynamicObj = types.NewObject(nil, types.NewDynamicProperty(types.A, types.A))
|
||||
|
||||
var verifyDecl = &ast.Builtin{
|
||||
Name: "attestations.verify_envelope",
|
||||
Decl: types.NewFunction(types.Args(dynamicObj, arrayObj), dynamicObj),
|
||||
Name: "attest.verify",
|
||||
Decl: types.NewFunction(types.Args(dynamicObj, dynamicObj), dynamicObj),
|
||||
Nondeterministic: true,
|
||||
}
|
||||
var attestDecl = &ast.Builtin{
|
||||
Name: "attestations.attestation",
|
||||
Decl: types.NewFunction(types.Args(types.S), setObj),
|
||||
Name: "attest.fetch",
|
||||
Decl: types.NewFunction(types.Args(types.S), dynamicObj),
|
||||
Nondeterministic: true,
|
||||
}
|
||||
|
||||
func wrapFunctionResult(value *ast.Term, err error) (*ast.Term, error) {
|
||||
var terms [][2]*ast.Term
|
||||
if err != nil {
|
||||
terms = append(terms, [2]*ast.Term{ast.StringTerm("error"), ast.StringTerm(err.Error())})
|
||||
}
|
||||
if value != nil {
|
||||
terms = append(terms, [2]*ast.Term{ast.StringTerm("value"), value})
|
||||
}
|
||||
return ast.ObjectTerm(terms...), nil
|
||||
}
|
||||
|
||||
func handleErrors1(f func(rCtx rego.BuiltinContext, a *ast.Term) (*ast.Term, error)) rego.Builtin1 {
|
||||
return func(rCtx rego.BuiltinContext, a *ast.Term) (*ast.Term, error) {
|
||||
return wrapFunctionResult(f(rCtx, a))
|
||||
}
|
||||
}
|
||||
|
||||
func handleErrors2(f func(rCtx rego.BuiltinContext, a, b *ast.Term) (*ast.Term, error)) rego.Builtin2 {
|
||||
return func(rCtx rego.BuiltinContext, a, b *ast.Term) (*ast.Term, error) {
|
||||
return wrapFunctionResult(f(rCtx, a, b))
|
||||
}
|
||||
}
|
||||
|
||||
func RegoFunctions(resolver oci.AttestationResolver) []*tester.Builtin {
|
||||
return []*tester.Builtin{
|
||||
{
|
||||
@@ -159,7 +180,7 @@ func RegoFunctions(resolver oci.AttestationResolver) []*tester.Builtin {
|
||||
Memoize: true,
|
||||
Nondeterministic: verifyDecl.Nondeterministic,
|
||||
},
|
||||
verifyIntotoEnvelope),
|
||||
handleErrors2(verifyIntotoEnvelope)),
|
||||
},
|
||||
{
|
||||
Decl: attestDecl,
|
||||
@@ -170,12 +191,12 @@ func RegoFunctions(resolver oci.AttestationResolver) []*tester.Builtin {
|
||||
Memoize: true,
|
||||
Nondeterministic: attestDecl.Nondeterministic,
|
||||
},
|
||||
fetchIntotoAttestations(resolver)),
|
||||
handleErrors1(fetchIntotoAttestations(resolver))),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func fetchIntotoAttestations(resolver oci.AttestationResolver) func(rego.BuiltinContext, *ast.Term) (*ast.Term, error) {
|
||||
func fetchIntotoAttestations(resolver oci.AttestationResolver) rego.Builtin1 {
|
||||
return func(rCtx rego.BuiltinContext, predicateTypeTerm *ast.Term) (*ast.Term, error) {
|
||||
predicateTypeStr, ok := predicateTypeTerm.Value.(ast.String)
|
||||
if !ok {
|
||||
@@ -205,22 +226,19 @@ func fetchIntotoAttestations(resolver oci.AttestationResolver) func(rego.Builtin
|
||||
}
|
||||
}
|
||||
|
||||
func verifyIntotoEnvelope(rCtx rego.BuiltinContext, envTerm, keysTerm *ast.Term) (*ast.Term, error) {
|
||||
func verifyIntotoEnvelope(rCtx rego.BuiltinContext, envTerm, optsTerm *ast.Term) (*ast.Term, error) {
|
||||
env := new(att.Envelope)
|
||||
var keys att.Keys
|
||||
opts := new(att.VerifyOptions)
|
||||
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)
|
||||
err = ast.As(optsTerm.Value, &opts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to cast keys: %w", err)
|
||||
return nil, fmt.Errorf("failed to cast verifier options: %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)
|
||||
|
||||
payload, err := att.VerifyDSSE(rCtx.Context, env, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -242,7 +260,6 @@ func verifyIntotoEnvelope(rCtx rego.BuiltinContext, envTerm, keysTerm *ast.Term)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ast.NewTerm(value), nil
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@ policies:
|
||||
prefix: library/
|
||||
id: docker-official-images
|
||||
description: Docker Official Images
|
||||
attestations:
|
||||
repo: "localhost:5001/library-refs"
|
||||
files:
|
||||
- path: doi/policy.rego
|
||||
mirrors:
|
||||
|
||||
@@ -3,17 +3,17 @@ package attest
|
||||
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
|
||||
"id": "a0c296026645799b2a297913878e81b0aefff2a0c301e97232f717e14402f3e4",
|
||||
"key": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEgH23D1i2+ZIOtVjmfB7iFvX8AhVN\n9CPJ4ie9axw+WRHozGnRy99U2dRge3zueBBg2MweF0zrToXGig2v3YOrdw==\n-----END PUBLIC KEY-----",
|
||||
"from": "2023-12-15T14:00:00Z",
|
||||
"to": null,
|
||||
}]
|
||||
|
||||
opts := {"keys": keys}
|
||||
|
||||
success if {
|
||||
some env in attestations.attestation("foo")
|
||||
statement := attestations.verify_envelope(env, keys)
|
||||
some env in attest.fetch("foo")
|
||||
statement := attest.verify(env, opts)
|
||||
}
|
||||
|
||||
result := {
|
||||
"success": success
|
||||
}
|
||||
result := {"success": success}
|
||||
|
||||
@@ -3,19 +3,28 @@ package attest
|
||||
import rego.v1
|
||||
|
||||
keys := [{
|
||||
"id": "a0c296026645799b2a297913878e81b0aefff2a0c301e97232f717e14402f3e4",
|
||||
"key": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEHyZpSgzvqFqNv7f3x7865OS38rAb\nQMcff55zM2UH/KR3Pr84a8QsGDNgaNGzJQJWjtMSgfV8WnNoffNK+svFNg==\n-----END PUBLIC KEY-----",
|
||||
"from": "2023-12-15T14:00:00Z",
|
||||
"to": null,
|
||||
"id": "a0c296026645799b2a297913878e81b0aefff2a0c301e97232f717e14402f3e4",
|
||||
"key": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEHyZpSgzvqFqNv7f3x7865OS38rAb\nQMcff55zM2UH/KR3Pr84a8QsGDNgaNGzJQJWjtMSgfV8WnNoffNK+svFNg==\n-----END PUBLIC KEY-----",
|
||||
"from": "2023-12-15T14:00:00Z",
|
||||
"to": null,
|
||||
}]
|
||||
|
||||
default success := false
|
||||
|
||||
success if {
|
||||
some env in attestations.attestation("foo")
|
||||
statement := attestations.verify_envelope(env, keys)
|
||||
provs(pred) := p if {
|
||||
res := attest.fetch(pred)
|
||||
not res.error
|
||||
p := res.value
|
||||
}
|
||||
|
||||
result := {
|
||||
"success": success
|
||||
atts := union({provs("foo")})
|
||||
|
||||
opts := {"keys": keys}
|
||||
|
||||
success if {
|
||||
some env in atts
|
||||
res := attest.verify(env, opts)
|
||||
not res.error
|
||||
}
|
||||
|
||||
result := {"success": success}
|
||||
|
||||
53
pkg/policy/types.go
Normal file
53
pkg/policy/types.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package policy
|
||||
|
||||
import (
|
||||
"github.com/docker/attest/pkg/config"
|
||||
"github.com/docker/attest/pkg/tuf"
|
||||
intoto "github.com/in-toto/in-toto-golang/in_toto"
|
||||
)
|
||||
|
||||
type Summary struct {
|
||||
Subjects []intoto.Subject `json:"subjects"`
|
||||
SLSALevels []string `json:"slsa_levels"`
|
||||
Verifier string `json:"verifier"`
|
||||
PolicyURI string `json:"policy_uri"`
|
||||
}
|
||||
|
||||
type Violation struct {
|
||||
Type string `json:"type"`
|
||||
Description string `json:"description"`
|
||||
Attestation *intoto.Statement `json:"attestation"`
|
||||
Details map[string]any `json:"details"`
|
||||
}
|
||||
|
||||
type Result struct {
|
||||
Success bool `json:"success"`
|
||||
Violations []Violation `json:"violations"`
|
||||
Summary Summary `json:"summary"`
|
||||
}
|
||||
|
||||
type PolicyOptions struct {
|
||||
TufClient tuf.TUFClient
|
||||
LocalTargetsDir string
|
||||
LocalPolicyDir string
|
||||
PolicyId string
|
||||
ReferrersRepo string
|
||||
AttestationStyle config.AttestationStyle
|
||||
}
|
||||
|
||||
type Policy struct {
|
||||
InputFiles []*PolicyFile
|
||||
Query string
|
||||
Mapping *config.PolicyMapping
|
||||
}
|
||||
|
||||
type PolicyInput struct {
|
||||
Digest string `json:"digest"`
|
||||
Purl string `json:"purl"`
|
||||
IsCanonical bool `json:"isCanonical"`
|
||||
}
|
||||
|
||||
type PolicyFile struct {
|
||||
Path string
|
||||
Content []byte
|
||||
}
|
||||
28
pkg/signerverifier/gcp.go
Normal file
28
pkg/signerverifier/gcp.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package signerverifier
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/secure-systems-lab/go-securesystemslib/dsse"
|
||||
gcpsigner "github.com/sigstore/sigstore/pkg/signature/kms/gcp"
|
||||
"google.golang.org/api/option"
|
||||
)
|
||||
|
||||
// using GCP KMS
|
||||
// reference should be in the format projects/[PROJECT_ID]/locations/[LOCATION]/keyRings/[KEY_RING]/cryptoKeys/[KEY]/cryptoKeyVersions/[VERSION]
|
||||
func GetGCPSigner(ctx context.Context, reference string, opts ...option.ClientOption) (dsse.SignerVerifier, error) {
|
||||
reference = fmt.Sprintf("gcpkms://%s", reference)
|
||||
sv, err := gcpsigner.LoadSignerVerifier(ctx, reference, opts...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error loading gcp signer verifier: %w", err)
|
||||
}
|
||||
cs, _, err := sv.CryptoSigner(ctx, func(err error) {})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting gcp crypto signer: %w", err)
|
||||
}
|
||||
signer := &ECDSA256_SignerVerifier{
|
||||
Signer: cs,
|
||||
}
|
||||
return signer, nil
|
||||
}
|
||||
45
pkg/signerverifier/gcp_test.go
Normal file
45
pkg/signerverifier/gcp_test.go
Normal file
@@ -0,0 +1,45 @@
|
||||
//go:build e2e
|
||||
|
||||
package signerverifier
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/attest/internal/util"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const publicKeyPEM = `-----BEGIN PUBLIC KEY-----
|
||||
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuMswW3iu7PR/rWTQjlhVmUsPK7rF
|
||||
k2s4SO3XbQ2GG2alm289SUUpmBAuVxvT8muYQ8HC/QzixzyTACTXsBDjQg==
|
||||
-----END PUBLIC KEY-----`
|
||||
|
||||
// to run locally, we need to impersonate the GCP service account
|
||||
// gcloud auth application-default login --impersonate-service-account attest-kms-test@attest-kms-test.iam.gserviceaccount.com
|
||||
|
||||
func TestGCPKMS_Signer(t *testing.T) {
|
||||
// create a new signer
|
||||
ctx := context.Background()
|
||||
ref := "projects/attest-kms-test/locations/us-west1/keyRings/attest-kms-test/cryptoKeys/test-signing-key/cryptoKeyVersions/1"
|
||||
signer, err := GetGCPSigner(ctx, ref)
|
||||
require.NoError(t, err)
|
||||
msg := []byte("hello world")
|
||||
hash := util.SHA256(msg)
|
||||
|
||||
// sign message digest
|
||||
sig, err := signer.Sign(ctx, hash)
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, sig)
|
||||
// get Key ID from signer
|
||||
keyId, err := signer.KeyID()
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, keyId)
|
||||
publicKey, err := Parse([]byte(publicKeyPEM))
|
||||
require.NoError(t, err)
|
||||
// verify payload ecdsa signature
|
||||
ok := ecdsa.VerifyASN1(publicKey, hash, sig)
|
||||
assert.True(t, ok)
|
||||
}
|
||||
@@ -21,7 +21,7 @@ func ExampleNewTufClient_registry() {
|
||||
metadataURI := "registry-1.docker.io/docker/tuf-metadata:latest"
|
||||
targetsURI := "registry-1.docker.io/docker/tuf-targets"
|
||||
|
||||
registryClient, err := tuf.NewTufClient(embed.StagingRoot, tufOutputPath, metadataURI, targetsURI, tuf.NewMockVersionChecker())
|
||||
registryClient, err := tuf.NewTufClient(embed.RootStaging.Data, tufOutputPath, metadataURI, targetsURI, tuf.NewMockVersionChecker())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/attest/pkg/oci"
|
||||
"github.com/google/go-containerregistry/pkg/authn"
|
||||
"github.com/google/go-containerregistry/pkg/crane"
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
@@ -124,7 +125,7 @@ func (d *RegistryFetcher) getManifest(ref string) ([]byte, error) {
|
||||
crane.WithUserAgent(d.httpUserAgent),
|
||||
crane.WithTransport(transportWithTimeout(d.timeout)),
|
||||
crane.WithAuth(authn.Anonymous),
|
||||
crane.WithAuthFromKeychain(authn.DefaultKeychain))
|
||||
crane.WithAuthFromKeychain(oci.MultiKeychainAll()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -144,7 +145,7 @@ func (d *RegistryFetcher) pullFileLayer(ref string, maxLength int64) ([]byte, er
|
||||
crane.WithUserAgent(d.httpUserAgent),
|
||||
crane.WithTransport(transportWithTimeout(d.timeout)),
|
||||
crane.WithAuth(authn.Anonymous),
|
||||
crane.WithAuthFromKeychain(authn.DefaultKeychain))
|
||||
crane.WithAuthFromKeychain(oci.MultiKeychainAll()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
|
||||
"github.com/docker/attest/internal/embed"
|
||||
"github.com/docker/attest/internal/util"
|
||||
"github.com/google/go-containerregistry/pkg/authn"
|
||||
"github.com/docker/attest/pkg/oci"
|
||||
"github.com/google/go-containerregistry/pkg/crane"
|
||||
"github.com/google/go-containerregistry/pkg/name"
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
@@ -52,7 +52,7 @@ func TestRegistryFetcher(t *testing.T) {
|
||||
delegatedDir := CreateTempDir(t, dir, delegatedRole)
|
||||
delegatedTargetFile := fmt.Sprintf("%s/%s", delegatedRole, targetFile)
|
||||
|
||||
cfg, err := config.New(metadataRepo, embed.DevRoot)
|
||||
cfg, err := config.New(metadataRepo, embed.RootDev.Data)
|
||||
assert.NoError(t, err)
|
||||
|
||||
cfg.Fetcher = NewRegistryFetcher(metadataRepo, metadataImgTag, targetsRepo)
|
||||
@@ -407,13 +407,13 @@ func LoadRegistryTestData(t *testing.T, registry *url.URL, path string) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = remote.Write(ref, img, remote.WithAuthFromKeychain(authn.DefaultKeychain))
|
||||
err = remote.Write(ref, img, oci.MultiKeychainOption())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
} else if len(mf.Manifests) > 1 {
|
||||
// delegated target
|
||||
err = remote.WriteIndex(ref, tIdx, remote.WithAuthFromKeychain(authn.DefaultKeychain))
|
||||
err = remote.WriteIndex(ref, tIdx, oci.MultiKeychainOption())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -441,5 +441,5 @@ func LoadMetadata(path, host, repo, tag string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return remote.Write(ref, img, remote.WithAuthFromKeychain(authn.DefaultKeychain))
|
||||
return remote.Write(ref, img, oci.MultiKeychainOption())
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/attest/internal/embed"
|
||||
"github.com/docker/attest/internal/util"
|
||||
"github.com/theupdateframework/go-tuf/v2/metadata"
|
||||
"github.com/theupdateframework/go-tuf/v2/metadata/config"
|
||||
@@ -227,3 +228,8 @@ func ensureTrailingSlash(url string) string {
|
||||
}
|
||||
return url + "/"
|
||||
}
|
||||
|
||||
// GetEmbeddedTufRoot returns the embedded TUF root based on the given root name
|
||||
func GetEmbeddedTufRoot(root string) (*embed.EmbeddedRoot, error) {
|
||||
return embed.GetRootFromName(root)
|
||||
}
|
||||
|
||||
@@ -65,17 +65,17 @@ func TestRootInit(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
_, err := NewTufClient(embed.DevRoot, tufPath, tc.metadataSource, tc.targetsSource, alwaysGoodVersionChecker)
|
||||
_, err := NewTufClient(embed.RootDev.Data, tufPath, tc.metadataSource, tc.targetsSource, alwaysGoodVersionChecker)
|
||||
assert.NoErrorf(t, err, "Failed to create TUF client: %v", err)
|
||||
|
||||
// recreation should work with same root
|
||||
_, err = NewTufClient(embed.DevRoot, tufPath, tc.metadataSource, tc.targetsSource, alwaysGoodVersionChecker)
|
||||
_, err = NewTufClient(embed.RootDev.Data, tufPath, tc.metadataSource, tc.targetsSource, alwaysGoodVersionChecker)
|
||||
assert.NoErrorf(t, err, "Failed to recreate TUF client: %v", err)
|
||||
|
||||
_, err = NewTufClient([]byte("broken"), tufPath, tc.metadataSource, tc.targetsSource, alwaysGoodVersionChecker)
|
||||
assert.Errorf(t, err, "Expected error recreating TUF client with broken root: %v", err)
|
||||
|
||||
_, err = NewTufClient(embed.DevRoot, tufPath, tc.metadataSource, tc.targetsSource, alwaysBadVersionChecker)
|
||||
_, err = NewTufClient(embed.RootDev.Data, tufPath, tc.metadataSource, tc.targetsSource, alwaysBadVersionChecker)
|
||||
assert.Errorf(t, err, "Expected error creating TUF client with bad attest version: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -111,7 +111,7 @@ func TestDownloadTarget(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tufClient, err := NewTufClient(embed.DevRoot, tufPath, tc.metadataSource, tc.targetsSource, alwaysGoodVersionChecker)
|
||||
tufClient, err := NewTufClient(embed.RootDev.Data, tufPath, tc.metadataSource, tc.targetsSource, alwaysGoodVersionChecker)
|
||||
assert.NoErrorf(t, err, "Failed to create TUF client: %v", err)
|
||||
|
||||
// get trusted tuf metadata
|
||||
@@ -133,3 +133,24 @@ func TestDownloadTarget(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetEmbeddedTufRootBytes(t *testing.T) {
|
||||
dev, err := GetEmbeddedTufRoot("dev")
|
||||
assert.NoError(t, err)
|
||||
|
||||
staging, err := GetEmbeddedTufRoot("staging")
|
||||
assert.NoError(t, err)
|
||||
assert.NotEqual(t, dev.Data, staging.Data)
|
||||
|
||||
prod, err := GetEmbeddedTufRoot("prod")
|
||||
assert.NoError(t, err)
|
||||
assert.NotEqual(t, dev.Data, prod.Data)
|
||||
assert.NotEqual(t, staging.Data, prod.Data)
|
||||
|
||||
def, err := GetEmbeddedTufRoot("")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, def.Data, prod.Data)
|
||||
|
||||
_, err = GetEmbeddedTufRoot("invalid")
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
16
test/testdata/local-policy-fail/doi/policy.rego
vendored
16
test/testdata/local-policy-fail/doi/policy.rego
vendored
@@ -12,14 +12,24 @@ keys := [{
|
||||
"signing-format": "dssev1",
|
||||
}]
|
||||
|
||||
provs(pred) := p if {
|
||||
res := attest.fetch(pred)
|
||||
not res.error
|
||||
p := res.value
|
||||
}
|
||||
|
||||
atts := union({
|
||||
attestations.attestation("https://slsa.dev/provenance/v0.2"),
|
||||
attestations.attestation("https://spdx.dev/Document"),
|
||||
provs("https://slsa.dev/provenance/v0.2"),
|
||||
provs("https://spdx.dev/Document"),
|
||||
})
|
||||
|
||||
opts := {"keys": keys}
|
||||
|
||||
statements contains s if {
|
||||
some att in atts
|
||||
s := attestations.verify_envelope(att, keys)
|
||||
res := attest.verify(att, opts)
|
||||
not res.error
|
||||
s := res.value
|
||||
}
|
||||
|
||||
subjects contains subject if {
|
||||
|
||||
49
test/testdata/local-policy-no-tl/doi/policy.rego
vendored
Normal file
49
test/testdata/local-policy-no-tl/doi/policy.rego
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
package attest
|
||||
|
||||
import rego.v1
|
||||
|
||||
keys := [{
|
||||
"id": "6b241993defaba26558c64f94a94303ce860e7ad9163d801495c91cf57197c75",
|
||||
"key": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZmicqYSY38DprGr42jU0V3ND0ROj\nzSRH1+yjsxhh0bi52Hh/DuOhrSq2KJ5a09lW3ybnDjljowbkof0Y1i9Oow==\n-----END PUBLIC KEY-----",
|
||||
"from": "2023-12-15T14:00:00Z",
|
||||
"to": null,
|
||||
# this key is still active
|
||||
"status": "active",
|
||||
"signing-format": "dssev1",
|
||||
}]
|
||||
|
||||
provs(pred) := p if {
|
||||
res := attest.fetch(pred)
|
||||
not res.error
|
||||
p := res.value
|
||||
}
|
||||
|
||||
atts := union({
|
||||
provs("https://slsa.dev/provenance/v0.2"),
|
||||
provs("https://spdx.dev/Document"),
|
||||
})
|
||||
|
||||
opts := {"keys": keys, "skip_tl": true}
|
||||
|
||||
statements contains s if {
|
||||
some att in atts
|
||||
res := attest.verify(att, opts)
|
||||
not res.error
|
||||
s := res.value
|
||||
}
|
||||
|
||||
subjects contains subject if {
|
||||
some statement in statements
|
||||
some subject in statement.subject
|
||||
}
|
||||
|
||||
result := {
|
||||
"success": true,
|
||||
"violations": set(),
|
||||
"summary": {
|
||||
"subjects": subjects,
|
||||
"slsa_levels": ["SLSA_BUILD_LEVEL_3"],
|
||||
"verifier": "docker-official-images",
|
||||
"policy_uri": "https://docker.com/official/policy/v0.1",
|
||||
},
|
||||
}
|
||||
11
test/testdata/local-policy-no-tl/mapping.yaml
vendored
Normal file
11
test/testdata/local-policy-no-tl/mapping.yaml
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
# map repos to policies
|
||||
version: v1
|
||||
kind: policy-mapping
|
||||
policies:
|
||||
- origin:
|
||||
domain: docker.io
|
||||
prefix: library/
|
||||
id: test-images
|
||||
description: Local test images
|
||||
files:
|
||||
- path: doi/policy.rego
|
||||
16
test/testdata/local-policy-pass/doi/policy.rego
vendored
16
test/testdata/local-policy-pass/doi/policy.rego
vendored
@@ -12,14 +12,24 @@ keys := [{
|
||||
"signing-format": "dssev1",
|
||||
}]
|
||||
|
||||
provs(pred) := p if {
|
||||
res := attest.fetch(pred)
|
||||
not res.error
|
||||
p := res.value
|
||||
}
|
||||
|
||||
atts := union({
|
||||
attestations.attestation("https://slsa.dev/provenance/v0.2"),
|
||||
attestations.attestation("https://spdx.dev/Document"),
|
||||
provs("https://slsa.dev/provenance/v0.2"),
|
||||
provs("https://spdx.dev/Document"),
|
||||
})
|
||||
|
||||
opts := {"keys": keys}
|
||||
|
||||
statements contains s if {
|
||||
some att in atts
|
||||
s := attestations.verify_envelope(att, keys)
|
||||
res := attest.verify(att, opts)
|
||||
not res.error
|
||||
s := res.value
|
||||
}
|
||||
|
||||
subjects contains subject if {
|
||||
|
||||
5
test/testdata/local-policy-pass/mapping.yaml
vendored
5
test/testdata/local-policy-pass/mapping.yaml
vendored
@@ -9,3 +9,8 @@ policies:
|
||||
description: Local test images
|
||||
files:
|
||||
- path: doi/policy.rego
|
||||
mirrors:
|
||||
- policy-id: test-images
|
||||
mirror:
|
||||
domains: ["*"]
|
||||
prefix: ""
|
||||
|
||||
49
test/testdata/local-policy/doi/policy.rego
vendored
Normal file
49
test/testdata/local-policy/doi/policy.rego
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
package attest
|
||||
|
||||
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,
|
||||
"status": "active",
|
||||
"signing-format": "dssev1",
|
||||
}]
|
||||
|
||||
provs(pred) := p if {
|
||||
res := attest.fetch(pred)
|
||||
not res.error
|
||||
p := res.value
|
||||
}
|
||||
|
||||
atts := union({
|
||||
provs("https://slsa.dev/provenance/v0.2"),
|
||||
provs("https://spdx.dev/Document"),
|
||||
})
|
||||
|
||||
opts := {"keys": keys}
|
||||
|
||||
statements contains s if {
|
||||
some att in atts
|
||||
res := attest.verify(att, opts)
|
||||
not res.error
|
||||
s := res.value
|
||||
}
|
||||
|
||||
subjects contains subject if {
|
||||
some statement in statements
|
||||
some subject in statement.subject
|
||||
}
|
||||
|
||||
result := {
|
||||
"success": count(atts) > 0,
|
||||
"violations": set(),
|
||||
"attestations": statements,
|
||||
"summary": {
|
||||
"subjects": subjects,
|
||||
"slsa_level": "SLSA_BUILD_LEVEL_3",
|
||||
"verifier": "docker-official-images",
|
||||
"policy_uri": "https://docker.com/official/policy/v0.1",
|
||||
},
|
||||
}
|
||||
29
test/testdata/local-policy/mapping.yaml
vendored
Normal file
29
test/testdata/local-policy/mapping.yaml
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
# map repos to policies
|
||||
version: v1
|
||||
kind: policy-mapping
|
||||
policies:
|
||||
- origin:
|
||||
domain: docker.io
|
||||
prefix: library/
|
||||
id: test-images
|
||||
description: Local test images
|
||||
files:
|
||||
- path: "doi/policy.rego"
|
||||
|
||||
mirrors:
|
||||
- policy-id: test-images
|
||||
mirror:
|
||||
domains: ["*"]
|
||||
prefix: "repo"
|
||||
- policy-id: test-images
|
||||
mirror:
|
||||
domains: ["*"]
|
||||
prefix: "library/"
|
||||
- policy-id: test-images
|
||||
mirror:
|
||||
domains: ["*"]
|
||||
prefix: "test-image"
|
||||
- policy-id: test-images
|
||||
mirror:
|
||||
domains: ["*"]
|
||||
prefix: "image-signer-verifier-test"
|
||||
15
test/testdata/unsigned-test-image/index.json
vendored
15
test/testdata/unsigned-test-image/index.json
vendored
@@ -1 +1,14 @@
|
||||
{"schemaVersion":2,"manifests":[{"mediaType":"application/vnd.oci.image.index.v1+json","digest":"sha256:db8f2a6e112ea6396f57d073269ecfac61e8dcdad3a4a643dcb577522492f898","size":1607,"annotations":{"org.opencontainers.image.created":"2024-04-29T10:23:46Z","org.opencontainers.image.ref.name":"docker.io/library/test-image:test"}}]}
|
||||
{
|
||||
"schemaVersion": 2,
|
||||
"manifests": [
|
||||
{
|
||||
"mediaType": "application/vnd.oci.image.index.v1+json",
|
||||
"digest": "sha256:db8f2a6e112ea6396f57d073269ecfac61e8dcdad3a4a643dcb577522492f898",
|
||||
"size": 1607,
|
||||
"annotations": {
|
||||
"org.opencontainers.image.created": "2024-04-29T10:23:46Z",
|
||||
"org.opencontainers.image.ref.name": "docker.io/library/test-image:test"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user