10 Commits

Author SHA1 Message Date
Jonny Stoten
aed959f858 fix: use a client pointing at Docker's TUF by default (#104)
`policy.Options` now contains the arguments to `tuf.Client`'s constructor rather than an actual Client. If these arguments are not provided, defaults pointing at Docker's TUF repo will be used. An actual TUF client can be passed in on the context (which is useful for testing). If this is not provided `attest.Verify` will create a TUF client using the options on `policy.Options`.

---------

Co-authored-by: Joel Kamp <joel.kamp@docker.com>
2024-08-23 09:33:30 +01:00
James Carnegie
802725caf0 feat: add purl details to policy inputs (#129) 2024-08-21 12:01:11 -05:00
Joel Kamp
9c3f267870 Merge pull request #126 from docker/dependabot/go_modules/go_modules-56f2e24de8
feat(deps): bump github.com/docker/docker from 27.1.0+incompatible to 27.1.1+incompatible in the go_modules group
2024-08-16 09:10:37 -05:00
Joel Kamp
6cc9191e1e Merge branch 'main' into dependabot/go_modules/go_modules-56f2e24de8 2024-08-16 09:06:27 -05:00
Joel Kamp
7ce2817111 Merge pull request #123 from docker/dependabot/go_modules/google.golang.org/api-0.192.0
feat(deps): bump google.golang.org/api from 0.191.0 to 0.192.0
2024-08-16 09:06:00 -05:00
dependabot[bot]
a60aab9338 feat(deps): bump github.com/docker/docker in the go_modules group
Bumps the go_modules group with 1 update: [github.com/docker/docker](https://github.com/docker/docker).


Updates `github.com/docker/docker` from 27.1.0+incompatible to 27.1.1+incompatible
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v27.1.0...v27.1.1)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  dependency-type: indirect
  dependency-group: go_modules
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-16 14:05:57 +00:00
Joel Kamp
2ef3a158ae Merge branch 'main' into dependabot/go_modules/google.golang.org/api-0.192.0 2024-08-16 09:04:20 -05:00
Joel Kamp
4f163f4283 Merge pull request #125 from docker/dependabot/go_modules/github.com/aws/aws-sdk-go-v2/config-1.27.28
feat(deps): bump github.com/aws/aws-sdk-go-v2/config from 1.27.27 to 1.27.28
2024-08-16 09:03:58 -05:00
dependabot[bot]
74e8d8beb3 feat(deps): bump github.com/aws/aws-sdk-go-v2/config
Bumps [github.com/aws/aws-sdk-go-v2/config](https://github.com/aws/aws-sdk-go-v2) from 1.27.27 to 1.27.28.
- [Release notes](https://github.com/aws/aws-sdk-go-v2/releases)
- [Commits](https://github.com/aws/aws-sdk-go-v2/compare/config/v1.27.27...config/v1.27.28)

---
updated-dependencies:
- dependency-name: github.com/aws/aws-sdk-go-v2/config
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-16 08:19:46 +00:00
dependabot[bot]
881e9d9582 feat(deps): bump google.golang.org/api from 0.191.0 to 0.192.0
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.191.0 to 0.192.0.
- [Release notes](https://github.com/googleapis/google-api-go-client/releases)
- [Changelog](https://github.com/googleapis/google-api-go-client/blob/main/CHANGES.md)
- [Commits](https://github.com/googleapis/google-api-go-client/compare/v0.191.0...v0.192.0)

---
updated-dependencies:
- dependency-name: google.golang.org/api
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-14 08:58:17 +00:00
27 changed files with 425 additions and 176 deletions

View File

@@ -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 ./...

View File

@@ -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

32
go.mod
View File

@@ -4,7 +4,7 @@ go 1.22.5
require ( require (
github.com/Masterminds/semver/v3 v3.2.1 github.com/Masterminds/semver/v3 v3.2.1
github.com/aws/aws-sdk-go-v2/config v1.27.27 github.com/aws/aws-sdk-go-v2/config v1.27.28
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
@@ -24,7 +24,7 @@ require (
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.32.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.192.0
sigs.k8s.io/yaml v1.4.0 sigs.k8s.io/yaml v1.4.0
) )
@@ -33,7 +33,7 @@ replace github.com/google/go-containerregistry => github.com/docker/go-container
require ( require (
cloud.google.com/go v0.115.0 // indirect cloud.google.com/go v0.115.0 // indirect
cloud.google.com/go/auth v0.7.3 // indirect cloud.google.com/go/auth v0.8.1 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.3 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.3 // 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
@@ -47,21 +47,21 @@ require (
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.28 // 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.4 // 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 +77,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

64
go.sum
View File

@@ -1,8 +1,8 @@
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.0 h1:CnFSK6Xo3lDYRoBKEcAtia6VSC837/ZkJuRduSFnr14=
cloud.google.com/go v0.115.0/go.mod h1:8jIM5vVgoAEoiVxQ/O4BFTfHqulPZgs/ufEzMcFMdWU= cloud.google.com/go v0.115.0/go.mod h1:8jIM5vVgoAEoiVxQ/O4BFTfHqulPZgs/ufEzMcFMdWU=
cloud.google.com/go/auth v0.7.3 h1:98Vr+5jMaCZ5NZk6e/uBgf60phTk/XN84r8QEWB9yjY= cloud.google.com/go/auth v0.8.1 h1:QZW9FjC5lZzN864p13YxvAtGUlQ+KgRL+8Sg45Z6vxo=
cloud.google.com/go/auth v0.7.3/go.mod h1:HJtWUx1P5eqjy/f6Iq5KeytNpbAcGolPhOgyop2LlzA= cloud.google.com/go/auth v0.8.1/go.mod h1:qGVp/Y3kDRSDZ5gFD/XPUfYQ9xW1iI7q8RIRoCyBbJc=
cloud.google.com/go/auth/oauth2adapt v0.2.3 h1:MlxF+Pd3OmSudg/b1yZ5lJwoXCEaeedAguodky1PcKI= cloud.google.com/go/auth/oauth2adapt v0.2.3 h1:MlxF+Pd3OmSudg/b1yZ5lJwoXCEaeedAguodky1PcKI=
cloud.google.com/go/auth/oauth2adapt v0.2.3/go.mod h1:tMQXOfZzFuNuUxOypHlQEXgdfX5cuhwU+ffUuXRJE8I= cloud.google.com/go/auth/oauth2adapt v0.2.3/go.mod h1:tMQXOfZzFuNuUxOypHlQEXgdfX5cuhwU+ffUuXRJE8I=
cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY= cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY=
@@ -104,38 +104,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.28 h1:OTxWGW/91C61QlneCtnD62NLb4W616/NM1jA8LhJqbg=
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.28/go.mod h1:uzVRVtJSU5EFv6Fu82AoVFKozJi2ZCY6WRCXj06rbvs=
github.com/aws/aws-sdk-go-v2/credentials v1.17.27 h1:2raNba6gr2IfA0eqqiP2XiQ0UVOpGPgDSi0I9iAP+UI= github.com/aws/aws-sdk-go-v2/credentials v1.17.28 h1:m8+AHY/ND8CMHJnPoH7PJIRakWGa4gbfbxuY9TGTUXM=
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.28/go.mod h1:6TF7dSc78ehD1SL6KpRIPKMA1GyyWflIkjqg+qmf4+c=
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.4 h1:iAckBT2OeEK/kBDyN/jDtpEExhjeeA/Im2q4X0rJZT8=
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.4/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 +222,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=
@@ -809,8 +809,8 @@ 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.192.0 h1:PljqpNAfZaaSpS+TnANfnNAXKdzHM/B9bKhwRlo7JP0=
google.golang.org/api v0.191.0/go.mod h1:tD5dsFGxFza0hnQveGfVk9QQYKcfp+VzgRqyXFxE0+E= google.golang.org/api v0.192.0/go.mod h1:9VcphjvAxPKLmSxVSzPlSRXy/5ARMEw5bf58WoVXafQ=
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=

View File

@@ -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,10 @@ 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
} }
src, err := oci.ParseImageSpec(image, oci.WithPlatform(platform)) src, err := oci.ParseImageSpec(image, oci.WithPlatform(platform))

View File

@@ -8,6 +8,7 @@ import (
"github.com/docker/attest/pkg/attestation" "github.com/docker/attest/pkg/attestation"
"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"
v02 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.2" v02 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.2"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -20,11 +21,14 @@ 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"
) )
func TestSignVerifyOCILayout(t *testing.T) { func TestSignVerifyOCILayout(t *testing.T) {
ctx, signer := test.Setup(t) ctx, signer := test.Setup(t)
ctx = tuf.WithDownloader(ctx, tuf.NewMockTufClient(EmptyPolicyDir, test.CreateTempDir(t, "", "tuf-dest")))
testCases := []struct { testCases := []struct {
name string name string

View File

@@ -3,6 +3,8 @@ package attest
import ( import (
"context" "context"
"fmt" "fmt"
"os"
"path/filepath"
"strings" "strings"
"time" "time"
@@ -11,6 +13,7 @@ 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"
) )
@@ -20,13 +23,20 @@ func Verify(ctx context.Context, src *oci.ImageSpec, opts *policy.Options) (resu
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 == "" { err = populateDefaultOptions(opts)
opts.AttestationStyle = config.AttestationStyleReferrers if err != nil {
return nil, err
} }
if opts.ReferrersRepo != "" && opts.AttestationStyle != config.AttestationStyleReferrers {
return nil, fmt.Errorf("referrers repo specified but attestation source not set to referrers") tufClient, ok := tuf.GetDownloader(ctx)
if !ok {
tufClient, err = tuf.NewClient(opts.TUFClientOptions)
if err != nil {
return nil, fmt.Errorf("failed to create TUF client: %w", err)
}
} }
pctx, err := policy.ResolvePolicy(ctx, detailsResolver, opts)
pctx, err := policy.ResolvePolicy(ctx, tufClient, 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)
} }
@@ -60,6 +70,36 @@ func Verify(ctx context.Context, src *oci.ImageSpec, opts *policy.Options) (resu
return result, nil return result, nil
} }
func populateDefaultOptions(opts *policy.Options) (err error) {
if opts.LocalTargetsDir == "" {
opts.LocalTargetsDir, err = defaultLocalTargetsDir()
if err != nil {
return err
}
}
if opts.TUFClientOptions == nil {
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 +180,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)

View File

@@ -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"
@@ -68,6 +70,7 @@ func TestVerifyAttestations(t *testing.T) {
func TestVSA(t *testing.T) { func TestVSA(t *testing.T) {
ctx, signer := test.Setup(t) ctx, signer := test.Setup(t)
ctx = policy.WithPolicyEvaluator(ctx, policy.NewRegoEvaluator(true)) ctx = policy.WithPolicyEvaluator(ctx, policy.NewRegoEvaluator(true))
ctx = tuf.WithDownloader(ctx, tuf.NewMockTufClient(EmptyPolicyDir, test.CreateTempDir(t, "", "tuf-dest")))
// setup an image with signed attestations // setup an image with signed attestations
outputLayout := test.CreateTempDir(t, "", TestTempDir) outputLayout := test.CreateTempDir(t, "", TestTempDir)
@@ -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)
@@ -120,6 +123,7 @@ func TestVSA(t *testing.T) {
func TestVerificationFailure(t *testing.T) { func TestVerificationFailure(t *testing.T) {
ctx, signer := test.Setup(t) ctx, signer := test.Setup(t)
ctx = policy.WithPolicyEvaluator(ctx, policy.NewRegoEvaluator(true)) ctx = policy.WithPolicyEvaluator(ctx, policy.NewRegoEvaluator(true))
ctx = tuf.WithDownloader(ctx, tuf.NewMockTufClient(EmptyPolicyDir, test.CreateTempDir(t, "", "tuf-dest")))
// setup an image with signed attestations // setup an image with signed attestations
outputLayout := test.CreateTempDir(t, "", TestTempDir) outputLayout := test.CreateTempDir(t, "", TestTempDir)
@@ -172,21 +176,24 @@ func TestVerificationFailure(t *testing.T) {
func TestSignVerify(t *testing.T) { func TestSignVerify(t *testing.T) {
ctx, signer := test.Setup(t) ctx, signer := test.Setup(t)
ctx = policy.WithPolicyEvaluator(ctx, policy.NewRegoEvaluator(true)) ctx = policy.WithPolicyEvaluator(ctx, policy.NewRegoEvaluator(true))
ctx = tuf.WithDownloader(ctx, tuf.NewMockTufClient(EmptyPolicyDir, test.CreateTempDir(t, "", "tuf-dest")))
// setup an image with signed attestations // setup an image with signed attestations
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)
@@ -218,17 +225,84 @@ func TestSignVerify(t *testing.T) {
LocalPolicyDir: tc.policyDir, LocalPolicyDir: tc.policyDir,
} }
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
}{
{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"},
}
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,
}
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)
}
})
}
}

View File

@@ -13,6 +13,7 @@ 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/google/go-containerregistry/pkg/name" "github.com/google/go-containerregistry/pkg/name"
"github.com/google/go-containerregistry/pkg/registry" "github.com/google/go-containerregistry/pkg/registry"
"github.com/google/go-containerregistry/pkg/v1/remote" "github.com/google/go-containerregistry/pkg/v1/remote"
@@ -27,12 +28,14 @@ var (
LocalPolicyAttached = filepath.Join("..", "..", "test", "testdata", "local-policy-attached") LocalPolicyAttached = filepath.Join("..", "..", "test", "testdata", "local-policy-attached")
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")
EmptyTUFDir = filepath.Join("..", "..", "test", "testdata", "local-policy-no-policies")
TestTempDir = "attest-sign-test" TestTempDir = "attest-sign-test"
) )
func TestAttestationReferenceTypes(t *testing.T) { func TestAttestationReferenceTypes(t *testing.T) {
ctx, signer := test.Setup(t) ctx, signer := test.Setup(t)
ctx = policy.WithPolicyEvaluator(ctx, policy.NewRegoEvaluator(true)) ctx = policy.WithPolicyEvaluator(ctx, policy.NewRegoEvaluator(true))
ctx = tuf.WithDownloader(ctx, tuf.NewMockTufClient(EmptyTUFDir, test.CreateTempDir(t, "", "tuf-dest")))
platforms := []string{"linux/amd64", "linux/arm64"} platforms := []string{"linux/amd64", "linux/arm64"}
for _, tc := range []struct { for _, tc := range []struct {
name string name string
@@ -182,6 +185,7 @@ func TestAttestationReferenceTypes(t *testing.T) {
func TestReferencesInDifferentRepo(t *testing.T) { func TestReferencesInDifferentRepo(t *testing.T) {
ctx, signer := test.Setup(t) ctx, signer := test.Setup(t)
ctx = tuf.WithDownloader(ctx, tuf.NewMockTufClient(EmptyTUFDir, test.CreateTempDir(t, "", "tuf-dest")))
repoName := "repo" repoName := "repo"
for _, tc := range []struct { for _, tc := range []struct {
name string name string

View File

@@ -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)
} }

View File

@@ -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()

View File

@@ -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)
} }

View File

@@ -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()

View File

@@ -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 {

View File

@@ -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)

View File

@@ -12,6 +12,7 @@ import (
"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/tuf"
) )
func resolveLocalPolicy(opts *Options, mapping *config.PolicyMapping, imageName string, matchedName string) (*Policy, error) { func resolveLocalPolicy(opts *Options, mapping *config.PolicyMapping, imageName string, matchedName string) (*Policy, error) {
@@ -57,13 +58,13 @@ func resolveLocalPolicy(opts *Options, mapping *config.PolicyMapping, imageName
return policy, nil return policy, nil
} }
func resolveTUFPolicy(opts *Options, mapping *config.PolicyMapping, imageName string, matchedName string) (*Policy, error) { func resolveTUFPolicy(opts *Options, tufClient tuf.Downloader, mapping *config.PolicyMapping, imageName string, matchedName string) (*Policy, error) {
var URI string var URI string
var digest map[string]string var digest map[string]string
files := make([]*File, 0, len(mapping.Files)) files := make([]*File, 0, len(mapping.Files))
for _, f := range mapping.Files { for _, f := range mapping.Files {
filename := f.Path filename := f.Path
file, err := opts.TUFClient.DownloadTarget(filename, filepath.Join(opts.LocalTargetsDir, filename)) file, err := tufClient.DownloadTarget(filename, filepath.Join(opts.LocalTargetsDir, filename))
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to download policy file %s: %w", filename, err) return nil, fmt.Errorf("failed to download policy file %s: %w", filename, err)
} }
@@ -154,7 +155,7 @@ func findPolicyMatchImpl(imageName string, mappings *config.PolicyMappings, matc
return &policyMatch{matchType: matchTypeNoMatch, matchedName: imageName}, nil return &policyMatch{matchType: matchTypeNoMatch, matchedName: imageName}, nil
} }
func resolvePolicyByID(opts *Options) (*Policy, error) { func resolvePolicyByID(opts *Options, tufClient tuf.Downloader) (*Policy, error) {
if opts.PolicyID != "" { if opts.PolicyID != "" {
localMappings, err := config.LoadLocalMappings(opts.LocalPolicyDir) localMappings, err := config.LoadLocalMappings(opts.LocalPolicyDir)
if err != nil { if err != nil {
@@ -168,21 +169,21 @@ func resolvePolicyByID(opts *Options) (*Policy, error) {
} }
// must check tuf // must check tuf
tufMappings, err := config.LoadTUFMappings(opts.TUFClient, opts.LocalTargetsDir) tufMappings, err := config.LoadTUFMappings(tufClient, opts.LocalTargetsDir)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to load tuf policy mappings by id: %w", err) return nil, fmt.Errorf("failed to load tuf policy mappings by id: %w", err)
} }
policy := tufMappings.Policies[opts.PolicyID] policy := tufMappings.Policies[opts.PolicyID]
if policy != nil { if policy != nil {
return resolveTUFPolicy(opts, policy, "", "") return resolveTUFPolicy(opts, tufClient, policy, "", "")
} }
return nil, fmt.Errorf("policy with id %s not found", opts.PolicyID) return nil, fmt.Errorf("policy with id %s not found", opts.PolicyID)
} }
return nil, nil return nil, nil
} }
func ResolvePolicy(ctx context.Context, detailsResolver oci.ImageDetailsResolver, opts *Options) (*Policy, error) { func ResolvePolicy(ctx context.Context, tufClient tuf.Downloader, detailsResolver oci.ImageDetailsResolver, opts *Options) (*Policy, error) {
p, err := resolvePolicyByID(opts) p, err := resolvePolicyByID(opts, tufClient)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to resolve policy by id: %w", err) return nil, fmt.Errorf("failed to resolve policy by id: %w", err)
} }
@@ -209,7 +210,7 @@ func ResolvePolicy(ctx context.Context, detailsResolver oci.ImageDetailsResolver
return resolveLocalPolicy(opts, match.policy, imageName, match.matchedName) return resolveLocalPolicy(opts, match.policy, imageName, match.matchedName)
} }
// must check tuf // must check tuf
tufMappings, err := config.LoadTUFMappings(opts.TUFClient, opts.LocalTargetsDir) tufMappings, err := config.LoadTUFMappings(tufClient, opts.LocalTargetsDir)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to load tuf policy mappings as fallback: %w", err) return nil, fmt.Errorf("failed to load tuf policy mappings as fallback: %w", err)
} }
@@ -218,7 +219,7 @@ func ResolvePolicy(ctx context.Context, detailsResolver oci.ImageDetailsResolver
if match.matchType == matchTypeMatchNoPolicy { if match.matchType == matchTypeMatchNoPolicy {
for _, mapping := range tufMappings.Policies { for _, mapping := range tufMappings.Policies {
if mapping.ID == match.rule.PolicyID { if mapping.ID == match.rule.PolicyID {
return resolveTUFPolicy(opts, mapping, imageName, match.matchedName) return resolveTUFPolicy(opts, tufClient, mapping, imageName, match.matchedName)
} }
} }
} }
@@ -229,7 +230,7 @@ func ResolvePolicy(ctx context.Context, detailsResolver oci.ImageDetailsResolver
return nil, err return nil, err
} }
if match.matchType == matchTypePolicy { if match.matchType == matchTypePolicy {
return resolveTUFPolicy(opts, match.policy, imageName, match.matchedName) return resolveTUFPolicy(opts, tufClient, match.policy, imageName, match.matchedName)
} }
return nil, nil return nil, nil
} }

View File

@@ -51,29 +51,30 @@ func TestRegoEvaluator_Evaluate(t *testing.T) {
policyID string policyID string
resolveErrorStr string resolveErrorStr string
}{ }{
{repo: "testdata/mock-tuf-allow", expectSuccess: true, isCanonical: false, resolver: defaultResolver}, {repo: "testdata/mock-tuf-allow", expectSuccess: true, resolver: defaultResolver},
{repo: "testdata/mock-tuf-allow", expectSuccess: true, isCanonical: false, resolver: defaultResolver, policyID: "docker-official-images"}, {repo: "testdata/mock-tuf-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/mock-tuf-allow", resolver: defaultResolver, policyID: "non-existent-policy-id", resolveErrorStr: resolveErrorStr},
{repo: "testdata/mock-tuf-deny", expectSuccess: false, isCanonical: false, resolver: defaultResolver}, {repo: "testdata/mock-tuf-deny", resolver: defaultResolver},
{repo: "testdata/mock-tuf-verify-sig", expectSuccess: true, isCanonical: false, resolver: defaultResolver}, {repo: "testdata/mock-tuf-verify-sig", expectSuccess: true, resolver: defaultResolver},
{repo: "testdata/mock-tuf-wrong-key", expectSuccess: false, isCanonical: false, resolver: defaultResolver}, {repo: "testdata/mock-tuf-wrong-key", resolver: defaultResolver},
{repo: "testdata/mock-tuf-allow-canonical", expectSuccess: true, isCanonical: true, resolver: defaultResolver}, {repo: "testdata/mock-tuf-allow-canonical", expectSuccess: true, isCanonical: true, resolver: defaultResolver},
{repo: "testdata/mock-tuf-allow-canonical", expectSuccess: false, isCanonical: false, resolver: defaultResolver}, {repo: "testdata/mock-tuf-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/mock-tuf-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")) tufClient := tuf.NewMockTufClient(tc.repo, test.CreateTempDir(t, "", "tuf-dest"))
if tc.policy == nil { if tc.policy == nil {
tc.policy = &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,
} }
@@ -86,7 +87,7 @@ func TestRegoEvaluator_Evaluate(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
resolver, err := policy.CreateImageDetailsResolver(src) resolver, err := policy.CreateImageDetailsResolver(src)
require.NoError(t, err) require.NoError(t, err)
policy, err := policy.ResolvePolicy(ctx, resolver, tc.policy) policy, err := policy.ResolvePolicy(ctx, tufClient, 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)

View File

@@ -2,6 +2,10 @@ package attest
import rego.v1 import rego.v1
result := { default canonical = false
"success": input.isCanonical,
canonical if {
not input.tag
} }
result := {"success": canonical}

View File

@@ -27,7 +27,7 @@ type Result struct {
} }
type Options struct { type Options struct {
TUFClient tuf.Downloader TUFClientOptions *tuf.ClientOptions
LocalTargetsDir string LocalTargetsDir string
LocalPolicyDir string LocalPolicyDir string
PolicyID string PolicyID string
@@ -45,9 +45,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 {

View File

@@ -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)
} }

View File

@@ -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)

View File

@@ -1,6 +1,7 @@
package tuf package tuf
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"io/fs" "io/fs"
@@ -20,6 +21,24 @@ import (
"github.com/theupdateframework/go-tuf/v2/metadata/updater" "github.com/theupdateframework/go-tuf/v2/metadata/updater"
) )
type tufCtxKeyType struct{}
var DownloaderCtxKey tufCtxKeyType
// WithDownloader sets Downloader in context.
func WithDownloader(ctx context.Context, downloader Downloader) context.Context {
return context.WithValue(ctx, DownloaderCtxKey, downloader)
}
// GetDownloader returns the Downloader from context and `true` if it exists, otherwise `nil` and `false`.
func GetDownloader(ctx context.Context) (Downloader, bool) {
t, ok := ctx.Value(DownloaderCtxKey).(Downloader)
if !ok {
return nil, false
}
return t, true
}
type Source string type Source string
const ( const (
@@ -35,6 +54,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 +75,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 +118,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 +160,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
} }

View File

@@ -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()

View 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",
},
}

View 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

View File

@@ -38,7 +38,7 @@ subjects contains subject if {
} }
success if { success if {
print("input:",input) # print("input:",input)
true true
} }

View File

@@ -0,0 +1,4 @@
version: v1
kind: policy-mapping
policies:
rules: