Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6f94d59a96 | ||
|
|
95319494b5 | ||
|
|
64046df6f8 | ||
|
|
57b6df0ab5 | ||
|
|
857be568b5 | ||
|
|
9d39c5ae3d | ||
|
|
aed959f858 | ||
|
|
802725caf0 | ||
|
|
9c3f267870 | ||
|
|
6cc9191e1e | ||
|
|
7ce2817111 | ||
|
|
a60aab9338 | ||
|
|
2ef3a158ae | ||
|
|
4f163f4283 | ||
|
|
74e8d8beb3 | ||
|
|
881e9d9582 |
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@@ -50,7 +50,7 @@ jobs:
|
|||||||
token: ${{ secrets.TC_CLOUD_TOKEN }}
|
token: ${{ secrets.TC_CLOUD_TOKEN }}
|
||||||
- name: go test including e2e
|
- name: go test including e2e
|
||||||
if: matrix.os == 'ubuntu-latest' && github.actor != 'dependabot[bot]'
|
if: matrix.os == 'ubuntu-latest' && github.actor != 'dependabot[bot]'
|
||||||
run: go test -tags=e2e -v ./... -coverprofile=coverage.out -covermode=atomic
|
run: go test -tags=e2e -v ./... -coverpkg=./... -coverprofile=coverage.out -covermode=atomic
|
||||||
- name: go test excluding e2e
|
- name: go test excluding e2e
|
||||||
if: matrix.os == 'macos-latest' || github.actor == 'dependabot[bot]'
|
if: matrix.os == 'macos-latest' || github.actor == 'dependabot[bot]'
|
||||||
run: go test -v ./...
|
run: go test -v ./...
|
||||||
|
|||||||
@@ -128,7 +128,10 @@ The input to the policy is an object with the following fields:
|
|||||||
|
|
||||||
- `digest` (string): the digest of the image being verified
|
- `digest` (string): the digest of the image being verified
|
||||||
- `purl` (string): the package URL of the image being verified
|
- `purl` (string): the package URL of the image being verified
|
||||||
- `is_canonical` (bool): whether the image being verified was referenced by a 'canonical' name, i.e. one that contains a digest
|
- `platform` (string): the platform of the image being verified
|
||||||
|
- `normalized_name` (string): defaults are filled out. e.g. if the image is `alpine`, this would be `library/alpine`
|
||||||
|
- `familiar_name` (string): short version of above (e.g. `alpine`)
|
||||||
|
- `tag`: (string): tag of the image being verified (if present)
|
||||||
|
|
||||||
### Builtin Functions
|
### Builtin Functions
|
||||||
|
|
||||||
|
|||||||
59
go.mod
59
go.mod
@@ -3,8 +3,8 @@ module github.com/docker/attest
|
|||||||
go 1.22.5
|
go 1.22.5
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Masterminds/semver/v3 v3.2.1
|
github.com/Masterminds/semver/v3 v3.3.0
|
||||||
github.com/aws/aws-sdk-go-v2/config v1.27.27
|
github.com/aws/aws-sdk-go-v2/config v1.27.31
|
||||||
github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20231024185945-8841054dbdb8
|
github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20231024185945-8841054dbdb8
|
||||||
github.com/containerd/platforms v0.2.1
|
github.com/containerd/platforms v0.2.1
|
||||||
github.com/distribution/reference v0.6.0
|
github.com/distribution/reference v0.6.0
|
||||||
@@ -22,9 +22,9 @@ require (
|
|||||||
github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.8
|
github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.8
|
||||||
github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.8.8
|
github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.8.8
|
||||||
github.com/stretchr/testify v1.9.0
|
github.com/stretchr/testify v1.9.0
|
||||||
github.com/testcontainers/testcontainers-go/modules/registry v0.32.0
|
github.com/testcontainers/testcontainers-go/modules/registry v0.33.0
|
||||||
github.com/theupdateframework/go-tuf/v2 v2.0.0
|
github.com/theupdateframework/go-tuf/v2 v2.0.0
|
||||||
google.golang.org/api v0.191.0
|
google.golang.org/api v0.194.0
|
||||||
sigs.k8s.io/yaml v1.4.0
|
sigs.k8s.io/yaml v1.4.0
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -32,9 +32,9 @@ require (
|
|||||||
replace github.com/google/go-containerregistry => github.com/docker/go-containerregistry v0.0.0-20240808132857-c8bfc44af7c8
|
replace github.com/google/go-containerregistry => github.com/docker/go-containerregistry v0.0.0-20240808132857-c8bfc44af7c8
|
||||||
|
|
||||||
require (
|
require (
|
||||||
cloud.google.com/go v0.115.0 // indirect
|
cloud.google.com/go v0.115.1 // indirect
|
||||||
cloud.google.com/go/auth v0.7.3 // indirect
|
cloud.google.com/go/auth v0.9.1 // indirect
|
||||||
cloud.google.com/go/auth/oauth2adapt v0.2.3 // indirect
|
cloud.google.com/go/auth/oauth2adapt v0.2.4 // indirect
|
||||||
cloud.google.com/go/compute/metadata v0.5.0 // indirect
|
cloud.google.com/go/compute/metadata v0.5.0 // indirect
|
||||||
cloud.google.com/go/iam v1.1.12 // indirect
|
cloud.google.com/go/iam v1.1.12 // indirect
|
||||||
cloud.google.com/go/kms v1.18.4 // indirect
|
cloud.google.com/go/kms v1.18.4 // indirect
|
||||||
@@ -42,26 +42,25 @@ require (
|
|||||||
dario.cat/mergo v1.0.0 // indirect
|
dario.cat/mergo v1.0.0 // indirect
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
|
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
|
||||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||||
github.com/Microsoft/hcsshim v0.12.3 // indirect
|
|
||||||
github.com/OneOfOne/xxhash v1.2.8 // indirect
|
github.com/OneOfOne/xxhash v1.2.8 // indirect
|
||||||
github.com/ProtonMail/go-crypto v1.0.0 // indirect
|
github.com/ProtonMail/go-crypto v1.0.0 // indirect
|
||||||
github.com/agnivade/levenshtein v1.1.1 // indirect
|
github.com/agnivade/levenshtein v1.1.1 // indirect
|
||||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
|
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2 v1.30.3 // indirect
|
github.com/aws/aws-sdk-go-v2 v1.30.4 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.27 // indirect
|
github.com/aws/aws-sdk-go-v2/credentials v1.17.30 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 // indirect
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.12 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15 // indirect
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.16 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15 // indirect
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.16 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect
|
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/ecr v1.29.1 // 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/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/accept-encoding v1.11.4 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 // indirect
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.18 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/kms v1.35.3 // indirect
|
github.com/aws/aws-sdk-go-v2/service/kms v1.35.3 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/sso v1.22.4 // indirect
|
github.com/aws/aws-sdk-go-v2/service/sso v1.22.5 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 // indirect
|
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.5 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 // indirect
|
github.com/aws/aws-sdk-go-v2/service/sts v1.30.5 // indirect
|
||||||
github.com/aws/smithy-go v1.20.3 // indirect
|
github.com/aws/smithy-go v1.20.4 // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/blang/semver v3.5.1+incompatible // indirect
|
github.com/blang/semver v3.5.1+incompatible // indirect
|
||||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||||
@@ -77,7 +76,7 @@ require (
|
|||||||
github.com/digitorus/timestamp v0.0.0-20231217203849-220c5c2851b7 // indirect
|
github.com/digitorus/timestamp v0.0.0-20231217203849-220c5c2851b7 // indirect
|
||||||
github.com/docker/cli v27.1.1+incompatible // indirect
|
github.com/docker/cli v27.1.1+incompatible // indirect
|
||||||
github.com/docker/distribution v2.8.3+incompatible // indirect
|
github.com/docker/distribution v2.8.3+incompatible // indirect
|
||||||
github.com/docker/docker v27.1.0+incompatible // indirect
|
github.com/docker/docker v27.1.1+incompatible // indirect
|
||||||
github.com/docker/docker-credential-helpers v0.8.1 // indirect
|
github.com/docker/docker-credential-helpers v0.8.1 // indirect
|
||||||
github.com/docker/go-connections v0.5.0 // indirect
|
github.com/docker/go-connections v0.5.0 // indirect
|
||||||
github.com/docker/go-units v0.5.0 // indirect
|
github.com/docker/go-units v0.5.0 // indirect
|
||||||
@@ -161,7 +160,7 @@ require (
|
|||||||
github.com/subosito/gotenv v1.6.0 // indirect
|
github.com/subosito/gotenv v1.6.0 // indirect
|
||||||
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect
|
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect
|
||||||
github.com/tchap/go-patricia/v2 v2.3.1 // indirect
|
github.com/tchap/go-patricia/v2 v2.3.1 // indirect
|
||||||
github.com/testcontainers/testcontainers-go v0.32.0 // indirect
|
github.com/testcontainers/testcontainers-go v0.33.0 // indirect
|
||||||
github.com/theupdateframework/go-tuf v0.7.0 // indirect
|
github.com/theupdateframework/go-tuf v0.7.0 // indirect
|
||||||
github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect
|
github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect
|
||||||
github.com/tklauser/go-sysconf v0.3.14 // indirect
|
github.com/tklauser/go-sysconf v0.3.14 // indirect
|
||||||
@@ -182,19 +181,19 @@ require (
|
|||||||
go.opentelemetry.io/otel/trace v1.28.0 // indirect
|
go.opentelemetry.io/otel/trace v1.28.0 // indirect
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
go.uber.org/zap v1.27.0 // indirect
|
go.uber.org/zap v1.27.0 // indirect
|
||||||
golang.org/x/crypto v0.25.0 // indirect
|
golang.org/x/crypto v0.26.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect
|
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect
|
||||||
golang.org/x/mod v0.19.0 // indirect
|
golang.org/x/mod v0.19.0 // indirect
|
||||||
golang.org/x/net v0.27.0 // indirect
|
golang.org/x/net v0.28.0 // indirect
|
||||||
golang.org/x/oauth2 v0.22.0 // indirect
|
golang.org/x/oauth2 v0.22.0 // indirect
|
||||||
golang.org/x/sync v0.8.0 // indirect
|
golang.org/x/sync v0.8.0 // indirect
|
||||||
golang.org/x/sys v0.22.0 // indirect
|
golang.org/x/sys v0.24.0 // indirect
|
||||||
golang.org/x/term v0.22.0 // indirect
|
golang.org/x/term v0.23.0 // indirect
|
||||||
golang.org/x/text v0.16.0 // indirect
|
golang.org/x/text v0.17.0 // indirect
|
||||||
golang.org/x/time v0.6.0 // indirect
|
golang.org/x/time v0.6.0 // indirect
|
||||||
google.golang.org/genproto v0.0.0-20240730163845-b1a4ccb954bf // indirect
|
google.golang.org/genproto v0.0.0-20240814211410-ddb44dafa142 // indirect
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20240725223205-93522f1f2a9f // indirect
|
google.golang.org/genproto/googleapis/api v0.0.0-20240730163845-b1a4ccb954bf // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240730163845-b1a4ccb954bf // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect
|
||||||
google.golang.org/grpc v1.65.0 // indirect
|
google.golang.org/grpc v1.65.0 // indirect
|
||||||
google.golang.org/protobuf v1.34.2 // indirect
|
google.golang.org/protobuf v1.34.2 // indirect
|
||||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
|
|||||||
118
go.sum
118
go.sum
@@ -1,10 +1,10 @@
|
|||||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
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.1 h1:Jo0SM9cQnSkYfp44+v+NQXHpcHqlnRJk2qxh6yvxxxQ=
|
||||||
cloud.google.com/go v0.115.0/go.mod h1:8jIM5vVgoAEoiVxQ/O4BFTfHqulPZgs/ufEzMcFMdWU=
|
cloud.google.com/go v0.115.1/go.mod h1:DuujITeaufu3gL68/lOFIirVNJwQeyf5UXyi+Wbgknc=
|
||||||
cloud.google.com/go/auth v0.7.3 h1:98Vr+5jMaCZ5NZk6e/uBgf60phTk/XN84r8QEWB9yjY=
|
cloud.google.com/go/auth v0.9.1 h1:+pMtLEV2k0AXKvs/tGZojuj6QaioxfUjOpMsG5Gtx+w=
|
||||||
cloud.google.com/go/auth v0.7.3/go.mod h1:HJtWUx1P5eqjy/f6Iq5KeytNpbAcGolPhOgyop2LlzA=
|
cloud.google.com/go/auth v0.9.1/go.mod h1:Sw8ocT5mhhXxFklyhT12Eiy0ed6tTrPMCJjSI8KhYLk=
|
||||||
cloud.google.com/go/auth/oauth2adapt v0.2.3 h1:MlxF+Pd3OmSudg/b1yZ5lJwoXCEaeedAguodky1PcKI=
|
cloud.google.com/go/auth/oauth2adapt v0.2.4 h1:0GWE/FUsXhf6C+jAkWgYm7X9tK8cuEIfy19DBn6B6bY=
|
||||||
cloud.google.com/go/auth/oauth2adapt v0.2.3/go.mod h1:tMQXOfZzFuNuUxOypHlQEXgdfX5cuhwU+ffUuXRJE8I=
|
cloud.google.com/go/auth/oauth2adapt v0.2.4/go.mod h1:jC/jOpwFP6JBxhB3P5Rr0a9HLMC/Pe3eaL4NmdvqPtc=
|
||||||
cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY=
|
cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY=
|
||||||
cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY=
|
cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY=
|
||||||
cloud.google.com/go/iam v1.1.12 h1:JixGLimRrNGcxvJEQ8+clfLxPlbeZA6MuRJ+qJNQ5Xw=
|
cloud.google.com/go/iam v1.1.12 h1:JixGLimRrNGcxvJEQ8+clfLxPlbeZA6MuRJ+qJNQ5Xw=
|
||||||
@@ -60,12 +60,10 @@ github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBp
|
|||||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU=
|
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/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/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.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0=
|
||||||
github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
|
github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
||||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||||
github.com/Microsoft/hcsshim v0.12.3 h1:LS9NXqXhMoqNCplK1ApmVSfB4UnVLRDWRapB6EIlxE0=
|
|
||||||
github.com/Microsoft/hcsshim v0.12.3/go.mod h1:Iyl1WVpZzr+UkzjekHZbV8o5Z9ZkxNGx6CtY2Qg/JVQ=
|
|
||||||
github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8=
|
github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8=
|
||||||
github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q=
|
github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q=
|
||||||
github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78=
|
github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78=
|
||||||
@@ -104,38 +102,38 @@ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3d
|
|||||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
|
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
|
||||||
github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU=
|
github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU=
|
||||||
github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
|
github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
|
||||||
github.com/aws/aws-sdk-go-v2 v1.30.3 h1:jUeBtG0Ih+ZIFH0F4UkmL9w3cSpaMv9tYYDbzILP8dY=
|
github.com/aws/aws-sdk-go-v2 v1.30.4 h1:frhcagrVNrzmT95RJImMHgabt99vkXGslubDaDagTk8=
|
||||||
github.com/aws/aws-sdk-go-v2 v1.30.3/go.mod h1:nIQjQVp5sfpQcTc9mPSr1B0PaWK5ByX9MOoDadSN4lc=
|
github.com/aws/aws-sdk-go-v2 v1.30.4/go.mod h1:CT+ZPWXbYrci8chcARI3OmI/qgd+f6WtuLOoaIA8PR0=
|
||||||
github.com/aws/aws-sdk-go-v2/config v1.27.27 h1:HdqgGt1OAP0HkEDDShEl0oSYa9ZZBSOmKpdpsDMdO90=
|
github.com/aws/aws-sdk-go-v2/config v1.27.31 h1:kxBoRsjhT3pq0cKthgj6RU6bXTm/2SgdoUMyrVw0rAI=
|
||||||
github.com/aws/aws-sdk-go-v2/config v1.27.27/go.mod h1:MVYamCg76dFNINkZFu4n4RjDixhVr51HLj4ErWzrVwg=
|
github.com/aws/aws-sdk-go-v2/config v1.27.31/go.mod h1:z04nZdSWFPaDwK3DdJOG2r+scLQzMYuJeW0CujEm9FM=
|
||||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.27 h1:2raNba6gr2IfA0eqqiP2XiQ0UVOpGPgDSi0I9iAP+UI=
|
github.com/aws/aws-sdk-go-v2/credentials v1.17.30 h1:aau/oYFtibVovr2rDt8FHlU17BTicFEMAi29V1U+L5Q=
|
||||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.27/go.mod h1:gniiwbGahQByxan6YjQUMcW4Aov6bLC3m+evgcoN4r4=
|
github.com/aws/aws-sdk-go-v2/credentials v1.17.30/go.mod h1:BPJ/yXV92ZVq6G8uYvbU0gSl8q94UB63nMT5ctNO38g=
|
||||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 h1:KreluoV8FZDEtI6Co2xuNk/UqI9iwMrOx/87PBNIKqw=
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.12 h1:yjwoSyDZF8Jth+mUk5lSPJCkMC0lMy6FaCD51jm6ayE=
|
||||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11/go.mod h1:SeSUYBLsMYFoRvHE0Tjvn7kbxaUhl75CJi1sbfhMxkU=
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.12/go.mod h1:fuR57fAgMk7ot3WcNQfb6rSEn+SUffl7ri+aa8uKysI=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15 h1:SoNJ4RlFEQEbtDcCEt+QG56MY4fm4W8rYirAmq+/DdU=
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.16 h1:TNyt/+X43KJ9IJJMjKfa3bNTiZbUP7DeCxfbTROESwY=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15/go.mod h1:U9ke74k1n2bf+RIgoX1SXFed1HLs51OgUSs+Ph0KJP8=
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.16/go.mod h1:2DwJF39FlNAUiX5pAc0UNeiz16lK2t7IaFcm0LFHEgc=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15 h1:C6WHdGnTDIYETAm5iErQUiVNsclNx9qbJVPIt03B6bI=
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.16 h1:jYfy8UPmd+6kJW5YhY0L1/KftReOGxI/4NtVSTh9O/I=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15/go.mod h1:ZQLZqhcu+JhSrA9/NXRm8SkDvsycE+JkV3WGY41e+IM=
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.16/go.mod h1:7ZfEPZxkW42Afq4uQB8H2E2e6ebh6mXTueEpYzjCzcs=
|
||||||
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.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ=
|
||||||
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/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc=
|
||||||
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 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/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 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/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.4 h1:KypMCbLPPHEmf9DgMGw51jMj77VfGPAN2Kv4cfhlfgI=
|
||||||
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/accept-encoding v1.11.4/go.mod h1:Vz1JQXliGcQktFTN/LN6uGppAIRoLBR2bMvIMP0gOjc=
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 h1:HGErhhrxZlQ044RiM+WdoZxp0p+EGM62y3L6pwA4olE=
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.18 h1:tJ5RnkHCiSH0jyd6gROjlJtNwov0eGYNz8s8nFcR0jQ=
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17/go.mod h1:RkZEx4l0EHYDJpWppMJ3nD9wZJAa8/0lq9aVC+r2UII=
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.18/go.mod h1:++NHzT+nAF7ZPrHPsA+ENvsXkOO8wEu+C6RXltAG4/c=
|
||||||
github.com/aws/aws-sdk-go-v2/service/kms v1.35.3 h1:UPTdlTOwWUX49fVi7cymEN6hDqCwe3LNv1vi7TXUutk=
|
github.com/aws/aws-sdk-go-v2/service/kms v1.35.3 h1:UPTdlTOwWUX49fVi7cymEN6hDqCwe3LNv1vi7TXUutk=
|
||||||
github.com/aws/aws-sdk-go-v2/service/kms v1.35.3/go.mod h1:gjDP16zn+WWalyaUqwCCioQ8gU8lzttCCc9jYsiQI/8=
|
github.com/aws/aws-sdk-go-v2/service/kms v1.35.3/go.mod h1:gjDP16zn+WWalyaUqwCCioQ8gU8lzttCCc9jYsiQI/8=
|
||||||
github.com/aws/aws-sdk-go-v2/service/sso v1.22.4 h1:BXx0ZIxvrJdSgSvKTZ+yRBeSqqgPM89VPlulEcl37tM=
|
github.com/aws/aws-sdk-go-v2/service/sso v1.22.5 h1:zCsFCKvbj25i7p1u94imVoO447I/sFv8qq+lGJhRN0c=
|
||||||
github.com/aws/aws-sdk-go-v2/service/sso v1.22.4/go.mod h1:ooyCOXjvJEsUw7x+ZDHeISPMhtwI3ZCB7ggFMcFfWLU=
|
github.com/aws/aws-sdk-go-v2/service/sso v1.22.5/go.mod h1:ZeDX1SnKsVlejeuz41GiajjZpRSWR7/42q/EyA/QEiM=
|
||||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 h1:yiwVzJW2ZxZTurVbYWA7QOrAaCYQR72t0wrSBfoesUE=
|
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.5 h1:SKvPgvdvmiTWoi0GAJ7AsJfOz3ngVkD/ERbs5pUnHNI=
|
||||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4/go.mod h1:0oxfLkpz3rQ/CHlx5hB7H69YUpFiI1tql6Q6Ne+1bCw=
|
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.5/go.mod h1:20sz31hv/WsPa3HhU3hfrIet2kxM4Pe0r20eBZ20Tac=
|
||||||
github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 h1:ZsDKRLXGWHk8WdtyYMoGNO7bTudrvuKpDKgMVRlepGE=
|
github.com/aws/aws-sdk-go-v2/service/sts v1.30.5 h1:OMsEmCyz2i89XwRwPouAJvhj81wINh+4UK+k/0Yo/q8=
|
||||||
github.com/aws/aws-sdk-go-v2/service/sts v1.30.3/go.mod h1:zwySh8fpFyXp9yOr/KVzxOl8SRqgf/IDw5aUt9UKFcQ=
|
github.com/aws/aws-sdk-go-v2/service/sts v1.30.5/go.mod h1:vmSqFK+BVIwVpDAGZB3CoCXHzurt4qBE8lf+I/kRTh0=
|
||||||
github.com/aws/smithy-go v1.20.3 h1:ryHwveWzPV5BIof6fyDvor6V3iUL7nTfiTKXHiW05nE=
|
github.com/aws/smithy-go v1.20.4 h1:2HK1zBdPgRbjFOHlfeQZfpC4r72MOb9bZkiFwggKO+4=
|
||||||
github.com/aws/smithy-go v1.20.3/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E=
|
github.com/aws/smithy-go v1.20.4/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
|
||||||
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 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/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=
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
@@ -222,8 +220,8 @@ github.com/docker/cli v27.1.1+incompatible h1:goaZxOqs4QKxznZjjBWKONQci/MywhtRv2
|
|||||||
github.com/docker/cli v27.1.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
github.com/docker/cli v27.1.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||||
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
|
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
|
||||||
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||||
github.com/docker/docker v27.1.0+incompatible h1:rEHVQc4GZ0MIQKifQPHSFGV/dVgaZafgRf8fCPtDYBs=
|
github.com/docker/docker v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY=
|
||||||
github.com/docker/docker v27.1.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||||
github.com/docker/docker-credential-helpers v0.8.1 h1:j/eKUktUltBtMzKqmfLB0PAgqYyMHOp5vfsD1807oKo=
|
github.com/docker/docker-credential-helpers v0.8.1 h1:j/eKUktUltBtMzKqmfLB0PAgqYyMHOp5vfsD1807oKo=
|
||||||
github.com/docker/docker-credential-helpers v0.8.1/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M=
|
github.com/docker/docker-credential-helpers v0.8.1/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M=
|
||||||
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
||||||
@@ -615,10 +613,10 @@ github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d h1:vfofYNRScrDd
|
|||||||
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48=
|
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48=
|
||||||
github.com/tchap/go-patricia/v2 v2.3.1 h1:6rQp39lgIYZ+MHmdEq4xzuk1t7OdC35z/xm0BGhTkes=
|
github.com/tchap/go-patricia/v2 v2.3.1 h1:6rQp39lgIYZ+MHmdEq4xzuk1t7OdC35z/xm0BGhTkes=
|
||||||
github.com/tchap/go-patricia/v2 v2.3.1/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k=
|
github.com/tchap/go-patricia/v2 v2.3.1/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k=
|
||||||
github.com/testcontainers/testcontainers-go v0.32.0 h1:ug1aK08L3gCHdhknlTTwWjPHPS+/alvLJU/DRxTD/ME=
|
github.com/testcontainers/testcontainers-go v0.33.0 h1:zJS9PfXYT5O0ZFXM2xxXfk4J5UMw/kRiISng037Gxdw=
|
||||||
github.com/testcontainers/testcontainers-go v0.32.0/go.mod h1:CRHrzHLQhlXUsa5gXjTOfqIEJcrK5+xMDmBr/WMI88E=
|
github.com/testcontainers/testcontainers-go v0.33.0/go.mod h1:W80YpTa8D5C3Yy16icheD01UTDu+LmXIA2Keo+jWtT8=
|
||||||
github.com/testcontainers/testcontainers-go/modules/registry v0.32.0 h1:b4JSSEhbGXGtQA1WXJ3BlbkVjjdXoFTtBPvLRe+9Y9Y=
|
github.com/testcontainers/testcontainers-go/modules/registry v0.33.0 h1:rpQS5KcFpyRPM3xVKERuXDqUcE5xjwE8MQUgmKVkL0o=
|
||||||
github.com/testcontainers/testcontainers-go/modules/registry v0.32.0/go.mod h1:bX3JF8vQkv3D2frmrDyQd0GCQIQGl5nPG91xUvl7UhA=
|
github.com/testcontainers/testcontainers-go/modules/registry v0.33.0/go.mod h1:qr3nJgBZ2ovQva6vadXchwi786/mBBDzhBPbrmWkYIE=
|
||||||
github.com/thales-e-security/pool v0.0.2 h1:RAPs4q2EbWsTit6tpzuvTFlgFRJ3S8Evf5gtvVDbmPg=
|
github.com/thales-e-security/pool v0.0.2 h1:RAPs4q2EbWsTit6tpzuvTFlgFRJ3S8Evf5gtvVDbmPg=
|
||||||
github.com/thales-e-security/pool v0.0.2/go.mod h1:qtpMm2+thHtqhLzTwgDBj/OuNnMpupY8mv0Phz0gjhU=
|
github.com/thales-e-security/pool v0.0.2/go.mod h1:qtpMm2+thHtqhLzTwgDBj/OuNnMpupY8mv0Phz0gjhU=
|
||||||
github.com/theupdateframework/go-tuf v0.7.0 h1:CqbQFrWo1ae3/I0UCblSbczevCCbS31Qvs5LdxRWqRI=
|
github.com/theupdateframework/go-tuf v0.7.0 h1:CqbQFrWo1ae3/I0UCblSbczevCCbS31Qvs5LdxRWqRI=
|
||||||
@@ -694,8 +692,8 @@ 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.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.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.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||||
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
|
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
||||||
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
|
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
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 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
|
||||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
|
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
|
||||||
@@ -727,8 +725,8 @@ 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.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.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.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||||
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
|
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
|
||||||
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
|
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA=
|
golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA=
|
||||||
golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||||
@@ -771,15 +769,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.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.11.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.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
|
||||||
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.24.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-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.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.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
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.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||||
golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=
|
golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU=
|
||||||
golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
|
golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
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.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
@@ -787,8 +785,8 @@ 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.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.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.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
|
||||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||||
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
|
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
|
||||||
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
golang.org/x/time v0.6.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-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
@@ -809,19 +807,19 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T
|
|||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/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-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
|
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
|
||||||
google.golang.org/api v0.191.0 h1:cJcF09Z+4HAB2t5qTQM1ZtfL/PemsLFkcFG67qq2afk=
|
google.golang.org/api v0.194.0 h1:dztZKG9HgtIpbI35FhfuSNR/zmaMVdxNlntHj1sIS4s=
|
||||||
google.golang.org/api v0.191.0/go.mod h1:tD5dsFGxFza0hnQveGfVk9QQYKcfp+VzgRqyXFxE0+E=
|
google.golang.org/api v0.194.0/go.mod h1:AgvUFdojGANh3vI+P7EVnxj3AISHllxGCJSFmggmnd0=
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
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/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-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-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-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||||
google.golang.org/genproto v0.0.0-20240730163845-b1a4ccb954bf h1:OqdXDEakZCVtDiZTjcxfwbHPCT11ycCEsTKesBVKvyY=
|
google.golang.org/genproto v0.0.0-20240814211410-ddb44dafa142 h1:oLiyxGgE+rt22duwci1+TG7bg2/L1LQsXwfjPlmuJA0=
|
||||||
google.golang.org/genproto v0.0.0-20240730163845-b1a4ccb954bf/go.mod h1:mCr1K1c8kX+1iSBREvU3Juo11CB+QOEWxbRS01wWl5M=
|
google.golang.org/genproto v0.0.0-20240814211410-ddb44dafa142/go.mod h1:G11eXq53iI5Q+kyNOmCvnzBaxEA2Q/Ik5Tj7nqBE8j4=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20240725223205-93522f1f2a9f h1:b1Ln/PG8orm0SsBbHZWke8dDp2lrCD4jSmfglFpTZbk=
|
google.golang.org/genproto/googleapis/api v0.0.0-20240730163845-b1a4ccb954bf h1:GillM0Ef0pkZPIB+5iO6SDK+4T9pf6TpaYR6ICD5rVE=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20240725223205-93522f1f2a9f/go.mod h1:AHT0dDg3SoMOgZGnZk29b5xTbPHMoEC8qthmBLJCpys=
|
google.golang.org/genproto/googleapis/api v0.0.0-20240730163845-b1a4ccb954bf/go.mod h1:OFMYQFHJ4TM3JRlWDZhJbZfra2uqc3WLBZiaaqP4DtU=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240730163845-b1a4ccb954bf h1:liao9UHurZLtiEwBgT9LMOnKYsHze6eA6w1KQCMVN2Q=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 h1:e7S5W7MGGLaSu8j3YjdezkZ+m1/Nm0uRVRMEMGk26Xs=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240730163845-b1a4ccb954bf/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
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.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||||
|
|||||||
@@ -6,24 +6,12 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/docker/attest/internal/embed"
|
|
||||||
"github.com/docker/attest/pkg/attest"
|
"github.com/docker/attest/pkg/attest"
|
||||||
"github.com/docker/attest/pkg/oci"
|
"github.com/docker/attest/pkg/oci"
|
||||||
"github.com/docker/attest/pkg/policy"
|
"github.com/docker/attest/pkg/policy"
|
||||||
"github.com/docker/attest/pkg/tuf"
|
"github.com/docker/attest/pkg/tuf"
|
||||||
)
|
)
|
||||||
|
|
||||||
func createTufClient(outputPath string) (*tuf.Client, error) {
|
|
||||||
// using oci tuf metadata and targets
|
|
||||||
metadataURI := "registry-1.docker.io/docker/tuf-metadata:latest"
|
|
||||||
targetsURI := "registry-1.docker.io/docker/tuf-targets"
|
|
||||||
// example using http tuf metadata and targets
|
|
||||||
// metadataURI := "https://docker.github.io/tuf-staging/metadata"
|
|
||||||
// targetsURI := "https://docker.github.io/tuf-staging/targets"
|
|
||||||
|
|
||||||
return tuf.NewClient(embed.RootStaging.Data, outputPath, metadataURI, targetsURI, tuf.NewMockVersionChecker())
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleVerify_remote() {
|
func ExampleVerify_remote() {
|
||||||
// create a tuf client
|
// create a tuf client
|
||||||
home, err := os.UserHomeDir()
|
home, err := os.UserHomeDir()
|
||||||
@@ -31,10 +19,7 @@ func ExampleVerify_remote() {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
tufOutputPath := filepath.Join(home, ".docker", "tuf")
|
tufOutputPath := filepath.Join(home, ".docker", "tuf")
|
||||||
tufClient, err := createTufClient(tufOutputPath)
|
tufClientOpts := tuf.NewDockerDefaultClientOptions(tufOutputPath)
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// create a resolver for remote attestations
|
// create a resolver for remote attestations
|
||||||
image := "registry-1.docker.io/library/notary:server"
|
image := "registry-1.docker.io/library/notary:server"
|
||||||
@@ -42,10 +27,11 @@ func ExampleVerify_remote() {
|
|||||||
|
|
||||||
// configure policy options
|
// configure policy options
|
||||||
opts := &policy.Options{
|
opts := &policy.Options{
|
||||||
TUFClient: tufClient,
|
TUFClientOptions: tufClientOpts,
|
||||||
LocalTargetsDir: filepath.Join(home, ".docker", "policy"), // location to store policy files downloaded from TUF
|
LocalTargetsDir: filepath.Join(home, ".docker", "policy"), // location to store policy files downloaded from TUF
|
||||||
LocalPolicyDir: "", // overrides TUF policy for local policy files if set
|
LocalPolicyDir: "", // overrides TUF policy for local policy files if set
|
||||||
PolicyID: "", // set to ignore policy mapping and select a policy by id
|
PolicyID: "", // set to ignore policy mapping and select a policy by id
|
||||||
|
DisableTUF: false, // set to disable TUF and rely on local policy files
|
||||||
}
|
}
|
||||||
|
|
||||||
src, err := oci.ParseImageSpec(image, oci.WithPlatform(platform))
|
src, err := oci.ParseImageSpec(image, oci.WithPlatform(platform))
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ var (
|
|||||||
PassMirrorPolicyDir = filepath.Join("..", "..", "test", "testdata", "local-policy-mirror")
|
PassMirrorPolicyDir = filepath.Join("..", "..", "test", "testdata", "local-policy-mirror")
|
||||||
PassNoTLPolicyDir = filepath.Join("..", "..", "test", "testdata", "local-policy-no-tl")
|
PassNoTLPolicyDir = filepath.Join("..", "..", "test", "testdata", "local-policy-no-tl")
|
||||||
FailPolicyDir = filepath.Join("..", "..", "test", "testdata", "local-policy-fail")
|
FailPolicyDir = filepath.Join("..", "..", "test", "testdata", "local-policy-fail")
|
||||||
|
InputsPolicyDir = filepath.Join("..", "..", "test", "testdata", "local-policy-inputs")
|
||||||
|
EmptyPolicyDir = filepath.Join("..", "..", "test", "testdata", "local-policy-no-policies")
|
||||||
TestTempDir = "attest-sign-test"
|
TestTempDir = "attest-sign-test"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -41,6 +43,7 @@ func TestSignVerifyOCILayout(t *testing.T) {
|
|||||||
}
|
}
|
||||||
policyOpts := &policy.Options{
|
policyOpts := &policy.Options{
|
||||||
LocalPolicyDir: PassPolicyDir,
|
LocalPolicyDir: PassPolicyDir,
|
||||||
|
DisableTUF: true,
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package attest
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -11,22 +13,49 @@ import (
|
|||||||
"github.com/docker/attest/pkg/config"
|
"github.com/docker/attest/pkg/config"
|
||||||
"github.com/docker/attest/pkg/oci"
|
"github.com/docker/attest/pkg/oci"
|
||||||
"github.com/docker/attest/pkg/policy"
|
"github.com/docker/attest/pkg/policy"
|
||||||
|
"github.com/docker/attest/pkg/tuf"
|
||||||
intoto "github.com/in-toto/in-toto-golang/in_toto"
|
intoto "github.com/in-toto/in-toto-golang/in_toto"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Verify(ctx context.Context, src *oci.ImageSpec, opts *policy.Options) (result *VerificationResult, err error) {
|
type Verifier interface {
|
||||||
|
Verify(ctx context.Context, src *oci.ImageSpec) (result *VerificationResult, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type tufVerifier struct {
|
||||||
|
opts *policy.Options
|
||||||
|
tufClient tuf.Downloader
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewVerifier(opts *policy.Options) (Verifier, error) {
|
||||||
|
err := populateDefaultOptions(opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var tufClient tuf.Downloader
|
||||||
|
if !opts.DisableTUF {
|
||||||
|
tufClient, err = tuf.NewClient(opts.TUFClientOptions)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create TUF client: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &tufVerifier{
|
||||||
|
opts: opts,
|
||||||
|
tufClient: tufClient,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *tufVerifier) Verify(ctx context.Context, src *oci.ImageSpec) (result *VerificationResult, err error) {
|
||||||
// so that we can resolve mapping from the image name earlier
|
// so that we can resolve mapping from the image name earlier
|
||||||
detailsResolver, err := policy.CreateImageDetailsResolver(src)
|
detailsResolver, err := policy.CreateImageDetailsResolver(src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create image details resolver: %w", err)
|
return nil, fmt.Errorf("failed to create image details resolver: %w", err)
|
||||||
}
|
}
|
||||||
if opts.AttestationStyle == "" {
|
imageName, err := detailsResolver.ImageName(ctx)
|
||||||
opts.AttestationStyle = config.AttestationStyleReferrers
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to resolve image name: %w", err)
|
||||||
}
|
}
|
||||||
if opts.ReferrersRepo != "" && opts.AttestationStyle != config.AttestationStyleReferrers {
|
policyResolver := policy.NewResolver(v.tufClient, v.opts)
|
||||||
return nil, fmt.Errorf("referrers repo specified but attestation source not set to referrers")
|
pctx, err := policyResolver.ResolvePolicy(ctx, imageName)
|
||||||
}
|
|
||||||
pctx, err := policy.ResolvePolicy(ctx, detailsResolver, opts)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to resolve policy: %w", err)
|
return nil, fmt.Errorf("failed to resolve policy: %w", err)
|
||||||
}
|
}
|
||||||
@@ -37,14 +66,14 @@ func Verify(ctx context.Context, src *oci.ImageSpec, opts *policy.Options) (resu
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
// this is overriding the mapping with a referrers config. Useful for testing if nothing else
|
// this is overriding the mapping with a referrers config. Useful for testing if nothing else
|
||||||
if opts.ReferrersRepo != "" {
|
if v.opts.ReferrersRepo != "" {
|
||||||
pctx.Mapping.Attestations = &config.AttestationConfig{
|
pctx.Mapping.Attestations = &config.AttestationConfig{
|
||||||
Repo: opts.ReferrersRepo,
|
Repo: v.opts.ReferrersRepo,
|
||||||
Style: config.AttestationStyleReferrers,
|
Style: config.AttestationStyleReferrers,
|
||||||
}
|
}
|
||||||
} else if opts.AttestationStyle == config.AttestationStyleAttached {
|
} else if v.opts.AttestationStyle == config.AttestationStyleAttached {
|
||||||
pctx.Mapping.Attestations = &config.AttestationConfig{
|
pctx.Mapping.Attestations = &config.AttestationConfig{
|
||||||
Repo: opts.ReferrersRepo,
|
Repo: v.opts.ReferrersRepo,
|
||||||
Style: config.AttestationStyleAttached,
|
Style: config.AttestationStyleAttached,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -60,6 +89,47 @@ func Verify(ctx context.Context, src *oci.ImageSpec, opts *policy.Options) (resu
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Verify(ctx context.Context, src *oci.ImageSpec, opts *policy.Options) (result *VerificationResult, err error) {
|
||||||
|
verifier, err := NewVerifier(opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return verifier.Verify(ctx, src)
|
||||||
|
}
|
||||||
|
|
||||||
|
func populateDefaultOptions(opts *policy.Options) (err error) {
|
||||||
|
if opts.LocalPolicyDir == "" && opts.DisableTUF {
|
||||||
|
return fmt.Errorf("local policy dir must be set if not using TUF")
|
||||||
|
}
|
||||||
|
if opts.LocalTargetsDir == "" {
|
||||||
|
opts.LocalTargetsDir, err = defaultLocalTargetsDir()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if opts.DisableTUF && opts.TUFClientOptions != nil {
|
||||||
|
return fmt.Errorf("TUF client options set but TUF disabled")
|
||||||
|
} else if opts.TUFClientOptions == nil && !opts.DisableTUF {
|
||||||
|
opts.TUFClientOptions = tuf.NewDockerDefaultClientOptions(opts.LocalTargetsDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.AttestationStyle == "" {
|
||||||
|
opts.AttestationStyle = config.AttestationStyleReferrers
|
||||||
|
}
|
||||||
|
if opts.ReferrersRepo != "" && opts.AttestationStyle != config.AttestationStyleReferrers {
|
||||||
|
return fmt.Errorf("referrers repo specified but attestation source not set to referrers")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultLocalTargetsDir() (string, error) {
|
||||||
|
homeDir, err := os.UserHomeDir()
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to get user home directory: %w", err)
|
||||||
|
}
|
||||||
|
return filepath.Join(homeDir, ".docker", "tuf"), nil
|
||||||
|
}
|
||||||
|
|
||||||
func toVerificationResult(p *policy.Policy, input *policy.Input, result *policy.Result) (*VerificationResult, error) {
|
func toVerificationResult(p *policy.Policy, input *policy.Input, result *policy.Result) (*VerificationResult, error) {
|
||||||
dgst, err := oci.SplitDigest(input.Digest)
|
dgst, err := oci.SplitDigest(input.Digest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -140,14 +210,34 @@ func VerifyAttestations(ctx context.Context, resolver attestation.Resolver, pctx
|
|||||||
name = strings.Replace(name, oldName, pctx.ResolvedName, 1)
|
name = strings.Replace(name, oldName, pctx.ResolvedName, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
purl, canonical, err := oci.RefToPURL(name, platform)
|
ref, err := reference.ParseNormalizedNamed(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse ref %q: %w", ref, err)
|
||||||
|
}
|
||||||
|
purl, canonical, err := oci.RefToPURL(ref, platform)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to convert ref to purl: %w", err)
|
return nil, fmt.Errorf("failed to convert ref to purl: %w", err)
|
||||||
}
|
}
|
||||||
|
var tag string
|
||||||
|
if !canonical {
|
||||||
|
// unlike the function name indicates, this adds latest if no tag is present
|
||||||
|
ref = reference.TagNameOnly(ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tagged, ok := ref.(reference.Tagged); ok {
|
||||||
|
tag = tagged.Tag()
|
||||||
|
}
|
||||||
input := &policy.Input{
|
input := &policy.Input{
|
||||||
Digest: digest,
|
Digest: digest,
|
||||||
PURL: purl,
|
PURL: purl,
|
||||||
IsCanonical: canonical,
|
Platform: platform.String(),
|
||||||
|
Domain: reference.Domain(ref),
|
||||||
|
NormalizedName: reference.Path(ref),
|
||||||
|
FamiliarName: reference.FamiliarName(ref),
|
||||||
|
}
|
||||||
|
// rego has null strings
|
||||||
|
if tag != "" {
|
||||||
|
input.Tag = tag
|
||||||
}
|
}
|
||||||
|
|
||||||
evaluator, err := policy.GetPolicyEvaluator(ctx)
|
evaluator, err := policy.GetPolicyEvaluator(ctx)
|
||||||
|
|||||||
@@ -8,11 +8,13 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/distribution/reference"
|
||||||
"github.com/docker/attest/internal/test"
|
"github.com/docker/attest/internal/test"
|
||||||
"github.com/docker/attest/pkg/attestation"
|
"github.com/docker/attest/pkg/attestation"
|
||||||
"github.com/docker/attest/pkg/config"
|
"github.com/docker/attest/pkg/config"
|
||||||
"github.com/docker/attest/pkg/oci"
|
"github.com/docker/attest/pkg/oci"
|
||||||
"github.com/docker/attest/pkg/policy"
|
"github.com/docker/attest/pkg/policy"
|
||||||
|
"github.com/docker/attest/pkg/tuf"
|
||||||
intoto "github.com/in-toto/in-toto-golang/in_toto"
|
intoto "github.com/in-toto/in-toto-golang/in_toto"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@@ -90,6 +92,7 @@ func TestVSA(t *testing.T) {
|
|||||||
policyOpts := &policy.Options{
|
policyOpts := &policy.Options{
|
||||||
LocalPolicyDir: PassPolicyDir,
|
LocalPolicyDir: PassPolicyDir,
|
||||||
AttestationStyle: config.AttestationStyleAttached,
|
AttestationStyle: config.AttestationStyleAttached,
|
||||||
|
DisableTUF: true,
|
||||||
}
|
}
|
||||||
results, err := Verify(ctx, spec, policyOpts)
|
results, err := Verify(ctx, spec, policyOpts)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -98,7 +101,7 @@ func TestVSA(t *testing.T) {
|
|||||||
|
|
||||||
if assert.NotNil(t, results.Input) {
|
if assert.NotNil(t, results.Input) {
|
||||||
assert.Equal(t, "sha256:da8b190665956ea07890a0273e2a9c96bfe291662f08e2860e868eef69c34620", results.Input.Digest)
|
assert.Equal(t, "sha256:da8b190665956ea07890a0273e2a9c96bfe291662f08e2860e868eef69c34620", results.Input.Digest)
|
||||||
assert.False(t, results.Input.IsCanonical)
|
assert.NotNil(t, results.Input.Tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.Equal(t, intoto.StatementInTotoV01, results.VSA.Type)
|
assert.Equal(t, intoto.StatementInTotoV01, results.VSA.Type)
|
||||||
@@ -142,6 +145,7 @@ func TestVerificationFailure(t *testing.T) {
|
|||||||
policyOpts := &policy.Options{
|
policyOpts := &policy.Options{
|
||||||
LocalPolicyDir: FailPolicyDir,
|
LocalPolicyDir: FailPolicyDir,
|
||||||
AttestationStyle: config.AttestationStyleAttached,
|
AttestationStyle: config.AttestationStyleAttached,
|
||||||
|
DisableTUF: true,
|
||||||
}
|
}
|
||||||
results, err := Verify(ctx, spec, policyOpts)
|
results, err := Verify(ctx, spec, policyOpts)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -176,17 +180,18 @@ func TestSignVerify(t *testing.T) {
|
|||||||
outputLayout := test.CreateTempDir(t, "", TestTempDir)
|
outputLayout := test.CreateTempDir(t, "", TestTempDir)
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
signTL bool
|
signTL bool
|
||||||
policyDir string
|
policyDir string
|
||||||
imageName string
|
imageName string
|
||||||
expectError bool
|
expectedNonSuccess Outcome
|
||||||
}{
|
}{
|
||||||
{name: "happy path", signTL: true, policyDir: PassNoTLPolicyDir},
|
{name: "happy path", signTL: true, policyDir: PassNoTLPolicyDir},
|
||||||
{name: "sign tl, verify no tl", signTL: true, policyDir: PassPolicyDir},
|
{name: "sign tl, verify no tl", signTL: true, policyDir: PassPolicyDir},
|
||||||
{name: "no tl", signTL: false, policyDir: PassPolicyDir},
|
{name: "no tl", signTL: false, policyDir: PassPolicyDir},
|
||||||
{name: "mirror", signTL: true, policyDir: PassMirrorPolicyDir, imageName: "mirror.org/library/test-image:test"},
|
{name: "mirror", signTL: true, policyDir: PassMirrorPolicyDir, imageName: "mirror.org/library/test-image:test"},
|
||||||
{name: "mirror no match", signTL: true, policyDir: PassMirrorPolicyDir, imageName: "incorrect.org/library/test-image:test", expectError: true},
|
{name: "mirror no match", signTL: true, policyDir: PassMirrorPolicyDir, imageName: "incorrect.org/library/test-image:test", expectedNonSuccess: OutcomeNoPolicy},
|
||||||
|
{name: "verify inputs", signTL: false, policyDir: InputsPolicyDir},
|
||||||
}
|
}
|
||||||
|
|
||||||
attIdx, err := oci.IndexFromPath(test.UnsignedTestImage)
|
attIdx, err := oci.IndexFromPath(test.UnsignedTestImage)
|
||||||
@@ -216,19 +221,93 @@ func TestSignVerify(t *testing.T) {
|
|||||||
|
|
||||||
policyOpts := &policy.Options{
|
policyOpts := &policy.Options{
|
||||||
LocalPolicyDir: tc.policyDir,
|
LocalPolicyDir: tc.policyDir,
|
||||||
|
DisableTUF: true,
|
||||||
}
|
}
|
||||||
results, err := Verify(ctx, spec, policyOpts)
|
results, err := Verify(ctx, spec, policyOpts)
|
||||||
if tc.expectError {
|
require.NoError(t, err)
|
||||||
require.Error(t, err)
|
if tc.expectedNonSuccess != "" {
|
||||||
|
assert.Equal(t, tc.expectedNonSuccess, results.Outcome)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, OutcomeSuccess, results.Outcome)
|
assert.Equal(t, OutcomeSuccess, results.Outcome)
|
||||||
platform, err := oci.ParsePlatform(LinuxAMD64)
|
platform, err := oci.ParsePlatform(LinuxAMD64)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
expectedPURL, _, err := oci.RefToPURL(attIdx.Name, platform)
|
|
||||||
|
ref, err := reference.ParseNormalizedNamed(attIdx.Name)
|
||||||
|
require.NoError(t, err)
|
||||||
|
expectedPURL, _, err := oci.RefToPURL(ref, platform)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, expectedPURL, results.Input.PURL)
|
assert.Equal(t, expectedPURL, results.Input.PURL)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDefaultOptions(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
tufOpts *tuf.ClientOptions
|
||||||
|
localTargetsDir string
|
||||||
|
attestationStyle config.AttestationStyle
|
||||||
|
referrersRepo string
|
||||||
|
expectedError string
|
||||||
|
disableTuf bool
|
||||||
|
localPolicyDir string
|
||||||
|
}{
|
||||||
|
{name: "empty"},
|
||||||
|
{name: "tufClient provided", tufOpts: &tuf.ClientOptions{MetadataSource: "a", TargetsSource: "b"}},
|
||||||
|
{name: "localTargetsDir provided", localTargetsDir: test.CreateTempDir(t, "", TestTempDir)},
|
||||||
|
{name: "attestationStyle provided", attestationStyle: config.AttestationStyleAttached},
|
||||||
|
{name: "referrersRepo provided", referrersRepo: "referrers"},
|
||||||
|
{name: "referrersRepo provided with attached", referrersRepo: "referrers", attestationStyle: config.AttestationStyleAttached, expectedError: "referrers repo specified but attestation source not set to referrers"},
|
||||||
|
{name: "tuf disabled and no local-policy-dir", disableTuf: true, expectedError: "local policy dir must be set if not using TUF"},
|
||||||
|
{name: "tuf disabled but options set", disableTuf: true, tufOpts: &tuf.ClientOptions{MetadataSource: "a", TargetsSource: "b"}, localPolicyDir: "foo", expectedError: "TUF client options set but TUF disabled"},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
defaultTargets, err := defaultLocalTargetsDir()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
opts := &policy.Options{
|
||||||
|
TUFClientOptions: tc.tufOpts,
|
||||||
|
LocalTargetsDir: tc.localTargetsDir,
|
||||||
|
AttestationStyle: tc.attestationStyle,
|
||||||
|
ReferrersRepo: tc.referrersRepo,
|
||||||
|
DisableTUF: tc.disableTuf,
|
||||||
|
LocalPolicyDir: tc.localPolicyDir,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = populateDefaultOptions(opts)
|
||||||
|
if tc.expectedError != "" {
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.Equal(t, tc.expectedError, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
if tc.localTargetsDir != "" {
|
||||||
|
assert.Equal(t, tc.localTargetsDir, opts.LocalTargetsDir)
|
||||||
|
} else {
|
||||||
|
assert.Equal(t, defaultTargets, opts.LocalTargetsDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.attestationStyle != "" {
|
||||||
|
assert.Equal(t, tc.attestationStyle, opts.AttestationStyle)
|
||||||
|
} else {
|
||||||
|
assert.Equal(t, config.AttestationStyleReferrers, opts.AttestationStyle)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.tufOpts != nil {
|
||||||
|
assert.Equal(t, tc.tufOpts, opts.TUFClientOptions)
|
||||||
|
} else {
|
||||||
|
assert.NotNil(t, opts.TUFClientOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.referrersRepo != "" {
|
||||||
|
assert.Equal(t, tc.referrersRepo, opts.ReferrersRepo)
|
||||||
|
} else {
|
||||||
|
assert.Empty(t, opts.ReferrersRepo)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -139,6 +139,7 @@ func TestAttestationReferenceTypes(t *testing.T) {
|
|||||||
|
|
||||||
policyOpts := &policy.Options{
|
policyOpts := &policy.Options{
|
||||||
LocalPolicyDir: LocalPolicy,
|
LocalPolicyDir: LocalPolicy,
|
||||||
|
DisableTUF: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
if tc.referrersRepo != "" {
|
if tc.referrersRepo != "" {
|
||||||
@@ -269,6 +270,7 @@ func TestReferencesInDifferentRepo(t *testing.T) {
|
|||||||
referencedImage := fmt.Sprintf("%s@%s", indexName, mf.Digest.String())
|
referencedImage := fmt.Sprintf("%s@%s", indexName, mf.Digest.String())
|
||||||
policyOpts := &policy.Options{
|
policyOpts := &policy.Options{
|
||||||
LocalPolicyDir: PassPolicyDir,
|
LocalPolicyDir: PassPolicyDir,
|
||||||
|
DisableTUF: true,
|
||||||
}
|
}
|
||||||
src, err := oci.ParseImageSpec(referencedImage)
|
src, err := oci.ParseImageSpec(referencedImage)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/attest/internal/embed"
|
|
||||||
"github.com/docker/attest/pkg/mirror"
|
"github.com/docker/attest/pkg/mirror"
|
||||||
"github.com/docker/attest/pkg/oci"
|
"github.com/docker/attest/pkg/oci"
|
||||||
"github.com/docker/attest/pkg/tuf"
|
"github.com/docker/attest/pkg/tuf"
|
||||||
@@ -30,7 +29,7 @@ func ExampleNewTUFMirror() {
|
|||||||
// configure TUF mirror
|
// configure TUF mirror
|
||||||
metadataURI := "https://docker.github.io/tuf-staging/metadata"
|
metadataURI := "https://docker.github.io/tuf-staging/metadata"
|
||||||
targetsURI := "https://docker.github.io/tuf-staging/targets"
|
targetsURI := "https://docker.github.io/tuf-staging/targets"
|
||||||
m, err := mirror.NewTUFMirror(embed.RootStaging.Data, tufOutputPath, metadataURI, targetsURI, tuf.NewMockVersionChecker())
|
m, err := mirror.NewTUFMirror(tuf.DockerTUFRootStaging.Data, tufOutputPath, metadataURI, targetsURI, tuf.NewMockVersionChecker())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/attest/internal/embed"
|
|
||||||
"github.com/docker/attest/internal/test"
|
"github.com/docker/attest/internal/test"
|
||||||
"github.com/docker/attest/pkg/tuf"
|
"github.com/docker/attest/pkg/tuf"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -26,7 +25,7 @@ func TestGetTufMetadataMirror(t *testing.T) {
|
|||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
|
||||||
path := test.CreateTempDir(t, "", "tuf_temp")
|
path := test.CreateTempDir(t, "", "tuf_temp")
|
||||||
m, err := NewTUFMirror(embed.RootDev.Data, path, server.URL+metadataPath, server.URL+targetsPath, tuf.NewMockVersionChecker())
|
m, err := NewTUFMirror(tuf.DockerTUFRootDev.Data, path, server.URL+metadataPath, server.URL+targetsPath, tuf.NewMockVersionChecker())
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
tufMetadata, err := m.getMetadataMirror(server.URL + metadataPath)
|
tufMetadata, err := m.getMetadataMirror(server.URL + metadataPath)
|
||||||
@@ -44,7 +43,7 @@ func TestGetMetadataManifest(t *testing.T) {
|
|||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
|
||||||
path := test.CreateTempDir(t, "", "tuf_temp")
|
path := test.CreateTempDir(t, "", "tuf_temp")
|
||||||
m, err := NewTUFMirror(embed.RootDev.Data, path, server.URL+metadataPath, server.URL+targetsPath, tuf.NewMockVersionChecker())
|
m, err := NewTUFMirror(tuf.DockerTUFRootDev.Data, path, server.URL+metadataPath, server.URL+targetsPath, tuf.NewMockVersionChecker())
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
img, err := m.GetMetadataManifest(server.URL + metadataPath)
|
img, err := m.GetMetadataManifest(server.URL + metadataPath)
|
||||||
@@ -83,7 +82,7 @@ func TestGetDelegatedMetadataMirrors(t *testing.T) {
|
|||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
|
||||||
path := test.CreateTempDir(t, "", "tuf_temp")
|
path := test.CreateTempDir(t, "", "tuf_temp")
|
||||||
m, err := NewTUFMirror(embed.RootDev.Data, path, server.URL+metadataPath, server.URL+targetsPath, tuf.NewMockVersionChecker())
|
m, err := NewTUFMirror(tuf.DockerTUFRootDev.Data, path, server.URL+metadataPath, server.URL+targetsPath, tuf.NewMockVersionChecker())
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
delegations, err := m.GetDelegatedMetadataMirrors()
|
delegations, err := m.GetDelegatedMetadataMirrors()
|
||||||
|
|||||||
@@ -3,15 +3,14 @@ package mirror
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/docker/attest/internal/embed"
|
|
||||||
"github.com/docker/attest/pkg/tuf"
|
"github.com/docker/attest/pkg/tuf"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewTUFMirror(root []byte, tufPath, metadataURL, targetsURL string, versionChecker tuf.VersionChecker) (*TUFMirror, error) {
|
func NewTUFMirror(root []byte, tufPath, metadataURL, targetsURL string, versionChecker tuf.VersionChecker) (*TUFMirror, error) {
|
||||||
if root == nil {
|
if root == nil {
|
||||||
root = embed.RootDefault.Data
|
root = tuf.DockerTUFRootDefault.Data
|
||||||
}
|
}
|
||||||
tufClient, err := tuf.NewClient(root, tufPath, metadataURL, targetsURL, versionChecker)
|
tufClient, err := tuf.NewClient(&tuf.ClientOptions{InitialRoot: root, Path: tufPath, MetadataSource: metadataURL, TargetsSource: targetsURL, VersionChecker: versionChecker})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create TUF client: %w", err)
|
return nil, fmt.Errorf("failed to create TUF client: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/attest/internal/embed"
|
|
||||||
"github.com/docker/attest/internal/test"
|
"github.com/docker/attest/internal/test"
|
||||||
"github.com/docker/attest/pkg/tuf"
|
"github.com/docker/attest/pkg/tuf"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -27,7 +26,7 @@ func TestGetTufTargetsMirror(t *testing.T) {
|
|||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
|
||||||
path := test.CreateTempDir(t, "", "tuf_temp")
|
path := test.CreateTempDir(t, "", "tuf_temp")
|
||||||
m, err := NewTUFMirror(embed.RootDev.Data, path, server.URL+metadataPath, server.URL+targetsPath, tuf.NewMockVersionChecker())
|
m, err := NewTUFMirror(tuf.DockerTUFRootDev.Data, path, server.URL+metadataPath, server.URL+targetsPath, tuf.NewMockVersionChecker())
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
targets, err := m.GetTUFTargetMirrors()
|
targets, err := m.GetTUFTargetMirrors()
|
||||||
@@ -61,7 +60,7 @@ func TestTargetDelegationMetadata(t *testing.T) {
|
|||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
|
||||||
path := test.CreateTempDir(t, "", "tuf_temp")
|
path := test.CreateTempDir(t, "", "tuf_temp")
|
||||||
tm, err := NewTUFMirror(embed.RootDev.Data, path, server.URL+metadataPath, server.URL+targetsPath, tuf.NewMockVersionChecker())
|
tm, err := NewTUFMirror(tuf.DockerTUFRootDev.Data, path, server.URL+metadataPath, server.URL+targetsPath, tuf.NewMockVersionChecker())
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
targets, err := tm.TUFClient.LoadDelegatedTargets("test-role", "targets")
|
targets, err := tm.TUFClient.LoadDelegatedTargets("test-role", "targets")
|
||||||
@@ -74,7 +73,7 @@ func TestGetDelegatedTargetMirrors(t *testing.T) {
|
|||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
|
||||||
path := test.CreateTempDir(t, "", "tuf_temp")
|
path := test.CreateTempDir(t, "", "tuf_temp")
|
||||||
m, err := NewTUFMirror(embed.RootDev.Data, path, server.URL+metadataPath, server.URL+targetsPath, tuf.NewMockVersionChecker())
|
m, err := NewTUFMirror(tuf.DockerTUFRootDev.Data, path, server.URL+metadataPath, server.URL+targetsPath, tuf.NewMockVersionChecker())
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
mirrors, err := m.GetDelegatedTargetMirrors()
|
mirrors, err := m.GetDelegatedTargetMirrors()
|
||||||
|
|||||||
@@ -52,12 +52,8 @@ func ImageDescriptor(ix *v1.IndexManifest, platform *v1.Platform) (*v1.Descripto
|
|||||||
return nil, fmt.Errorf("no image found for platform %v", platform)
|
return nil, fmt.Errorf("no image found for platform %v", platform)
|
||||||
}
|
}
|
||||||
|
|
||||||
func RefToPURL(ref string, platform *v1.Platform) (string, bool, error) {
|
func RefToPURL(named reference.Named, platform *v1.Platform) (string, bool, error) {
|
||||||
var isCanonical bool
|
var isCanonical bool
|
||||||
named, err := reference.ParseNormalizedNamed(ref)
|
|
||||||
if err != nil {
|
|
||||||
return "", false, fmt.Errorf("failed to parse ref %q: %w", ref, err)
|
|
||||||
}
|
|
||||||
var qualifiers []packageurl.Qualifier
|
var qualifiers []packageurl.Qualifier
|
||||||
|
|
||||||
if canonical, ok := named.(reference.Canonical); ok {
|
if canonical, ok := named.(reference.Canonical); ok {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package oci_test
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/distribution/reference"
|
||||||
"github.com/docker/attest/internal/test"
|
"github.com/docker/attest/internal/test"
|
||||||
"github.com/docker/attest/pkg/oci"
|
"github.com/docker/attest/pkg/oci"
|
||||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||||
@@ -14,42 +15,51 @@ import (
|
|||||||
func TestRefToPurl(t *testing.T) {
|
func TestRefToPurl(t *testing.T) {
|
||||||
arm, err := oci.ParsePlatform("arm64/linux")
|
arm, err := oci.ParsePlatform("arm64/linux")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
purl, canonical, err := oci.RefToPURL("alpine", arm)
|
ref, err := reference.ParseNormalizedNamed("alpine")
|
||||||
|
require.NoError(t, err)
|
||||||
|
purl, canonical, err := oci.RefToPURL(ref, arm)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, "pkg:docker/alpine@latest?platform=arm64%2Flinux", purl)
|
assert.Equal(t, "pkg:docker/alpine@latest?platform=arm64%2Flinux", purl)
|
||||||
assert.False(t, canonical)
|
assert.False(t, canonical)
|
||||||
|
ref, err = reference.ParseNormalizedNamed("alpine:123")
|
||||||
purl, canonical, err = oci.RefToPURL("alpine:123", arm)
|
require.NoError(t, err)
|
||||||
|
purl, canonical, err = oci.RefToPURL(ref, arm)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, "pkg:docker/alpine@123?platform=arm64%2Flinux", purl)
|
assert.Equal(t, "pkg:docker/alpine@123?platform=arm64%2Flinux", purl)
|
||||||
assert.False(t, canonical)
|
assert.False(t, canonical)
|
||||||
|
ref, err = reference.ParseNormalizedNamed("google/alpine:123")
|
||||||
purl, canonical, err = oci.RefToPURL("google/alpine:123", arm)
|
require.NoError(t, err)
|
||||||
|
purl, canonical, err = oci.RefToPURL(ref, arm)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, "pkg:docker/google/alpine@123?platform=arm64%2Flinux", purl)
|
assert.Equal(t, "pkg:docker/google/alpine@123?platform=arm64%2Flinux", purl)
|
||||||
assert.False(t, canonical)
|
assert.False(t, canonical)
|
||||||
|
ref, err = reference.ParseNormalizedNamed("library/alpine:123")
|
||||||
purl, canonical, err = oci.RefToPURL("library/alpine:123", arm)
|
require.NoError(t, err)
|
||||||
|
purl, canonical, err = oci.RefToPURL(ref, arm)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, "pkg:docker/alpine@123?platform=arm64%2Flinux", purl)
|
assert.Equal(t, "pkg:docker/alpine@123?platform=arm64%2Flinux", purl)
|
||||||
assert.False(t, canonical)
|
assert.False(t, canonical)
|
||||||
|
ref, err = reference.ParseNormalizedNamed("docker.io/library/alpine:123")
|
||||||
purl, canonical, err = oci.RefToPURL("docker.io/library/alpine:123", arm)
|
require.NoError(t, err)
|
||||||
|
purl, canonical, err = oci.RefToPURL(ref, arm)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, "pkg:docker/alpine@123?platform=arm64%2Flinux", purl)
|
assert.Equal(t, "pkg:docker/alpine@123?platform=arm64%2Flinux", purl)
|
||||||
assert.False(t, canonical)
|
assert.False(t, canonical)
|
||||||
|
ref, err = reference.ParseNormalizedNamed("localhost:5001/library/alpine:123")
|
||||||
purl, canonical, err = oci.RefToPURL("localhost:5001/library/alpine:123", arm)
|
require.NoError(t, err)
|
||||||
|
purl, canonical, err = oci.RefToPURL(ref, arm)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, "pkg:docker/localhost%3A5001/library/alpine@123?platform=arm64%2Flinux", purl)
|
assert.Equal(t, "pkg:docker/localhost%3A5001/library/alpine@123?platform=arm64%2Flinux", purl)
|
||||||
assert.False(t, canonical)
|
assert.False(t, canonical)
|
||||||
|
ref, err = reference.ParseNormalizedNamed("localhost:5001/alpine:123")
|
||||||
purl, canonical, err = oci.RefToPURL("localhost:5001/alpine:123", arm)
|
require.NoError(t, err)
|
||||||
|
purl, canonical, err = oci.RefToPURL(ref, arm)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, "pkg:docker/localhost%3A5001/alpine@123?platform=arm64%2Flinux", purl)
|
assert.Equal(t, "pkg:docker/localhost%3A5001/alpine@123?platform=arm64%2Flinux", purl)
|
||||||
assert.False(t, canonical)
|
assert.False(t, canonical)
|
||||||
|
ref, err = reference.ParseNormalizedNamed("localhost:5001/alpine@sha256:c5b1261d6d3e43071626931fc004f70149baeba2c8ec672bd4f27761f8e1ad6b")
|
||||||
purl, canonical, err = oci.RefToPURL("localhost:5001/alpine@sha256:c5b1261d6d3e43071626931fc004f70149baeba2c8ec672bd4f27761f8e1ad6b", arm)
|
require.NoError(t, err)
|
||||||
|
purl, canonical, err = oci.RefToPURL(ref, arm)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, "pkg:docker/localhost%3A5001/alpine?digest=sha256%3Ac5b1261d6d3e43071626931fc004f70149baeba2c8ec672bd4f27761f8e1ad6b&platform=arm64%2Flinux", purl)
|
assert.Equal(t, "pkg:docker/localhost%3A5001/alpine?digest=sha256%3Ac5b1261d6d3e43071626931fc004f70149baeba2c8ec672bd4f27761f8e1ad6b&platform=arm64%2Flinux", purl)
|
||||||
assert.True(t, canonical)
|
assert.True(t, canonical)
|
||||||
|
|||||||
65
pkg/policy/match.go
Normal file
65
pkg/policy/match.go
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
package policy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/docker/attest/pkg/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
type matchType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
matchTypePolicy matchType = "policy"
|
||||||
|
matchTypeMatchNoPolicy matchType = "match_no_policy"
|
||||||
|
matchTypeNoMatch matchType = "no_match"
|
||||||
|
)
|
||||||
|
|
||||||
|
type policyMatch struct {
|
||||||
|
matchType matchType
|
||||||
|
policy *config.PolicyMapping
|
||||||
|
rule *config.PolicyRule
|
||||||
|
matchedName string
|
||||||
|
}
|
||||||
|
|
||||||
|
func findPolicyMatch(imageName string, mappings *config.PolicyMappings) (*policyMatch, error) {
|
||||||
|
if mappings == nil {
|
||||||
|
return &policyMatch{matchType: matchTypeNoMatch, matchedName: imageName}, nil
|
||||||
|
}
|
||||||
|
return findPolicyMatchImpl(imageName, mappings, make(map[*config.PolicyRule]bool))
|
||||||
|
}
|
||||||
|
|
||||||
|
func findPolicyMatchImpl(imageName string, mappings *config.PolicyMappings, matched map[*config.PolicyRule]bool) (*policyMatch, error) {
|
||||||
|
for _, rule := range mappings.Rules {
|
||||||
|
if rule.Pattern.MatchString(imageName) {
|
||||||
|
switch {
|
||||||
|
case rule.PolicyID == "" && rule.Replacement == "":
|
||||||
|
return nil, fmt.Errorf("rule %s has neither policy-id nor rewrite", rule.Pattern)
|
||||||
|
case rule.PolicyID != "" && rule.Replacement != "":
|
||||||
|
return nil, fmt.Errorf("rule %s has both policy-id and rewrite", rule.Pattern)
|
||||||
|
case rule.PolicyID != "":
|
||||||
|
policy := mappings.Policies[rule.PolicyID]
|
||||||
|
if policy != nil {
|
||||||
|
return &policyMatch{
|
||||||
|
matchType: matchTypePolicy,
|
||||||
|
policy: policy,
|
||||||
|
rule: rule,
|
||||||
|
matchedName: imageName,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
return &policyMatch{
|
||||||
|
matchType: matchTypeMatchNoPolicy,
|
||||||
|
rule: rule,
|
||||||
|
matchedName: imageName,
|
||||||
|
}, nil
|
||||||
|
case rule.Replacement != "":
|
||||||
|
if matched[rule] {
|
||||||
|
return nil, fmt.Errorf("rewrite loop detected")
|
||||||
|
}
|
||||||
|
matched[rule] = true
|
||||||
|
imageName = rule.Pattern.ReplaceAllString(imageName, rule.Replacement)
|
||||||
|
return findPolicyMatchImpl(imageName, mappings, matched)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &policyMatch{matchType: matchTypeNoMatch, matchedName: imageName}, nil
|
||||||
|
}
|
||||||
@@ -1,247 +1,13 @@
|
|||||||
package policy
|
package policy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/distribution/reference"
|
|
||||||
"github.com/docker/attest/internal/util"
|
|
||||||
"github.com/docker/attest/pkg/attestation"
|
"github.com/docker/attest/pkg/attestation"
|
||||||
"github.com/docker/attest/pkg/config"
|
"github.com/docker/attest/pkg/config"
|
||||||
"github.com/docker/attest/pkg/oci"
|
"github.com/docker/attest/pkg/oci"
|
||||||
)
|
)
|
||||||
|
|
||||||
func resolveLocalPolicy(opts *Options, mapping *config.PolicyMapping, imageName string, matchedName string) (*Policy, error) {
|
|
||||||
if opts.LocalPolicyDir == "" {
|
|
||||||
return nil, fmt.Errorf("local policy dir not set")
|
|
||||||
}
|
|
||||||
var URI string
|
|
||||||
var digest map[string]string
|
|
||||||
files := make([]*File, 0, len(mapping.Files))
|
|
||||||
for _, f := range mapping.Files {
|
|
||||||
filename := f.Path
|
|
||||||
filePath := path.Join(opts.LocalPolicyDir, filename)
|
|
||||||
fileContents, err := os.ReadFile(filePath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to read policy file %s: %w", filename, err)
|
|
||||||
}
|
|
||||||
files = append(files, &File{
|
|
||||||
Path: filename,
|
|
||||||
Content: fileContents,
|
|
||||||
})
|
|
||||||
// if the file is a policy file, store the URI and digest
|
|
||||||
if filepath.Ext(filename) == ".rego" {
|
|
||||||
// TODO: support multiple rego files, need some way to identify the main policy file
|
|
||||||
if URI != "" {
|
|
||||||
return nil, fmt.Errorf("multiple policy files found in policy mapping")
|
|
||||||
}
|
|
||||||
URI = filePath
|
|
||||||
digest = map[string]string{"sha256": util.SHA256Hex(fileContents)}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if URI == "" {
|
|
||||||
return nil, fmt.Errorf("no policy file found in policy mapping")
|
|
||||||
}
|
|
||||||
policy := &Policy{
|
|
||||||
InputFiles: files,
|
|
||||||
Mapping: mapping,
|
|
||||||
URI: URI,
|
|
||||||
Digest: digest,
|
|
||||||
}
|
|
||||||
if imageName != matchedName {
|
|
||||||
policy.ResolvedName = matchedName
|
|
||||||
}
|
|
||||||
return policy, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func resolveTUFPolicy(opts *Options, mapping *config.PolicyMapping, imageName string, matchedName string) (*Policy, error) {
|
|
||||||
var URI string
|
|
||||||
var digest map[string]string
|
|
||||||
files := make([]*File, 0, len(mapping.Files))
|
|
||||||
for _, f := range mapping.Files {
|
|
||||||
filename := f.Path
|
|
||||||
file, err := opts.TUFClient.DownloadTarget(filename, filepath.Join(opts.LocalTargetsDir, filename))
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to download policy file %s: %w", filename, err)
|
|
||||||
}
|
|
||||||
files = append(files, &File{
|
|
||||||
Path: filename,
|
|
||||||
Content: file.Data,
|
|
||||||
})
|
|
||||||
// if the file is a policy file, store the URI and digest
|
|
||||||
if filepath.Ext(filename) == ".rego" {
|
|
||||||
// TODO: support multiple rego files, need some way to identify the main policy file
|
|
||||||
if URI != "" {
|
|
||||||
return nil, fmt.Errorf("multiple policy files found in policy mapping")
|
|
||||||
}
|
|
||||||
URI = file.TargetURI
|
|
||||||
digest = map[string]string{"sha256": file.Digest}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if URI == "" {
|
|
||||||
return nil, fmt.Errorf("no policy file found in policy mapping")
|
|
||||||
}
|
|
||||||
policy := &Policy{
|
|
||||||
InputFiles: files,
|
|
||||||
Mapping: mapping,
|
|
||||||
URI: URI,
|
|
||||||
Digest: digest,
|
|
||||||
}
|
|
||||||
if imageName != matchedName {
|
|
||||||
policy.ResolvedName = matchedName
|
|
||||||
}
|
|
||||||
return policy, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type matchType string
|
|
||||||
|
|
||||||
const (
|
|
||||||
matchTypePolicy matchType = "policy"
|
|
||||||
matchTypeMatchNoPolicy matchType = "match_no_policy"
|
|
||||||
matchTypeNoMatch matchType = "no_match"
|
|
||||||
)
|
|
||||||
|
|
||||||
type policyMatch struct {
|
|
||||||
matchType matchType
|
|
||||||
policy *config.PolicyMapping
|
|
||||||
rule *config.PolicyRule
|
|
||||||
matchedName string
|
|
||||||
}
|
|
||||||
|
|
||||||
func findPolicyMatch(imageName string, mappings *config.PolicyMappings) (*policyMatch, error) {
|
|
||||||
if mappings == nil {
|
|
||||||
return &policyMatch{matchType: matchTypeNoMatch, matchedName: imageName}, nil
|
|
||||||
}
|
|
||||||
return findPolicyMatchImpl(imageName, mappings, make(map[*config.PolicyRule]bool))
|
|
||||||
}
|
|
||||||
|
|
||||||
func findPolicyMatchImpl(imageName string, mappings *config.PolicyMappings, matched map[*config.PolicyRule]bool) (*policyMatch, error) {
|
|
||||||
for _, rule := range mappings.Rules {
|
|
||||||
if rule.Pattern.MatchString(imageName) {
|
|
||||||
switch {
|
|
||||||
case rule.PolicyID == "" && rule.Replacement == "":
|
|
||||||
return nil, fmt.Errorf("rule %s has neither policy-id nor rewrite", rule.Pattern)
|
|
||||||
case rule.PolicyID != "" && rule.Replacement != "":
|
|
||||||
return nil, fmt.Errorf("rule %s has both policy-id and rewrite", rule.Pattern)
|
|
||||||
case rule.PolicyID != "":
|
|
||||||
policy := mappings.Policies[rule.PolicyID]
|
|
||||||
if policy != nil {
|
|
||||||
return &policyMatch{
|
|
||||||
matchType: matchTypePolicy,
|
|
||||||
policy: policy,
|
|
||||||
rule: rule,
|
|
||||||
matchedName: imageName,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
return &policyMatch{
|
|
||||||
matchType: matchTypeMatchNoPolicy,
|
|
||||||
rule: rule,
|
|
||||||
matchedName: imageName,
|
|
||||||
}, nil
|
|
||||||
case rule.Replacement != "":
|
|
||||||
if matched[rule] {
|
|
||||||
return nil, fmt.Errorf("rewrite loop detected")
|
|
||||||
}
|
|
||||||
matched[rule] = true
|
|
||||||
imageName = rule.Pattern.ReplaceAllString(imageName, rule.Replacement)
|
|
||||||
return findPolicyMatchImpl(imageName, mappings, matched)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &policyMatch{matchType: matchTypeNoMatch, matchedName: imageName}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func resolvePolicyByID(opts *Options) (*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 {
|
|
||||||
policy := localMappings.Policies[opts.PolicyID]
|
|
||||||
if policy != nil {
|
|
||||||
return resolveLocalPolicy(opts, policy, "", "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
policy := tufMappings.Policies[opts.PolicyID]
|
|
||||||
if policy != nil {
|
|
||||||
return resolveTUFPolicy(opts, policy, "", "")
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("policy with id %s not found", opts.PolicyID)
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ResolvePolicy(ctx context.Context, detailsResolver oci.ImageDetailsResolver, opts *Options) (*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)
|
|
||||||
}
|
|
||||||
imageName, err = normalizeImageName(imageName)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to parse image name: %w", err)
|
|
||||||
}
|
|
||||||
localMappings, err := config.LoadLocalMappings(opts.LocalPolicyDir)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to load local policy mappings: %w", err)
|
|
||||||
}
|
|
||||||
match, err := findPolicyMatch(imageName, localMappings)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if match.matchType == matchTypePolicy {
|
|
||||||
return resolveLocalPolicy(opts, match.policy, imageName, match.matchedName)
|
|
||||||
}
|
|
||||||
// must check tuf
|
|
||||||
tufMappings, err := config.LoadTUFMappings(opts.TUFClient, opts.LocalTargetsDir)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to load tuf policy mappings as fallback: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// it's a mirror of a tuf policy
|
|
||||||
if match.matchType == matchTypeMatchNoPolicy {
|
|
||||||
for _, mapping := range tufMappings.Policies {
|
|
||||||
if mapping.ID == match.rule.PolicyID {
|
|
||||||
return resolveTUFPolicy(opts, mapping, imageName, match.matchedName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// try to resolve a tuf policy directly
|
|
||||||
match, err = findPolicyMatch(imageName, tufMappings)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if match.matchType == matchTypePolicy {
|
|
||||||
return resolveTUFPolicy(opts, match.policy, imageName, match.matchedName)
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func normalizeImageName(imageName string) (string, error) {
|
|
||||||
named, err := reference.ParseNormalizedNamed(imageName)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("failed to parse image name: %w", err)
|
|
||||||
}
|
|
||||||
return named.Name(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateImageDetailsResolver(imageSource *oci.ImageSpec) (oci.ImageDetailsResolver, error) {
|
func CreateImageDetailsResolver(imageSource *oci.ImageSpec) (oci.ImageDetailsResolver, error) {
|
||||||
switch imageSource.Type {
|
switch imageSource.Type {
|
||||||
case oci.OCI:
|
case oci.OCI:
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import (
|
|||||||
"github.com/docker/attest/pkg/config"
|
"github.com/docker/attest/pkg/config"
|
||||||
"github.com/docker/attest/pkg/oci"
|
"github.com/docker/attest/pkg/oci"
|
||||||
"github.com/docker/attest/pkg/policy"
|
"github.com/docker/attest/pkg/policy"
|
||||||
"github.com/docker/attest/pkg/tuf"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
@@ -47,46 +46,43 @@ func TestRegoEvaluator_Evaluate(t *testing.T) {
|
|||||||
expectSuccess bool
|
expectSuccess bool
|
||||||
isCanonical bool
|
isCanonical bool
|
||||||
resolver attestation.Resolver
|
resolver attestation.Resolver
|
||||||
policy *policy.Options
|
opts *policy.Options
|
||||||
policyID string
|
policyID string
|
||||||
resolveErrorStr string
|
resolveErrorStr string
|
||||||
}{
|
}{
|
||||||
{repo: "testdata/mock-tuf-allow", expectSuccess: true, isCanonical: false, resolver: defaultResolver},
|
{repo: "testdata/policies/allow", expectSuccess: true, resolver: defaultResolver},
|
||||||
{repo: "testdata/mock-tuf-allow", expectSuccess: true, isCanonical: false, resolver: defaultResolver, policyID: "docker-official-images"},
|
{repo: "testdata/policies/allow", expectSuccess: true, resolver: defaultResolver, policyID: "docker-official-images"},
|
||||||
{repo: "testdata/mock-tuf-allow", expectSuccess: false, isCanonical: false, resolver: defaultResolver, policyID: "non-existent-policy-id", resolveErrorStr: resolveErrorStr},
|
{repo: "testdata/policies/allow", resolver: defaultResolver, policyID: "non-existent-policy-id", resolveErrorStr: resolveErrorStr},
|
||||||
{repo: "testdata/mock-tuf-deny", expectSuccess: false, isCanonical: false, resolver: defaultResolver},
|
{repo: "testdata/policies/deny", resolver: defaultResolver},
|
||||||
{repo: "testdata/mock-tuf-verify-sig", expectSuccess: true, isCanonical: false, resolver: defaultResolver},
|
{repo: "testdata/policies/verify-sig", expectSuccess: true, resolver: defaultResolver},
|
||||||
{repo: "testdata/mock-tuf-wrong-key", expectSuccess: false, isCanonical: false, resolver: defaultResolver},
|
{repo: "testdata/policies/wrong-key", resolver: defaultResolver},
|
||||||
{repo: "testdata/mock-tuf-allow-canonical", expectSuccess: true, isCanonical: true, resolver: defaultResolver},
|
{repo: "testdata/policies/allow-canonical", expectSuccess: true, isCanonical: true, resolver: defaultResolver},
|
||||||
{repo: "testdata/mock-tuf-allow-canonical", expectSuccess: false, isCanonical: false, resolver: defaultResolver},
|
{repo: "testdata/policies/allow-canonical", resolver: defaultResolver},
|
||||||
{repo: "testdata/mock-tuf-no-rego", expectSuccess: false, isCanonical: false, resolver: defaultResolver, resolveErrorStr: "no policy file found in policy mapping"},
|
{repo: "testdata/policies/no-rego", resolver: defaultResolver, resolveErrorStr: "no policy file found in policy mapping"},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.repo, func(t *testing.T) {
|
t.Run(tc.repo, func(t *testing.T) {
|
||||||
input := &policy.Input{
|
input := &policy.Input{
|
||||||
Digest: "sha256:test-digest",
|
Digest: "sha256:test-digest",
|
||||||
PURL: "test-purl",
|
PURL: "test-purl",
|
||||||
IsCanonical: tc.isCanonical,
|
}
|
||||||
|
if !tc.isCanonical {
|
||||||
|
input.Tag = "test"
|
||||||
}
|
}
|
||||||
|
|
||||||
tufClient := tuf.NewMockTufClient(tc.repo, test.CreateTempDir(t, "", "tuf-dest"))
|
if tc.opts == nil {
|
||||||
if tc.policy == nil {
|
tc.opts = &policy.Options{
|
||||||
tc.policy = &policy.Options{
|
|
||||||
TUFClient: tufClient,
|
|
||||||
LocalTargetsDir: test.CreateTempDir(t, "", "tuf-targets"),
|
LocalTargetsDir: test.CreateTempDir(t, "", "tuf-targets"),
|
||||||
PolicyID: tc.policyID,
|
PolicyID: tc.policyID,
|
||||||
|
LocalPolicyDir: tc.repo,
|
||||||
|
DisableTUF: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
imageName, err := tc.resolver.ImageName(ctx)
|
imageName, err := tc.resolver.ImageName(ctx)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
platform, err := tc.resolver.ImagePlatform(ctx)
|
resolver := policy.NewResolver(nil, tc.opts)
|
||||||
require.NoError(t, err)
|
policy, err := resolver.ResolvePolicy(ctx, imageName)
|
||||||
src, err := oci.ParseImageSpec(imageName, oci.WithPlatform(platform.String()))
|
|
||||||
require.NoError(t, err)
|
|
||||||
resolver, err := policy.CreateImageDetailsResolver(src)
|
|
||||||
require.NoError(t, err)
|
|
||||||
policy, err := policy.ResolvePolicy(ctx, resolver, tc.policy)
|
|
||||||
if tc.resolveErrorStr != "" {
|
if tc.resolveErrorStr != "" {
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
assert.Contains(t, err.Error(), tc.resolveErrorStr)
|
assert.Contains(t, err.Error(), tc.resolveErrorStr)
|
||||||
@@ -107,7 +103,7 @@ func TestRegoEvaluator_Evaluate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadingMappings(t *testing.T) {
|
func TestLoadingMappings(t *testing.T) {
|
||||||
policyMappings, err := config.LoadLocalMappings(filepath.Join("testdata", "mock-tuf-allow"))
|
policyMappings, err := config.LoadLocalMappings(filepath.Join("testdata", "policies", "allow"))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, len(policyMappings.Rules), 3)
|
assert.Equal(t, len(policyMappings.Rules), 3)
|
||||||
for _, mirror := range policyMappings.Rules {
|
for _, mirror := range policyMappings.Rules {
|
||||||
|
|||||||
194
pkg/policy/resolver.go
Normal file
194
pkg/policy/resolver.go
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
package policy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/distribution/reference"
|
||||||
|
"github.com/docker/attest/internal/util"
|
||||||
|
"github.com/docker/attest/pkg/config"
|
||||||
|
"github.com/docker/attest/pkg/tuf"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Resolver struct {
|
||||||
|
tufClient tuf.Downloader
|
||||||
|
opts *Options
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewResolver(tufClient tuf.Downloader, opts *Options) *Resolver {
|
||||||
|
return &Resolver{
|
||||||
|
tufClient: tufClient,
|
||||||
|
opts: opts,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Resolver) ResolvePolicy(_ context.Context, imageName string) (*Policy, error) {
|
||||||
|
p, err := r.resolvePolicyByID()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to resolve policy by id: %w", err)
|
||||||
|
}
|
||||||
|
if p != nil {
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
imageName, err = normalizeImageName(imageName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse image name: %w", err)
|
||||||
|
}
|
||||||
|
localMappings, err := config.LoadLocalMappings(r.opts.LocalPolicyDir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to load local policy mappings: %w", err)
|
||||||
|
}
|
||||||
|
match, err := findPolicyMatch(imageName, localMappings)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if match.matchType == matchTypePolicy {
|
||||||
|
return r.resolveLocalPolicy(match.policy, imageName, match.matchedName)
|
||||||
|
}
|
||||||
|
if !r.opts.DisableTUF {
|
||||||
|
tufMappings, err := config.LoadTUFMappings(r.tufClient, r.opts.LocalTargetsDir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to load tuf policy mappings as fallback: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// it's a mirror of a tuf policy
|
||||||
|
if match.matchType == matchTypeMatchNoPolicy {
|
||||||
|
for _, mapping := range tufMappings.Policies {
|
||||||
|
if mapping.ID == match.rule.PolicyID {
|
||||||
|
return r.resolveTUFPolicy(mapping, imageName, match.matchedName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// try to resolve a tuf policy directly
|
||||||
|
match, err = findPolicyMatch(imageName, tufMappings)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if match.matchType == matchTypePolicy {
|
||||||
|
return r.resolveTUFPolicy(match.policy, imageName, match.matchedName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Resolver) resolveLocalPolicy(mapping *config.PolicyMapping, imageName string, matchedName string) (*Policy, error) {
|
||||||
|
if r.opts.LocalPolicyDir == "" {
|
||||||
|
return nil, fmt.Errorf("local policy dir not set")
|
||||||
|
}
|
||||||
|
var URI string
|
||||||
|
var digest map[string]string
|
||||||
|
files := make([]*File, 0, len(mapping.Files))
|
||||||
|
for _, f := range mapping.Files {
|
||||||
|
filename := f.Path
|
||||||
|
filePath := path.Join(r.opts.LocalPolicyDir, filename)
|
||||||
|
fileContents, err := os.ReadFile(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read policy file %s: %w", filename, err)
|
||||||
|
}
|
||||||
|
files = append(files, &File{
|
||||||
|
Path: filename,
|
||||||
|
Content: fileContents,
|
||||||
|
})
|
||||||
|
// if the file is a policy file, store the URI and digest
|
||||||
|
if filepath.Ext(filename) == ".rego" {
|
||||||
|
// TODO: support multiple rego files, need some way to identify the main policy file
|
||||||
|
if URI != "" {
|
||||||
|
return nil, fmt.Errorf("multiple policy files found in policy mapping")
|
||||||
|
}
|
||||||
|
URI = filePath
|
||||||
|
digest = map[string]string{"sha256": util.SHA256Hex(fileContents)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if URI == "" {
|
||||||
|
return nil, fmt.Errorf("no policy file found in policy mapping")
|
||||||
|
}
|
||||||
|
policy := &Policy{
|
||||||
|
InputFiles: files,
|
||||||
|
Mapping: mapping,
|
||||||
|
URI: URI,
|
||||||
|
Digest: digest,
|
||||||
|
}
|
||||||
|
if imageName != matchedName {
|
||||||
|
policy.ResolvedName = matchedName
|
||||||
|
}
|
||||||
|
return policy, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Resolver) resolveTUFPolicy(mapping *config.PolicyMapping, imageName string, matchedName string) (*Policy, error) {
|
||||||
|
var URI string
|
||||||
|
var digest map[string]string
|
||||||
|
files := make([]*File, 0, len(mapping.Files))
|
||||||
|
for _, f := range mapping.Files {
|
||||||
|
filename := f.Path
|
||||||
|
file, err := r.tufClient.DownloadTarget(filename, filepath.Join(r.opts.LocalTargetsDir, filename))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to download policy file %s: %w", filename, err)
|
||||||
|
}
|
||||||
|
files = append(files, &File{
|
||||||
|
Path: filename,
|
||||||
|
Content: file.Data,
|
||||||
|
})
|
||||||
|
// if the file is a policy file, store the URI and digest
|
||||||
|
if filepath.Ext(filename) == ".rego" {
|
||||||
|
// TODO: support multiple rego files, need some way to identify the main policy file
|
||||||
|
if URI != "" {
|
||||||
|
return nil, fmt.Errorf("multiple policy files found in policy mapping")
|
||||||
|
}
|
||||||
|
URI = file.TargetURI
|
||||||
|
digest = map[string]string{"sha256": file.Digest}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if URI == "" {
|
||||||
|
return nil, fmt.Errorf("no policy file found in policy mapping")
|
||||||
|
}
|
||||||
|
policy := &Policy{
|
||||||
|
InputFiles: files,
|
||||||
|
Mapping: mapping,
|
||||||
|
URI: URI,
|
||||||
|
Digest: digest,
|
||||||
|
}
|
||||||
|
if imageName != matchedName {
|
||||||
|
policy.ResolvedName = matchedName
|
||||||
|
}
|
||||||
|
return policy, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Resolver) resolvePolicyByID() (*Policy, error) {
|
||||||
|
if r.opts.PolicyID != "" {
|
||||||
|
localMappings, err := config.LoadLocalMappings(r.opts.LocalPolicyDir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to load local policy mappings: %w", err)
|
||||||
|
}
|
||||||
|
if localMappings != nil {
|
||||||
|
policy := localMappings.Policies[r.opts.PolicyID]
|
||||||
|
if policy != nil {
|
||||||
|
return r.resolveLocalPolicy(policy, "", "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !r.opts.DisableTUF {
|
||||||
|
tufMappings, err := config.LoadTUFMappings(r.tufClient, r.opts.LocalTargetsDir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to load tuf policy mappings by id: %w", err)
|
||||||
|
}
|
||||||
|
policy := tufMappings.Policies[r.opts.PolicyID]
|
||||||
|
if policy != nil {
|
||||||
|
return r.resolveTUFPolicy(policy, "", "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("policy with id %s not found", r.opts.PolicyID)
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeImageName(imageName string) (string, error) {
|
||||||
|
named, err := reference.ParseNormalizedNamed(imageName)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to parse image name: %w", err)
|
||||||
|
}
|
||||||
|
return named.Name(), nil
|
||||||
|
}
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
package attest
|
|
||||||
|
|
||||||
import rego.v1
|
|
||||||
|
|
||||||
result := {
|
|
||||||
"success": input.isCanonical,
|
|
||||||
}
|
|
||||||
11
pkg/policy/testdata/policies/allow-canonical/doi/policy.rego
vendored
Normal file
11
pkg/policy/testdata/policies/allow-canonical/doi/policy.rego
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package attest
|
||||||
|
|
||||||
|
import rego.v1
|
||||||
|
|
||||||
|
default canonical = false
|
||||||
|
|
||||||
|
canonical if {
|
||||||
|
not input.tag
|
||||||
|
}
|
||||||
|
|
||||||
|
result := {"success": canonical}
|
||||||
@@ -27,7 +27,8 @@ type Result struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Options struct {
|
type Options struct {
|
||||||
TUFClient tuf.Downloader
|
TUFClientOptions *tuf.ClientOptions
|
||||||
|
DisableTUF bool
|
||||||
LocalTargetsDir string
|
LocalTargetsDir string
|
||||||
LocalPolicyDir string
|
LocalPolicyDir string
|
||||||
PolicyID string
|
PolicyID string
|
||||||
@@ -45,9 +46,13 @@ type Policy struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Input struct {
|
type Input struct {
|
||||||
Digest string `json:"digest"`
|
Digest string `json:"digest"`
|
||||||
PURL string `json:"purl"`
|
PURL string `json:"purl"`
|
||||||
IsCanonical bool `json:"isCanonical"`
|
Tag string `json:"tag,omitempty"`
|
||||||
|
Domain string `json:"domain"`
|
||||||
|
NormalizedName string `json:"normalized_name"`
|
||||||
|
FamiliarName string `json:"familiar_name"`
|
||||||
|
Platform string `json:"platform"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type File struct {
|
type File struct {
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/docker/attest/internal/embed"
|
|
||||||
"github.com/docker/attest/pkg/tuf"
|
"github.com/docker/attest/pkg/tuf"
|
||||||
"github.com/theupdateframework/go-tuf/v2/metadata"
|
"github.com/theupdateframework/go-tuf/v2/metadata"
|
||||||
)
|
)
|
||||||
@@ -21,7 +20,7 @@ func ExampleNewClient_registry() {
|
|||||||
metadataURI := "registry-1.docker.io/docker/tuf-metadata:latest"
|
metadataURI := "registry-1.docker.io/docker/tuf-metadata:latest"
|
||||||
targetsURI := "registry-1.docker.io/docker/tuf-targets"
|
targetsURI := "registry-1.docker.io/docker/tuf-targets"
|
||||||
|
|
||||||
registryClient, err := tuf.NewClient(embed.RootStaging.Data, tufOutputPath, metadataURI, targetsURI, tuf.NewMockVersionChecker())
|
registryClient, err := tuf.NewClient(&tuf.ClientOptions{tuf.DockerTUFRootStaging.Data, tufOutputPath, metadataURI, targetsURI, tuf.NewMockVersionChecker()})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,67 +1,5 @@
|
|||||||
package tuf
|
package tuf
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/docker/attest/internal/util"
|
|
||||||
)
|
|
||||||
|
|
||||||
type MockTufClient struct {
|
|
||||||
srcPath string
|
|
||||||
dstPath string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewMockTufClient(srcPath string, dstPath string) *MockTufClient {
|
|
||||||
if srcPath == "" {
|
|
||||||
panic("srcPath must be set")
|
|
||||||
}
|
|
||||||
if dstPath == "" {
|
|
||||||
panic("dstPath must be set")
|
|
||||||
}
|
|
||||||
return &MockTufClient{
|
|
||||||
srcPath: srcPath,
|
|
||||||
dstPath: dstPath,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dc *MockTufClient) DownloadTarget(target string, filePath string) (file *TargetFile, err error) {
|
|
||||||
targetPath := filepath.Join(dc.srcPath, target)
|
|
||||||
src, err := os.Open(targetPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer src.Close()
|
|
||||||
|
|
||||||
var dstFilePath string
|
|
||||||
if filePath == "" {
|
|
||||||
dstFilePath = filepath.Join(dc.dstPath, filepath.FromSlash(target))
|
|
||||||
} else {
|
|
||||||
dstFilePath = filePath
|
|
||||||
}
|
|
||||||
|
|
||||||
err = os.MkdirAll(filepath.Dir(dstFilePath), os.ModePerm)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
dst, err := os.Create(dstFilePath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer dst.Close()
|
|
||||||
|
|
||||||
// reading from tee will read from src and write to dst at the same time
|
|
||||||
tee := io.TeeReader(src, dst)
|
|
||||||
|
|
||||||
b, err := io.ReadAll(tee)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &TargetFile{ActualFilePath: dstFilePath, TargetURI: targetPath, Data: b, Digest: util.SHA256Hex(b)}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type MockVersionChecker struct {
|
type MockVersionChecker struct {
|
||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/attest/internal/embed"
|
|
||||||
"github.com/docker/attest/internal/util"
|
"github.com/docker/attest/internal/util"
|
||||||
"github.com/docker/attest/pkg/oci"
|
"github.com/docker/attest/pkg/oci"
|
||||||
"github.com/google/go-containerregistry/pkg/crane"
|
"github.com/google/go-containerregistry/pkg/crane"
|
||||||
@@ -56,7 +55,7 @@ func TestRegistryFetcher(t *testing.T) {
|
|||||||
delegatedDir := CreateTempDir(t, dir, delegatedRole)
|
delegatedDir := CreateTempDir(t, dir, delegatedRole)
|
||||||
delegatedTargetFile := fmt.Sprintf("%s/%s", delegatedRole, targetFile)
|
delegatedTargetFile := fmt.Sprintf("%s/%s", delegatedRole, targetFile)
|
||||||
|
|
||||||
cfg, err := config.New(metadataRepo, embed.RootDev.Data)
|
cfg, err := config.New(metadataRepo, DockerTUFRootDev.Data)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
cfg.Fetcher = NewRegistryFetcher(metadataRepo, metadataImgTag, targetsRepo)
|
cfg.Fetcher = NewRegistryFetcher(metadataRepo, metadataImgTag, targetsRepo)
|
||||||
|
|||||||
@@ -35,6 +35,11 @@ var (
|
|||||||
DockerTUFRootDefault = embed.RootDefault
|
DockerTUFRootDefault = embed.RootDefault
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultMetadataSource = "docker/tuf-metadata:latest"
|
||||||
|
defaultTargetsSource = "docker/tuf-targets"
|
||||||
|
)
|
||||||
|
|
||||||
type Downloader interface {
|
type Downloader interface {
|
||||||
DownloadTarget(target, filePath string) (file *TargetFile, err error)
|
DownloadTarget(target, filePath string) (file *TargetFile, err error)
|
||||||
}
|
}
|
||||||
@@ -51,19 +56,37 @@ type TargetFile struct {
|
|||||||
Data []byte
|
Data []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ClientOptions struct {
|
||||||
|
InitialRoot []byte
|
||||||
|
Path string
|
||||||
|
MetadataSource string
|
||||||
|
TargetsSource string
|
||||||
|
VersionChecker VersionChecker
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDockerDefaultClientOptions(tufPath string) *ClientOptions {
|
||||||
|
return &ClientOptions{
|
||||||
|
InitialRoot: DockerTUFRootDefault.Data,
|
||||||
|
Path: tufPath,
|
||||||
|
MetadataSource: defaultMetadataSource,
|
||||||
|
TargetsSource: defaultTargetsSource,
|
||||||
|
VersionChecker: NewDefaultVersionChecker(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// NewClient creates a new TUF client.
|
// NewClient creates a new TUF client.
|
||||||
func NewClient(initialRoot []byte, tufPath, metadataSource, targetsSource string, versionChecker VersionChecker) (*Client, error) {
|
func NewClient(opts *ClientOptions) (*Client, error) {
|
||||||
var tufSource Source
|
var tufSource Source
|
||||||
if strings.HasPrefix(metadataSource, "https://") || strings.HasPrefix(metadataSource, "http://") {
|
if strings.HasPrefix(opts.MetadataSource, "https://") || strings.HasPrefix(opts.MetadataSource, "http://") {
|
||||||
tufSource = HTTPSource
|
tufSource = HTTPSource
|
||||||
} else {
|
} else {
|
||||||
tufSource = OCISource
|
tufSource = OCISource
|
||||||
}
|
}
|
||||||
|
|
||||||
tufRootDigest := util.SHA256Hex(initialRoot)
|
tufRootDigest := util.SHA256Hex(opts.InitialRoot)
|
||||||
|
|
||||||
// create a directory for each initial root.json
|
// create a directory for each initial root.json
|
||||||
metadataPath := filepath.Join(tufPath, tufRootDigest)
|
metadataPath := filepath.Join(opts.Path, tufRootDigest)
|
||||||
err := os.MkdirAll(metadataPath, os.ModePerm)
|
err := os.MkdirAll(metadataPath, os.ModePerm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create directory '%s': %w", metadataPath, err)
|
return nil, fmt.Errorf("failed to create directory '%s': %w", metadataPath, err)
|
||||||
@@ -76,29 +99,29 @@ func NewClient(initialRoot []byte, tufPath, metadataSource, targetsSource string
|
|||||||
return nil, fmt.Errorf("failed to read root.json: %w", err)
|
return nil, fmt.Errorf("failed to read root.json: %w", err)
|
||||||
}
|
}
|
||||||
// write the root.json file to the metadata directory
|
// write the root.json file to the metadata directory
|
||||||
err = os.WriteFile(rootFile, initialRoot, 0o666) // #nosec G306
|
err = os.WriteFile(rootFile, opts.InitialRoot, 0o666) // #nosec G306
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Failed to write root.json %w", err)
|
return nil, fmt.Errorf("Failed to write root.json %w", err)
|
||||||
}
|
}
|
||||||
rootBytes = initialRoot
|
rootBytes = opts.InitialRoot
|
||||||
}
|
}
|
||||||
|
|
||||||
// create updater configuration
|
// create updater configuration
|
||||||
cfg, err := config.New(metadataSource, rootBytes) // default config
|
cfg, err := config.New(opts.MetadataSource, rootBytes) // default config
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create TUF updater configuration: %w", err)
|
return nil, fmt.Errorf("failed to create TUF updater configuration: %w", err)
|
||||||
}
|
}
|
||||||
cfg.LocalMetadataDir = metadataPath
|
cfg.LocalMetadataDir = metadataPath
|
||||||
cfg.LocalTargetsDir = filepath.Join(metadataPath, "download")
|
cfg.LocalTargetsDir = filepath.Join(metadataPath, "download")
|
||||||
cfg.RemoteTargetsURL = targetsSource
|
cfg.RemoteTargetsURL = opts.TargetsSource
|
||||||
|
|
||||||
if tufSource == OCISource {
|
if tufSource == OCISource {
|
||||||
metadataRepo, metadataTag, found := strings.Cut(metadataSource, ":")
|
metadataRepo, metadataTag, found := strings.Cut(opts.MetadataSource, ":")
|
||||||
if !found {
|
if !found {
|
||||||
fmt.Printf("metadata tag not found in URL, using latest\n")
|
fmt.Printf("metadata tag not found in URL, using latest\n")
|
||||||
metadataTag = LatestTag
|
metadataTag = LatestTag
|
||||||
}
|
}
|
||||||
cfg.Fetcher = NewRegistryFetcher(metadataRepo, metadataTag, targetsSource)
|
cfg.Fetcher = NewRegistryFetcher(metadataRepo, metadataTag, opts.TargetsSource)
|
||||||
}
|
}
|
||||||
|
|
||||||
// create a new Updater instance
|
// create a new Updater instance
|
||||||
@@ -118,7 +141,7 @@ func NewClient(initialRoot []byte, tufPath, metadataSource, targetsSource string
|
|||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
}
|
}
|
||||||
|
|
||||||
err = versionChecker.CheckVersion(client)
|
err = opts.VersionChecker.CheckVersion(client)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/attest/internal/embed"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/theupdateframework/go-tuf/v2/metadata"
|
"github.com/theupdateframework/go-tuf/v2/metadata"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -65,18 +65,18 @@ func TestRootInit(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
_, err := NewClient(embed.RootDev.Data, tufPath, tc.metadataSource, tc.targetsSource, alwaysGoodVersionChecker)
|
_, err := NewClient(&ClientOptions{DockerTUFRootDev.Data, tufPath, tc.metadataSource, tc.targetsSource, alwaysGoodVersionChecker})
|
||||||
assert.NoErrorf(t, err, "Failed to create TUF client: %v", err)
|
assert.NoErrorf(t, err, "Failed to create TUF client: %v", err)
|
||||||
|
|
||||||
// recreation should work with same root
|
// recreation should work with same root
|
||||||
_, err = NewClient(embed.RootDev.Data, tufPath, tc.metadataSource, tc.targetsSource, alwaysGoodVersionChecker)
|
_, err = NewClient(&ClientOptions{DockerTUFRootDev.Data, tufPath, tc.metadataSource, tc.targetsSource, alwaysGoodVersionChecker})
|
||||||
assert.NoErrorf(t, err, "Failed to recreate TUF client: %v", err)
|
assert.NoErrorf(t, err, "Failed to recreate TUF client: %v", err)
|
||||||
|
|
||||||
_, err = NewClient([]byte("broken"), tufPath, tc.metadataSource, tc.targetsSource, alwaysGoodVersionChecker)
|
_, err = NewClient(&ClientOptions{[]byte("broken"), tufPath, tc.metadataSource, tc.targetsSource, alwaysGoodVersionChecker})
|
||||||
assert.Errorf(t, err, "Expected error recreating TUF client with broken root: %v", err)
|
assert.Errorf(t, err, "Expected error recreating TUF client with broken root: %v", err)
|
||||||
|
|
||||||
_, err = NewClient(embed.RootDev.Data, tufPath, tc.metadataSource, tc.targetsSource, alwaysBadVersionChecker)
|
_, err = NewClient(&ClientOptions{DockerTUFRootDev.Data, tufPath, tc.metadataSource, tc.targetsSource, alwaysBadVersionChecker})
|
||||||
assert.Errorf(t, err, "Expected error creating TUF client with bad attest version: %v", err)
|
assert.Errorf(t, err, "Expected error recreating TUF client with bad version checker")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,11 +108,13 @@ func TestDownloadTarget(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{"http", server.URL + "/metadata", server.URL + "/targets"},
|
{"http", server.URL + "/metadata", server.URL + "/targets"},
|
||||||
{"oci", regAddr.Host + "/tuf-metadata:latest", regAddr.Host + "/tuf-targets"},
|
{"oci", regAddr.Host + "/tuf-metadata:latest", regAddr.Host + "/tuf-targets"},
|
||||||
|
{"http, download before init", server.URL + "/metadata", server.URL + "/targets"},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
tufClient, err := NewClient(embed.RootDev.Data, tufPath, tc.metadataSource, tc.targetsSource, alwaysGoodVersionChecker)
|
tufClient, err := NewClient(&ClientOptions{DockerTUFRootDev.Data, tufPath, tc.metadataSource, tc.targetsSource, alwaysGoodVersionChecker})
|
||||||
assert.NoErrorf(t, err, "Failed to create TUF client: %v", err)
|
require.NoErrorf(t, err, "Failed to create TUF client: %v", err)
|
||||||
|
require.NotNil(t, tufClient.updater, "Failed to create updater")
|
||||||
|
|
||||||
// get trusted tuf metadata
|
// get trusted tuf metadata
|
||||||
trustedMetadata := tufClient.updater.GetTrustedMetadataSet()
|
trustedMetadata := tufClient.updater.GetTrustedMetadataSet()
|
||||||
|
|||||||
43
test/testdata/local-policy-inputs/doi/policy.rego
vendored
Normal file
43
test/testdata/local-policy-inputs/doi/policy.rego
vendored
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
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"),
|
||||||
|
})
|
||||||
|
|
||||||
|
success if {
|
||||||
|
input.domain == "docker.io"
|
||||||
|
input.familiar_name == "test-image"
|
||||||
|
input.normalized_name == "library/test-image"
|
||||||
|
input.platform == "linux/amd64"
|
||||||
|
input.tag == "test"
|
||||||
|
}
|
||||||
|
|
||||||
|
result := {
|
||||||
|
"success": success,
|
||||||
|
"violations": set(),
|
||||||
|
"attestations": set(),
|
||||||
|
"summary": {
|
||||||
|
"subjects": set(),
|
||||||
|
"slsa_level": "SLSA_BUILD_LEVEL_3",
|
||||||
|
"verifier": "docker-official-images",
|
||||||
|
"policy_uri": "https://docker.com/official/policy/v0.1",
|
||||||
|
},
|
||||||
|
}
|
||||||
18
test/testdata/local-policy-inputs/mapping.yaml
vendored
Normal file
18
test/testdata/local-policy-inputs/mapping.yaml
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
version: v1
|
||||||
|
kind: policy-mapping
|
||||||
|
policies:
|
||||||
|
- id: docker-official-images
|
||||||
|
description: Docker Official Images
|
||||||
|
files:
|
||||||
|
- path: doi/policy.rego
|
||||||
|
rules:
|
||||||
|
- pattern: "^docker[.]io/library/(.*)$"
|
||||||
|
policy-id: docker-official-images
|
||||||
|
- pattern: "repo$"
|
||||||
|
policy-id: docker-official-images
|
||||||
|
- pattern: "test-image$"
|
||||||
|
policy-id: docker-official-images
|
||||||
|
- pattern: "image-signer-verifier-test$"
|
||||||
|
policy-id: docker-official-images
|
||||||
|
- pattern: "library/(.*)$"
|
||||||
|
rewrite: docker.io/library/$1
|
||||||
@@ -38,7 +38,7 @@ subjects contains subject if {
|
|||||||
}
|
}
|
||||||
|
|
||||||
success if {
|
success if {
|
||||||
print("input:",input)
|
# print("input:",input)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
4
test/testdata/local-policy-no-policies/mapping.yaml
vendored
Normal file
4
test/testdata/local-policy-no-policies/mapping.yaml
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
version: v1
|
||||||
|
kind: policy-mapping
|
||||||
|
policies:
|
||||||
|
rules:
|
||||||
Reference in New Issue
Block a user