Compare commits
42 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d97d20eb93 | ||
|
|
42390b5fc2 | ||
|
|
70e6345942 | ||
|
|
f853875eea | ||
|
|
050497e5a7 | ||
|
|
d69334a1e6 | ||
|
|
a84268b133 | ||
|
|
2cd2e2da96 | ||
|
|
f1ece6893f | ||
|
|
116b9ea770 | ||
|
|
d291912208 | ||
|
|
9cad88a687 | ||
|
|
77ccbc097b | ||
|
|
45927967c8 | ||
|
|
9aa56e564d | ||
|
|
6d0a6de520 | ||
|
|
8767951fa2 | ||
|
|
f18b5877d3 | ||
|
|
93fd9daeb9 | ||
|
|
5df79de1c7 | ||
|
|
5b5e43b07a | ||
|
|
4c5135eb1b | ||
|
|
0133423f0d | ||
|
|
501b9b442d | ||
|
|
d84ed4821c | ||
|
|
c9e2ddd448 | ||
|
|
165241de42 | ||
|
|
c7d17faf05 | ||
|
|
58021646e3 | ||
|
|
3e7a85e9b8 | ||
|
|
bb7a9a257e | ||
|
|
c690d1090c | ||
|
|
1d1c258f9c | ||
|
|
5d096e226f | ||
|
|
7fc7ceaba0 | ||
|
|
78ec0b7666 | ||
|
|
053f764b8f | ||
|
|
ad3b8b9e49 | ||
|
|
9582e69968 | ||
|
|
b0b37f73f3 | ||
|
|
d21fc7853c | ||
|
|
008c14e3f3 |
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
|||||||
id-token: write
|
id-token: write
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.21.x]
|
go-version: [1.22.x]
|
||||||
# temp disable windows tests see https://github.com/docker/image-signer-verifier/pull/154
|
# temp disable windows tests see https://github.com/docker/image-signer-verifier/pull/154
|
||||||
# os: [ubuntu-latest, macos-latest, windows-latest]
|
# os: [ubuntu-latest, macos-latest, windows-latest]
|
||||||
os: [ubuntu-latest, macos-latest]
|
os: [ubuntu-latest, macos-latest]
|
||||||
|
|||||||
@@ -1,2 +1,5 @@
|
|||||||
ignore:
|
ignore:
|
||||||
- "internal/test"
|
- "internal/test"
|
||||||
|
coverage:
|
||||||
|
status:
|
||||||
|
patch: false
|
||||||
|
|||||||
50
go.mod
50
go.mod
@@ -13,32 +13,32 @@ require (
|
|||||||
github.com/google/go-containerregistry v0.20.1
|
github.com/google/go-containerregistry v0.20.1
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.2
|
github.com/hashicorp/go-cleanhttp v0.5.2
|
||||||
github.com/in-toto/in-toto-golang v0.9.0
|
github.com/in-toto/in-toto-golang v0.9.0
|
||||||
github.com/open-policy-agent/opa v0.67.0
|
github.com/open-policy-agent/opa v0.67.1
|
||||||
github.com/opencontainers/image-spec v1.1.0
|
github.com/opencontainers/image-spec v1.1.0
|
||||||
github.com/package-url/packageurl-go v0.1.3
|
github.com/package-url/packageurl-go v0.1.3
|
||||||
github.com/secure-systems-lab/go-securesystemslib v0.8.0
|
github.com/secure-systems-lab/go-securesystemslib v0.8.0
|
||||||
github.com/sigstore/cosign/v2 v2.3.0
|
github.com/sigstore/cosign/v2 v2.4.0
|
||||||
github.com/sigstore/rekor v1.3.6
|
github.com/sigstore/rekor v1.3.6
|
||||||
github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.7
|
github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.8
|
||||||
github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.8.7
|
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.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.189.0
|
google.golang.org/api v0.191.0
|
||||||
sigs.k8s.io/yaml v1.4.0
|
sigs.k8s.io/yaml v1.4.0
|
||||||
)
|
)
|
||||||
|
|
||||||
// fork of a fork (in case it goes away) with changes to support ArtifactType (https://github.com/google/go-containerregistry/pull/1931)
|
// fork with changes to support ArtifactType (https://github.com/google/go-containerregistry/pull/1931)
|
||||||
replace github.com/google/go-containerregistry => github.com/kipz/go-containerregistry v0.0.0-20240722163910-ebe90246535d
|
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.0 // indirect
|
||||||
cloud.google.com/go/auth v0.7.2 // indirect
|
cloud.google.com/go/auth v0.7.3 // 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.10 // indirect
|
cloud.google.com/go/iam v1.1.12 // indirect
|
||||||
cloud.google.com/go/kms v1.18.2 // indirect
|
cloud.google.com/go/kms v1.18.4 // indirect
|
||||||
cloud.google.com/go/longrunning v0.5.9 // indirect
|
cloud.google.com/go/longrunning v0.5.11 // indirect
|
||||||
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
|
||||||
@@ -57,7 +57,7 @@ require (
|
|||||||
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.3 // 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.17 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/kms v1.35.1 // 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.4 // 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.4 // 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.3 // indirect
|
||||||
@@ -75,9 +75,9 @@ require (
|
|||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||||
github.com/digitorus/pkcs7 v0.0.0-20230818184609-3a137a874352 // indirect
|
github.com/digitorus/pkcs7 v0.0.0-20230818184609-3a137a874352 // indirect
|
||||||
github.com/digitorus/timestamp v0.0.0-20231217203849-220c5c2851b7 // indirect
|
github.com/digitorus/timestamp v0.0.0-20231217203849-220c5c2851b7 // indirect
|
||||||
github.com/docker/cli v26.1.3+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.0.3+incompatible // indirect
|
github.com/docker/docker v27.1.0+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
|
||||||
@@ -101,13 +101,12 @@ require (
|
|||||||
github.com/gobwas/glob v0.2.3 // indirect
|
github.com/gobwas/glob v0.2.3 // indirect
|
||||||
github.com/gogo/protobuf v1.3.2 // indirect
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||||
github.com/golang/protobuf v1.5.4 // indirect
|
|
||||||
github.com/golang/snappy v0.0.4 // indirect
|
github.com/golang/snappy v0.0.4 // indirect
|
||||||
github.com/google/certificate-transparency-go v1.2.1 // indirect
|
github.com/google/certificate-transparency-go v1.2.1 // indirect
|
||||||
github.com/google/s2a-go v0.1.7 // indirect
|
github.com/google/s2a-go v0.1.8 // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
|
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
|
||||||
github.com/googleapis/gax-go/v2 v2.12.5 // indirect
|
github.com/googleapis/gax-go/v2 v2.13.0 // indirect
|
||||||
github.com/gorilla/mux v1.8.1 // indirect
|
github.com/gorilla/mux v1.8.1 // indirect
|
||||||
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
|
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
|
||||||
github.com/hashicorp/hcl v1.0.1-vault-5 // indirect
|
github.com/hashicorp/hcl v1.0.1-vault-5 // indirect
|
||||||
@@ -149,7 +148,8 @@ require (
|
|||||||
github.com/shibumi/go-pathspec v1.3.0 // indirect
|
github.com/shibumi/go-pathspec v1.3.0 // indirect
|
||||||
github.com/shirou/gopsutil/v3 v3.24.4 // indirect
|
github.com/shirou/gopsutil/v3 v3.24.4 // indirect
|
||||||
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||||
github.com/sigstore/sigstore v1.8.7 // indirect
|
github.com/sigstore/protobuf-specs v0.3.2 // indirect
|
||||||
|
github.com/sigstore/sigstore v1.8.8 // indirect
|
||||||
github.com/sigstore/timestamp-authority v1.2.2 // indirect
|
github.com/sigstore/timestamp-authority v1.2.2 // indirect
|
||||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||||
@@ -184,17 +184,17 @@ require (
|
|||||||
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.25.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.17.0 // indirect
|
golang.org/x/mod v0.19.0 // indirect
|
||||||
golang.org/x/net v0.27.0 // indirect
|
golang.org/x/net v0.27.0 // indirect
|
||||||
golang.org/x/oauth2 v0.21.0 // indirect
|
golang.org/x/oauth2 v0.22.0 // indirect
|
||||||
golang.org/x/sync v0.7.0 // indirect
|
golang.org/x/sync v0.8.0 // indirect
|
||||||
golang.org/x/sys v0.22.0 // indirect
|
golang.org/x/sys v0.22.0 // indirect
|
||||||
golang.org/x/term v0.22.0 // indirect
|
golang.org/x/term v0.22.0 // indirect
|
||||||
golang.org/x/text v0.16.0 // indirect
|
golang.org/x/text v0.16.0 // indirect
|
||||||
golang.org/x/time v0.5.0 // indirect
|
golang.org/x/time v0.6.0 // indirect
|
||||||
google.golang.org/genproto v0.0.0-20240722135656-d784300faade // indirect
|
google.golang.org/genproto v0.0.0-20240730163845-b1a4ccb954bf // indirect
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 // indirect
|
google.golang.org/genproto/googleapis/api v0.0.0-20240725223205-93522f1f2a9f // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240722135656-d784300faade // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240730163845-b1a4ccb954bf // 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
|
||||||
|
|||||||
132
go.sum
132
go.sum
@@ -1,18 +1,18 @@
|
|||||||
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.2 h1:uiha352VrCDMXg+yoBtaD0tUF4Kv9vrtrWPYXwutnDE=
|
cloud.google.com/go/auth v0.7.3 h1:98Vr+5jMaCZ5NZk6e/uBgf60phTk/XN84r8QEWB9yjY=
|
||||||
cloud.google.com/go/auth v0.7.2/go.mod h1:VEc4p5NNxycWQTMQEDQF0bd6aTMb6VgYDXEwiJJQAbs=
|
cloud.google.com/go/auth v0.7.3/go.mod h1:HJtWUx1P5eqjy/f6Iq5KeytNpbAcGolPhOgyop2LlzA=
|
||||||
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=
|
||||||
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.10 h1:ZSAr64oEhQSClwBL670MsJAW5/RLiC6kfw3Bqmd5ZDI=
|
cloud.google.com/go/iam v1.1.12 h1:JixGLimRrNGcxvJEQ8+clfLxPlbeZA6MuRJ+qJNQ5Xw=
|
||||||
cloud.google.com/go/iam v1.1.10/go.mod h1:iEgMq62sg8zx446GCaijmA2Miwg5o3UbO+nI47WHJps=
|
cloud.google.com/go/iam v1.1.12/go.mod h1:9LDX8J7dN5YRyzVHxwQzrQs9opFFqn0Mxs9nAeB+Hhg=
|
||||||
cloud.google.com/go/kms v1.18.2 h1:EGgD0B9k9tOOkbPhYW1PHo2W0teamAUYMOUIcDRMfPk=
|
cloud.google.com/go/kms v1.18.4 h1:dYN3OCsQ6wJLLtOnI8DGUwQ5shMusXsWCCC+s09ATsk=
|
||||||
cloud.google.com/go/kms v1.18.2/go.mod h1:YFz1LYrnGsXARuRePL729oINmN5J/5e7nYijgvfiIeY=
|
cloud.google.com/go/kms v1.18.4/go.mod h1:SG1bgQ3UWW6/KdPo9uuJnzELXY5YTTMJtDYvajiQ22g=
|
||||||
cloud.google.com/go/longrunning v0.5.9 h1:haH9pAuXdPAMqHvzX0zlWQigXT7B0+CL4/2nXXdBo5k=
|
cloud.google.com/go/longrunning v0.5.11 h1:Havn1kGjz3whCfoD8dxMLP73Ph5w+ODyZB9RUsDxtGk=
|
||||||
cloud.google.com/go/longrunning v0.5.9/go.mod h1:HD+0l9/OOW0za6UWdKJtXoFAX/BGg/3Wj8p10NeWF7c=
|
cloud.google.com/go/longrunning v0.5.11/go.mod h1:rDn7//lmlfWV1Dx6IB4RatCPenTwwmqXuiP0/RgoEO4=
|
||||||
cuelabs.dev/go/oci/ociregistry v0.0.0-20240404174027-a39bec0462d2 h1:BnG6pr9TTr6CYlrJznYUDj6V7xldD1W+1iXPum0wT/w=
|
cuelabs.dev/go/oci/ociregistry v0.0.0-20240404174027-a39bec0462d2 h1:BnG6pr9TTr6CYlrJznYUDj6V7xldD1W+1iXPum0wT/w=
|
||||||
cuelabs.dev/go/oci/ociregistry v0.0.0-20240404174027-a39bec0462d2/go.mod h1:pK23AUVXuNzzTpfMCA06sxZGeVQ/75FdVtW249de9Uo=
|
cuelabs.dev/go/oci/ociregistry v0.0.0-20240404174027-a39bec0462d2/go.mod h1:pK23AUVXuNzzTpfMCA06sxZGeVQ/75FdVtW249de9Uo=
|
||||||
cuelang.org/go v0.9.2 h1:pfNiry2PdRBr02G/aKm5k2vhzmqbAOoaB4WurmEbWvs=
|
cuelang.org/go v0.9.2 h1:pfNiry2PdRBr02G/aKm5k2vhzmqbAOoaB4WurmEbWvs=
|
||||||
@@ -29,12 +29,12 @@ github.com/AliyunContainerService/ack-ram-tool/pkg/credentials/alibabacloudsdkgo
|
|||||||
github.com/AliyunContainerService/ack-ram-tool/pkg/credentials/alibabacloudsdkgo/helper v0.2.0/go.mod h1:GgeIE+1be8Ivm7Sh4RgwI42aTtC9qrcj+Y9Y6CjJhJs=
|
github.com/AliyunContainerService/ack-ram-tool/pkg/credentials/alibabacloudsdkgo/helper v0.2.0/go.mod h1:GgeIE+1be8Ivm7Sh4RgwI42aTtC9qrcj+Y9Y6CjJhJs=
|
||||||
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU=
|
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU=
|
||||||
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.12.0 h1:1nGuui+4POelzDwI7RG56yfQJHCnKvwfMoU7VsEp+Zg=
|
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.13.0 h1:GJHeeA2N7xrG3q30L2UXDyuWRzDM900/65j70wcM4Ww=
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.12.0/go.mod h1:99EvauvlcJ1U06amZiksfYz/3aFGyIhWGHVyiZXtBAI=
|
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.13.0/go.mod h1:l38EPgmsp71HHLq9j7De57JcKOWPyhrsW1Awm1JS6K0=
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 h1:tfLQ34V6F7tVSwoTf/4lH5sE0o6eCJuNDTmH09nDpbc=
|
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 h1:tfLQ34V6F7tVSwoTf/4lH5sE0o6eCJuNDTmH09nDpbc=
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg=
|
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg=
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.9.0 h1:H+U3Gk9zY56G3u872L82bk4thcsy2Gghb9ExT4Zvm1o=
|
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY=
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.9.0/go.mod h1:mgrmMSgaLp9hmax62XQTd0N4aAqSE5E0DulSpVYK7vc=
|
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY=
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.1.0 h1:DRiANoJTiW6obBQe3SqZizkuV1PEgfiiGivmVocDy64=
|
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.1.0 h1:DRiANoJTiW6obBQe3SqZizkuV1PEgfiiGivmVocDy64=
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.1.0/go.mod h1:qLIye2hwb/ZouqhpSD9Zn3SJipvpEnz1Ywl3VUk9Y0s=
|
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.1.0/go.mod h1:qLIye2hwb/ZouqhpSD9Zn3SJipvpEnz1Ywl3VUk9Y0s=
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0 h1:D3occbWoio4EBLkbkevetNMAVX197GkzbUMtqjGWn80=
|
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0 h1:D3occbWoio4EBLkbkevetNMAVX197GkzbUMtqjGWn80=
|
||||||
@@ -102,8 +102,8 @@ github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig
|
|||||||
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
|
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
|
||||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
|
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
|
||||||
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.54.19 h1:tyWV+07jagrNiCcGRzRhdtVjQs7Vy41NwsuOcl0IbVI=
|
github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU=
|
||||||
github.com/aws/aws-sdk-go v1.54.19/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.3 h1:jUeBtG0Ih+ZIFH0F4UkmL9w3cSpaMv9tYYDbzILP8dY=
|
||||||
github.com/aws/aws-sdk-go-v2 v1.30.3/go.mod h1:nIQjQVp5sfpQcTc9mPSr1B0PaWK5ByX9MOoDadSN4lc=
|
github.com/aws/aws-sdk-go-v2 v1.30.3/go.mod h1:nIQjQVp5sfpQcTc9mPSr1B0PaWK5ByX9MOoDadSN4lc=
|
||||||
github.com/aws/aws-sdk-go-v2/config v1.27.27 h1:HdqgGt1OAP0HkEDDShEl0oSYa9ZZBSOmKpdpsDMdO90=
|
github.com/aws/aws-sdk-go-v2/config v1.27.27 h1:HdqgGt1OAP0HkEDDShEl0oSYa9ZZBSOmKpdpsDMdO90=
|
||||||
@@ -126,8 +126,8 @@ github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 h1:dT3MqvG
|
|||||||
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.3/go.mod h1:GlAeCkHwugxdHaueRr4nhPuY+WW+gR8UjlcqzPr1SPI=
|
||||||
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.17 h1:HGErhhrxZlQ044RiM+WdoZxp0p+EGM62y3L6pwA4olE=
|
||||||
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.17/go.mod h1:RkZEx4l0EHYDJpWppMJ3nD9wZJAa8/0lq9aVC+r2UII=
|
||||||
github.com/aws/aws-sdk-go-v2/service/kms v1.35.1 h1:0gP2OJJT6HM2BYltZ9x+A87OE8LJL96DXeAAdLv3t1M=
|
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.1/go.mod h1:hGONorZkQCfR5DW6l2xdy7zC8vfO0r9pJlwyg6gmGeo=
|
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.4 h1:BXx0ZIxvrJdSgSvKTZ+yRBeSqqgPM89VPlulEcl37tM=
|
||||||
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.4/go.mod h1:ooyCOXjvJEsUw7x+ZDHeISPMhtwI3ZCB7ggFMcFfWLU=
|
||||||
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.4 h1:yiwVzJW2ZxZTurVbYWA7QOrAaCYQR72t0wrSBfoesUE=
|
||||||
@@ -142,8 +142,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
|||||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
|
github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
|
||||||
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
|
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
|
||||||
github.com/buildkite/agent/v3 v3.75.1 h1:FJg1gss9nUESExTsfx6yWqs/g20Vyd4pHSEB9iuT4pI=
|
github.com/buildkite/agent/v3 v3.76.2 h1:SweFq3e0N20RikWsVeOXzTjfr0AoOskxm9c0bcNyI0E=
|
||||||
github.com/buildkite/agent/v3 v3.75.1/go.mod h1:UXCAEaqJnw5UsoZjJ9NxMvdSGc4oMHBnQFQqzGPDM0Y=
|
github.com/buildkite/agent/v3 v3.76.2/go.mod h1:9ffbmJD7d7C/nOcElj6Qm+uIj1QoYh3NNvka4rkKkss=
|
||||||
github.com/buildkite/go-pipeline v0.10.0 h1:EDffu+LfMY2k5u+iEdo6Jn3obGKsrL5wicc1O/yFeRs=
|
github.com/buildkite/go-pipeline v0.10.0 h1:EDffu+LfMY2k5u+iEdo6Jn3obGKsrL5wicc1O/yFeRs=
|
||||||
github.com/buildkite/go-pipeline v0.10.0/go.mod h1:eMH1kiav5VeiTiu0Mk2/M7nZhKyFeL4iGj7Y7rj4f3w=
|
github.com/buildkite/go-pipeline v0.10.0/go.mod h1:eMH1kiav5VeiTiu0Mk2/M7nZhKyFeL4iGj7Y7rj4f3w=
|
||||||
github.com/buildkite/interpolate v0.1.3 h1:OFEhqji1rNTRg0u9DsSodg63sjJQEb1uWbENq9fUOBM=
|
github.com/buildkite/interpolate v0.1.3 h1:OFEhqji1rNTRg0u9DsSodg63sjJQEb1uWbENq9fUOBM=
|
||||||
@@ -218,16 +218,18 @@ github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi
|
|||||||
github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
|
github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
|
||||||
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||||
github.com/docker/cli v26.1.3+incompatible h1:bUpXT/N0kDE3VUHI2r5VMsYQgi38kYuoC0oL9yt3lqc=
|
github.com/docker/cli v27.1.1+incompatible h1:goaZxOqs4QKxznZjjBWKONQci/MywhtRv2oNn0GkeZE=
|
||||||
github.com/docker/cli v26.1.3+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.0.3+incompatible h1:aBGI9TeQ4MPlhquTQKq9XbK79rKFVwXNUAYz9aXyEBE=
|
github.com/docker/docker v27.1.0+incompatible h1:rEHVQc4GZ0MIQKifQPHSFGV/dVgaZafgRf8fCPtDYBs=
|
||||||
github.com/docker/docker v27.0.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
github.com/docker/docker v27.1.0+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=
|
||||||
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
||||||
|
github.com/docker/go-containerregistry v0.0.0-20240808132857-c8bfc44af7c8 h1:T/wutVfQ1Oj4H5tbP5IZL5l6PZqzvapVJ5cB4Wy4Ucc=
|
||||||
|
github.com/docker/go-containerregistry v0.0.0-20240808132857-c8bfc44af7c8/go.mod h1:z38EKdKh4h7IP2gSfUUqEvalZBqs6AoLeWfUy34nQC8=
|
||||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
@@ -353,8 +355,8 @@ github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17
|
|||||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
|
github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM=
|
||||||
github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
|
github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA=
|
||||||
github.com/google/tink/go v1.7.0 h1:6Eox8zONGebBFcCBqkVmt60LaWZa6xg1cl/DwAh/J1w=
|
github.com/google/tink/go v1.7.0 h1:6Eox8zONGebBFcCBqkVmt60LaWZa6xg1cl/DwAh/J1w=
|
||||||
github.com/google/tink/go v1.7.0/go.mod h1:GAUOd+QE3pgj9q8VKIGTCP33c/B7eb4NhxLcgTJZStM=
|
github.com/google/tink/go v1.7.0/go.mod h1:GAUOd+QE3pgj9q8VKIGTCP33c/B7eb4NhxLcgTJZStM=
|
||||||
github.com/google/trillian v1.6.0 h1:jMBeDBIkINFvS2n6oV5maDqfRlxREAc6CW9QYWQ0qT4=
|
github.com/google/trillian v1.6.0 h1:jMBeDBIkINFvS2n6oV5maDqfRlxREAc6CW9QYWQ0qT4=
|
||||||
@@ -364,8 +366,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
|||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
|
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
|
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
|
||||||
github.com/googleapis/gax-go/v2 v2.12.5 h1:8gw9KZK8TiVKB6q3zHY3SBzLnrGp6HQjyfYBYGmXdxA=
|
github.com/googleapis/gax-go/v2 v2.13.0 h1:yitjD5f7jQHhyDsnhKEBU52NdvvdSeGzlAnDPT0hH1s=
|
||||||
github.com/googleapis/gax-go/v2 v2.12.5/go.mod h1:BUDKcWo+RaKq5SC9vVYL0wLADa3VcfswbOMMRmB9H3E=
|
github.com/googleapis/gax-go/v2 v2.13.0/go.mod h1:Z/fvTZXF8/uw7Xu5GuslPw+bplx6SS338j1Is2S+B7A=
|
||||||
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
|
github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
|
||||||
@@ -417,8 +419,6 @@ github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8Hm
|
|||||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
github.com/kipz/go-containerregistry v0.0.0-20240722163910-ebe90246535d h1:5QaWAwKhslfqxEyMZY0ofvsbMJkMLcx5E30JFufMVj8=
|
|
||||||
github.com/kipz/go-containerregistry v0.0.0-20240722163910-ebe90246535d/go.mod h1:YCMFNQeeXeLF+dnhhWkqDItx/JSkH01j1Kis4PsjzFI=
|
|
||||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=
|
github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=
|
||||||
@@ -493,8 +493,8 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y
|
|||||||
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||||
github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw=
|
github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw=
|
||||||
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
|
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
|
||||||
github.com/open-policy-agent/opa v0.67.0 h1:FOdsO9yNhfmrh+72oVK7ImWmzruG+VSpfbr5IBqEWVs=
|
github.com/open-policy-agent/opa v0.67.1 h1:rzy26J6g1X+CKknAcx0Vfbt41KqjuSzx4E0A8DAZf3E=
|
||||||
github.com/open-policy-agent/opa v0.67.0/go.mod h1:aqKlHc8E2VAAylYE9x09zJYr/fYzGX+JKne89UGqFzk=
|
github.com/open-policy-agent/opa v0.67.1/go.mod h1:aqKlHc8E2VAAylYE9x09zJYr/fYzGX+JKne89UGqFzk=
|
||||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||||
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
|
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
|
||||||
@@ -555,22 +555,26 @@ github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFt
|
|||||||
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
||||||
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
|
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
|
||||||
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
|
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
|
||||||
github.com/sigstore/cosign/v2 v2.3.0 h1:rBLVdKMYuER0blmaKMfMNkvawBdK1lTMz2L5PtTPrI8=
|
github.com/sigstore/cosign/v2 v2.4.0 h1:2NdidNgClg+oXr/fDIr37E/BE6j00gqgUhSiBK2kjSQ=
|
||||||
github.com/sigstore/cosign/v2 v2.3.0/go.mod h1:tjACBZS6LoH3bap5hU8MxyGC4DIJiatqhZxuJWFcIJ0=
|
github.com/sigstore/cosign/v2 v2.4.0/go.mod h1:j+fH1DCUkcn92qp6ezDj4JbGMri6eG1nLJC+hs64rvc=
|
||||||
github.com/sigstore/fulcio v1.5.1 h1:Iasy1zfNjaq8BV4S8o6pXspLDU28PQC2z07GmOu9zpM=
|
github.com/sigstore/fulcio v1.5.1 h1:Iasy1zfNjaq8BV4S8o6pXspLDU28PQC2z07GmOu9zpM=
|
||||||
github.com/sigstore/fulcio v1.5.1/go.mod h1:W1A/UHrTopy1IBZPMtHmxg7GPYAu+vt5dRXM3W6yjPo=
|
github.com/sigstore/fulcio v1.5.1/go.mod h1:W1A/UHrTopy1IBZPMtHmxg7GPYAu+vt5dRXM3W6yjPo=
|
||||||
|
github.com/sigstore/protobuf-specs v0.3.2 h1:nCVARCN+fHjlNCk3ThNXwrZRqIommIeNKWwQvORuRQo=
|
||||||
|
github.com/sigstore/protobuf-specs v0.3.2/go.mod h1:RZ0uOdJR4OB3tLQeAyWoJFbNCBFrPQdcokntde4zRBA=
|
||||||
github.com/sigstore/rekor v1.3.6 h1:QvpMMJVWAp69a3CHzdrLelqEqpTM3ByQRt5B5Kspbi8=
|
github.com/sigstore/rekor v1.3.6 h1:QvpMMJVWAp69a3CHzdrLelqEqpTM3ByQRt5B5Kspbi8=
|
||||||
github.com/sigstore/rekor v1.3.6/go.mod h1:JDTSNNMdQ/PxdsS49DJkJ+pRJCO/83nbR5p3aZQteXc=
|
github.com/sigstore/rekor v1.3.6/go.mod h1:JDTSNNMdQ/PxdsS49DJkJ+pRJCO/83nbR5p3aZQteXc=
|
||||||
github.com/sigstore/sigstore v1.8.7 h1:L7/zKauHTg0d0Hukx7qlR4nifh6T6O6UIt9JBwAmTIg=
|
github.com/sigstore/sigstore v1.8.8 h1:B6ZQPBKK7Z7tO3bjLNnlCMG+H66tO4E/+qAphX8T/hg=
|
||||||
github.com/sigstore/sigstore v1.8.7/go.mod h1:MPiQ/NIV034Fc3Kk2IX9/XmBQdK60wfmpvgK9Z1UjRA=
|
github.com/sigstore/sigstore v1.8.8/go.mod h1:GW0GgJSCTBJY3fUOuGDHeFWcD++c4G8Y9K015pwcpDI=
|
||||||
github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.7 h1:SoahswHQm2JhO8h3KTAeX8IZeE7mSR2Lc53ay5choes=
|
github.com/sigstore/sigstore-go v0.5.1 h1:5IhKvtjlQBeLnjKkzMELNG4tIBf+xXQkDzhLV77+/8Y=
|
||||||
github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.7/go.mod h1:TOVOPOqldrrz4dP7x4/0DFQTs9QSXZUoHu21+JHmixA=
|
github.com/sigstore/sigstore-go v0.5.1/go.mod h1:TuOfV7THHqiDaUHuJ5+QN23RP/YoKmsbwJpY+aaYPN0=
|
||||||
github.com/sigstore/sigstore/pkg/signature/kms/azure v1.8.7 h1:jcdKxc5bvwfL7+ZbeCszaN/qsBd6180fGAHxeX5Ckm0=
|
github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.8 h1:2zHmUvaYCwV6LVeTo+OAkTm8ykOGzA9uFlAjwDPAUWM=
|
||||||
github.com/sigstore/sigstore/pkg/signature/kms/azure v1.8.7/go.mod h1:gakVcpRiN+aFdLhPXXP8ubOCWiedM1YJ/gR74ez/tT0=
|
github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.8/go.mod h1:OEhheBplZinUsm7W9BupafztVZV3ldkAxEHbpAeC0Pk=
|
||||||
github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.8.7 h1:zYg1XlbKpQkmE7FpWTkLuUn7RttLAq4FcZ1G9JcqhoY=
|
github.com/sigstore/sigstore/pkg/signature/kms/azure v1.8.8 h1:RKk4Z+qMaLORUdT7zntwMqKiYAej1VQlCswg0S7xNSY=
|
||||||
github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.8.7/go.mod h1:VmUsO1R4OHuyHBEgI4bbSUn0z2nojszrDMvlDxyX/dE=
|
github.com/sigstore/sigstore/pkg/signature/kms/azure v1.8.8/go.mod h1:dMJdlBWKHMu2xf0wIKpbo7+QfG+RzVkBB3nHP8EMM5o=
|
||||||
github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.8.7 h1:dbcB9VEddYrvK+y4rHeES5OZ/pMQuucfJ0qCNWQmnp0=
|
github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.8.8 h1:89Xtxj8oqZt3UlSpCP4wApFvnQ2Z/dgowW5QOVhQigI=
|
||||||
github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.8.7/go.mod h1:96MrPJBkHiAvqFyqviuYbwPAdbPCj8CR3V0RJ9bKjrE=
|
github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.8.8/go.mod h1:Wa4xn/H3pU/yW/6tHiMXTpObBtBSGC5q29KYFEPKN6o=
|
||||||
|
github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.8.8 h1:Zte3Oogkd8m+nu2oK3yHtGmN++TZWh2Lm6q2iSprT1M=
|
||||||
|
github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.8.8/go.mod h1:j00crVw6ki4/WViXflw0zWgNALrAzZT+GbIK8v7Xlz4=
|
||||||
github.com/sigstore/timestamp-authority v1.2.2 h1:X4qyutnCQqJ0apMewFyx+3t7Tws00JQ/JonBiu3QvLE=
|
github.com/sigstore/timestamp-authority v1.2.2 h1:X4qyutnCQqJ0apMewFyx+3t7Tws00JQ/JonBiu3QvLE=
|
||||||
github.com/sigstore/timestamp-authority v1.2.2/go.mod h1:nEah4Eq4wpliDjlY342rXclGSO7Kb9hoRrl9tqLW13A=
|
github.com/sigstore/timestamp-authority v1.2.2/go.mod h1:nEah4Eq4wpliDjlY342rXclGSO7Kb9hoRrl9tqLW13A=
|
||||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||||
@@ -676,8 +680,8 @@ go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+
|
|||||||
go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI=
|
go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI=
|
||||||
go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0=
|
go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0=
|
||||||
go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8=
|
go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8=
|
||||||
go.step.sm/crypto v0.50.0 h1:BqI9sEgocoHDLLHiZnFqdqXl5FjdMvOWKMm/fKL/lrw=
|
go.step.sm/crypto v0.51.1 h1:ktUg/2hetEMiBAqgz502ktZDGoDoGrcHFg3XpkmkvvA=
|
||||||
go.step.sm/crypto v0.50.0/go.mod h1:NCFMhLS6FJXQ9sD9PP282oHtsBWLrI6wXZY0eOkq7t8=
|
go.step.sm/crypto v0.51.1/go.mod h1:PdrhttNU/tG9/YsVd4fdlysBN+UV503p0o2irFZQlAw=
|
||||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||||
@@ -702,8 +706,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8=
|
||||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
@@ -726,8 +730,8 @@ 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.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
|
||||||
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
|
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
|
||||||
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.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs=
|
golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA=
|
||||||
golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
@@ -735,8 +739,8 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ
|
|||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
@@ -785,8 +789,8 @@ 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.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
|
||||||
golang.org/x/time v0.5.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=
|
||||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||||
@@ -798,26 +802,26 @@ golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4f
|
|||||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
|
golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg=
|
||||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-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.189.0 h1:equMo30LypAkdkLMBqfeIqtyAnlyig1JSZArl4XPwdI=
|
google.golang.org/api v0.191.0 h1:cJcF09Z+4HAB2t5qTQM1ZtfL/PemsLFkcFG67qq2afk=
|
||||||
google.golang.org/api v0.189.0/go.mod h1:FLWGJKb0hb+pU2j+rJqwbnsF+ym+fQs73rbJ+KAUgy8=
|
google.golang.org/api v0.191.0/go.mod h1:tD5dsFGxFza0hnQveGfVk9QQYKcfp+VzgRqyXFxE0+E=
|
||||||
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-20240722135656-d784300faade h1:lKFsS7wpngDgSCeFn7MoLy+wBDQZ1UQIJD4UNM1Qvkg=
|
google.golang.org/genproto v0.0.0-20240730163845-b1a4ccb954bf h1:OqdXDEakZCVtDiZTjcxfwbHPCT11ycCEsTKesBVKvyY=
|
||||||
google.golang.org/genproto v0.0.0-20240722135656-d784300faade/go.mod h1:FfBgJBJg9GcpPvKIuHSZ/aE1g2ecGL74upMzGZjiGEY=
|
google.golang.org/genproto v0.0.0-20240730163845-b1a4ccb954bf/go.mod h1:mCr1K1c8kX+1iSBREvU3Juo11CB+QOEWxbRS01wWl5M=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 h1:0+ozOGcrp+Y8Aq8TLNN2Aliibms5LEzsq99ZZmAGYm0=
|
google.golang.org/genproto/googleapis/api v0.0.0-20240725223205-93522f1f2a9f h1:b1Ln/PG8orm0SsBbHZWke8dDp2lrCD4jSmfglFpTZbk=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094/go.mod h1:fJ/e3If/Q67Mj99hin0hMhiNyCRmt6BQ2aWIJshUSJw=
|
google.golang.org/genproto/googleapis/api v0.0.0-20240725223205-93522f1f2a9f/go.mod h1:AHT0dDg3SoMOgZGnZk29b5xTbPHMoEC8qthmBLJCpys=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240722135656-d784300faade h1:oCRSWfwGXQsqlVdErcyTt4A93Y8fo0/9D4b1gnI++qo=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240730163845-b1a4ccb954bf h1:liao9UHurZLtiEwBgT9LMOnKYsHze6eA6w1KQCMVN2Q=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240722135656-d784300faade/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240730163845-b1a4ccb954bf/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
|
||||||
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=
|
||||||
@@ -875,8 +879,8 @@ k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0 h1:jgGTlFYnhF1PM1Ax/lAlxUPE+KfCI
|
|||||||
k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
|
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
|
||||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
|
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
|
||||||
sigs.k8s.io/release-utils v0.8.3 h1:KtOtA4qDmzJyeQ2zkDsFVI25+NViwms/o5eL2NftFdA=
|
sigs.k8s.io/release-utils v0.8.4 h1:4QVr3UgbyY/d9p74LBhg0njSVQofUsAZqYOzVZBhdBw=
|
||||||
sigs.k8s.io/release-utils v0.8.3/go.mod h1:fp82Fma06OXBhEJ+GUJKqvcplDBomruK1R/1fWJnsrQ=
|
sigs.k8s.io/release-utils v0.8.4/go.mod h1:m1bHfscTemQp+z+pLCZnkXih9n0+WukIUU70n6nFnU0=
|
||||||
sigs.k8s.io/structured-merge-diff/v4 v4.3.0 h1:UZbZAZfX0wV2zr7YZorDz6GXROfDFj6LvqCRm4VUVKk=
|
sigs.k8s.io/structured-merge-diff/v4 v4.3.0 h1:UZbZAZfX0wV2zr7YZorDz6GXROfDFj6LvqCRm4VUVKk=
|
||||||
sigs.k8s.io/structured-merge-diff/v4 v4.3.0/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08=
|
sigs.k8s.io/structured-merge-diff/v4 v4.3.0/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08=
|
||||||
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
|
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
|
||||||
|
|||||||
@@ -16,11 +16,13 @@ var prodRoot []byte
|
|||||||
|
|
||||||
var defaultRoot = prodRoot
|
var defaultRoot = prodRoot
|
||||||
|
|
||||||
type RootName string
|
type (
|
||||||
type EmbeddedRoot struct {
|
RootName string
|
||||||
Data []byte
|
EmbeddedRoot struct {
|
||||||
Name RootName
|
Data []byte
|
||||||
}
|
Name RootName
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
RootDev = EmbeddedRoot{Data: devRoot, Name: "dev"}
|
RootDev = EmbeddedRoot{Data: devRoot, Name: "dev"}
|
||||||
|
|||||||
@@ -1,64 +0,0 @@
|
|||||||
package test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/docker/attest/pkg/attestation"
|
|
||||||
"github.com/docker/attest/pkg/oci"
|
|
||||||
"github.com/docker/attest/pkg/signerverifier"
|
|
||||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
|
||||||
"github.com/secure-systems-lab/go-securesystemslib/dsse"
|
|
||||||
)
|
|
||||||
|
|
||||||
type MockResolver struct {
|
|
||||||
Envs []*attestation.Envelope
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r MockResolver) Attestations(ctx context.Context, mediaType string) ([]*attestation.Envelope, error) {
|
|
||||||
return r.Envs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r MockResolver) ImageName(ctx context.Context) (string, error) {
|
|
||||||
return "library/alpine:latest", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r MockResolver) ImageDescriptor(ctx context.Context) (*v1.Descriptor, error) {
|
|
||||||
digest, err := v1.NewHash("sha256:da8b190665956ea07890a0273e2a9c96bfe291662f08e2860e868eef69c34620")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &v1.Descriptor{
|
|
||||||
Digest: digest,
|
|
||||||
Size: 1234,
|
|
||||||
MediaType: "application/vnd.oci.image.manifest.v1+json",
|
|
||||||
}, nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r MockResolver) ImagePlatform(ctx context.Context) (*v1.Platform, error) {
|
|
||||||
return oci.ParsePlatform("linux/amd64")
|
|
||||||
}
|
|
||||||
|
|
||||||
type MockRegistryResolver struct {
|
|
||||||
Subject *v1.Descriptor
|
|
||||||
ImageNameStr string
|
|
||||||
*MockResolver
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *MockRegistryResolver) ImageDescriptor(ctx context.Context) (*v1.Descriptor, error) {
|
|
||||||
return r.Subject, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *MockRegistryResolver) ImageName(ctx context.Context) (string, error) {
|
|
||||||
return r.ImageNameStr, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetMockSigner(ctx context.Context) (dsse.SignerVerifier, error) {
|
|
||||||
priv, err := os.ReadFile(filepath.Join("..", "..", "test", "testdata", "test-signing-key.pem"))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return signerverifier.LoadKeyPair(priv)
|
|
||||||
}
|
|
||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -21,12 +22,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
USE_MOCK_TL = true
|
UseMockTL = true
|
||||||
USE_MOCK_KMS = true
|
UseMockKMS = true
|
||||||
USE_MOCK_POLICY = true
|
UseMockPolicy = true
|
||||||
|
|
||||||
AwsRegion = "us-east-1"
|
AWSRegion = "us-east-1"
|
||||||
AwsKmsKeyArn = "arn:aws:kms:us-east-1:175142243308:alias/doi-signing" // sandbox
|
AWSKMSKeyARN = "arn:aws:kms:us-east-1:175142243308:alias/doi-signing" // sandbox
|
||||||
)
|
)
|
||||||
|
|
||||||
func CreateTempDir(t *testing.T, dir, pattern string) string {
|
func CreateTempDir(t *testing.T, dir, pattern string) string {
|
||||||
@@ -45,9 +46,17 @@ func CreateTempDir(t *testing.T, dir, pattern string) string {
|
|||||||
return tempDir
|
return tempDir
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetMockSigner(_ context.Context) (dsse.SignerVerifier, error) {
|
||||||
|
priv, err := os.ReadFile(filepath.Join("..", "..", "test", "testdata", "test-signing-key.pem"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return signerverifier.LoadKeyPair(priv)
|
||||||
|
}
|
||||||
|
|
||||||
func Setup(t *testing.T) (context.Context, dsse.SignerVerifier) {
|
func Setup(t *testing.T) (context.Context, dsse.SignerVerifier) {
|
||||||
var tl tlog.TL
|
var tl tlog.TL
|
||||||
if USE_MOCK_TL {
|
if UseMockTL {
|
||||||
tl = tlog.GetMockTL()
|
tl = tlog.GetMockTL()
|
||||||
} else {
|
} else {
|
||||||
tl = &tlog.RekorTL{}
|
tl = &tlog.RekorTL{}
|
||||||
@@ -55,8 +64,8 @@ func Setup(t *testing.T) (context.Context, dsse.SignerVerifier) {
|
|||||||
|
|
||||||
ctx := tlog.WithTL(context.Background(), tl)
|
ctx := tlog.WithTL(context.Background(), tl)
|
||||||
|
|
||||||
var policyEvaluator policy.PolicyEvaluator
|
var policyEvaluator policy.Evaluator
|
||||||
if USE_MOCK_POLICY {
|
if UseMockPolicy {
|
||||||
policyEvaluator = policy.GetMockPolicy()
|
policyEvaluator = policy.GetMockPolicy()
|
||||||
} else {
|
} else {
|
||||||
policyEvaluator = policy.NewRegoEvaluator(true)
|
policyEvaluator = policy.NewRegoEvaluator(true)
|
||||||
@@ -66,13 +75,13 @@ func Setup(t *testing.T) (context.Context, dsse.SignerVerifier) {
|
|||||||
|
|
||||||
var signer dsse.SignerVerifier
|
var signer dsse.SignerVerifier
|
||||||
var err error
|
var err error
|
||||||
if USE_MOCK_KMS {
|
if UseMockKMS {
|
||||||
signer, err = GetMockSigner(ctx)
|
signer, err = GetMockSigner(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
signer, err = signerverifier.GetAWSSigner(ctx, AwsKmsKeyArn, AwsRegion)
|
signer, err = signerverifier.GetAWSSigner(ctx, AWSKMSKeyARN, AWSRegion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -95,7 +104,8 @@ func ExtractStatementsFromIndex(idx v1.ImageIndex, mediaType string) ([]*Annotat
|
|||||||
|
|
||||||
var statements []*AnnotatedStatement
|
var statements []*AnnotatedStatement
|
||||||
|
|
||||||
for _, mf := range mfs2.Manifests {
|
for i := range mfs2.Manifests {
|
||||||
|
mf := &mfs2.Manifests[i]
|
||||||
if mf.Annotations[attestation.DockerReferenceType] != "attestation-manifest" {
|
if mf.Annotations[attestation.DockerReferenceType] != "attestation-manifest" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -124,10 +134,10 @@ func ExtractStatementsFromIndex(idx v1.ImageIndex, mediaType string) ([]*Annotat
|
|||||||
return nil, fmt.Errorf("failed to get layer contents: %w", err)
|
return nil, fmt.Errorf("failed to get layer contents: %w", err)
|
||||||
}
|
}
|
||||||
defer r.Close()
|
defer r.Close()
|
||||||
var intotoStatement = new(intoto.Statement)
|
inTotoStatement := new(intoto.Statement)
|
||||||
var desc *v1.Descriptor
|
var desc *v1.Descriptor
|
||||||
if strings.HasSuffix(string(mt), "+dsse") {
|
if strings.HasSuffix(string(mt), "+dsse") {
|
||||||
var env = new(attestation.Envelope)
|
env := new(attestation.Envelope)
|
||||||
err = json.NewDecoder(r).Decode(env)
|
err = json.NewDecoder(r).Decode(env)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to decode env: %w", err)
|
return nil, fmt.Errorf("failed to decode env: %w", err)
|
||||||
@@ -136,7 +146,7 @@ func ExtractStatementsFromIndex(idx v1.ImageIndex, mediaType string) ([]*Annotat
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to decode payload: %w", err)
|
return nil, fmt.Errorf("failed to decode payload: %w", err)
|
||||||
}
|
}
|
||||||
err = json.Unmarshal([]byte(payload), intotoStatement)
|
err = json.Unmarshal([]byte(payload), inTotoStatement)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to decode %s statement: %w", mediaType, err)
|
return nil, fmt.Errorf("failed to decode %s statement: %w", mediaType, err)
|
||||||
}
|
}
|
||||||
@@ -158,7 +168,7 @@ func ExtractStatementsFromIndex(idx v1.ImageIndex, mediaType string) ([]*Annotat
|
|||||||
}
|
}
|
||||||
statements = append(statements, &AnnotatedStatement{
|
statements = append(statements, &AnnotatedStatement{
|
||||||
OCIDescriptor: desc,
|
OCIDescriptor: desc,
|
||||||
InTotoStatement: intotoStatement,
|
InTotoStatement: inTotoStatement,
|
||||||
Annotations: annotations,
|
Annotations: annotations,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,9 +8,7 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var UnsignedTestImage = filepath.Join("..", "..", "test", "testdata", "unsigned-test-image")
|
||||||
UnsignedTestImage = filepath.Join("..", "..", "test", "testdata", "unsigned-test-image")
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ExpectedStatements = 4
|
ExpectedStatements = 4
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
|
|
||||||
"github.com/docker/attest/pkg/attest"
|
"github.com/docker/attest/pkg/attest"
|
||||||
"github.com/docker/attest/pkg/attestation"
|
"github.com/docker/attest/pkg/attestation"
|
||||||
"github.com/docker/attest/pkg/mirror"
|
|
||||||
"github.com/docker/attest/pkg/oci"
|
"github.com/docker/attest/pkg/oci"
|
||||||
"github.com/docker/attest/pkg/signerverifier"
|
"github.com/docker/attest/pkg/signerverifier"
|
||||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||||
@@ -55,7 +54,7 @@ func ExampleSignStatements_remote() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// push image index with signed attestation-manifests
|
// push image index with signed attestation-manifests
|
||||||
err = mirror.PushIndexToRegistry(signedIndex, ref)
|
err = oci.PushIndexToRegistry(signedIndex, ref)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@@ -66,11 +65,11 @@ func ExampleSignStatements_remote() {
|
|||||||
Add: signedIndex,
|
Add: signedIndex,
|
||||||
Descriptor: v1.Descriptor{
|
Descriptor: v1.Descriptor{
|
||||||
Annotations: map[string]string{
|
Annotations: map[string]string{
|
||||||
oci.OciReferenceTarget: attIdx.Name,
|
oci.OCIReferenceTarget: attIdx.Name,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
err = mirror.SaveIndexAsOCILayout(idx, path)
|
err = oci.SaveIndexAsOCILayout(idx, path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import (
|
|||||||
"github.com/docker/attest/pkg/tuf"
|
"github.com/docker/attest/pkg/tuf"
|
||||||
)
|
)
|
||||||
|
|
||||||
func createTufClient(outputPath string) (*tuf.TufClient, error) {
|
func createTufClient(outputPath string) (*tuf.Client, error) {
|
||||||
// using oci tuf metadata and targets
|
// using oci tuf metadata and targets
|
||||||
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"
|
||||||
@@ -21,7 +21,7 @@ func createTufClient(outputPath string) (*tuf.TufClient, error) {
|
|||||||
// 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"
|
||||||
|
|
||||||
return tuf.NewTufClient(embed.RootStaging.Data, outputPath, metadataURI, targetsURI, tuf.NewMockVersionChecker())
|
return tuf.NewClient(embed.RootStaging.Data, outputPath, metadataURI, targetsURI, tuf.NewMockVersionChecker())
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExampleVerify_remote() {
|
func ExampleVerify_remote() {
|
||||||
@@ -41,11 +41,11 @@ func ExampleVerify_remote() {
|
|||||||
platform := "linux/amd64"
|
platform := "linux/amd64"
|
||||||
|
|
||||||
// configure policy options
|
// configure policy options
|
||||||
opts := &policy.PolicyOptions{
|
opts := &policy.Options{
|
||||||
TufClient: tufClient,
|
TUFClient: tufClient,
|
||||||
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))
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import (
|
|||||||
"github.com/secure-systems-lab/go-securesystemslib/dsse"
|
"github.com/secure-systems-lab/go-securesystemslib/dsse"
|
||||||
)
|
)
|
||||||
|
|
||||||
// this is only relevant if there are (unsigned) in-toto statements
|
// this is only relevant if there are (unsigned) in-toto statements.
|
||||||
func SignStatements(ctx context.Context, idx v1.ImageIndex, signer dsse.SignerVerifier, opts *attestation.SigningOptions) ([]*attestation.AttestationManifest, error) {
|
func SignStatements(ctx context.Context, idx v1.ImageIndex, signer dsse.SignerVerifier, opts *attestation.SigningOptions) ([]*attestation.Manifest, error) {
|
||||||
// extract attestation manifests from index
|
// extract attestation manifests from index
|
||||||
attestationManifests, err := attestation.GetAttestationManifestsFromIndex(idx)
|
attestationManifests, err := attestation.GetAttestationManifestsFromIndex(idx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -10,14 +10,10 @@ import (
|
|||||||
|
|
||||||
"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/mirror"
|
|
||||||
"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/google/go-containerregistry/pkg/registry"
|
"github.com/google/go-containerregistry/pkg/registry"
|
||||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||||
"github.com/google/go-containerregistry/pkg/v1/empty"
|
|
||||||
"github.com/google/go-containerregistry/pkg/v1/layout"
|
|
||||||
"github.com/google/go-containerregistry/pkg/v1/mutate"
|
|
||||||
"github.com/google/go-containerregistry/pkg/v1/static"
|
"github.com/google/go-containerregistry/pkg/v1/static"
|
||||||
"github.com/google/go-containerregistry/pkg/v1/types"
|
"github.com/google/go-containerregistry/pkg/v1/types"
|
||||||
intoto "github.com/in-toto/in-toto-golang/in_toto"
|
intoto "github.com/in-toto/in-toto-golang/in_toto"
|
||||||
@@ -52,7 +48,7 @@ func TestSignVerifyOCILayout(t *testing.T) {
|
|||||||
{"no provenance (replace)", NoProvenanceImage, 0, 2, true},
|
{"no provenance (replace)", NoProvenanceImage, 0, 2, true},
|
||||||
{"no provenance (no replace)", NoProvenanceImage, 2, 2, false},
|
{"no provenance (no replace)", NoProvenanceImage, 2, 2, false},
|
||||||
}
|
}
|
||||||
policyOpts := &policy.PolicyOptions{
|
policyOpts := &policy.Options{
|
||||||
LocalPolicyDir: PassPolicyDir,
|
LocalPolicyDir: PassPolicyDir,
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
@@ -66,21 +62,11 @@ func TestSignVerifyOCILayout(t *testing.T) {
|
|||||||
signedIndex := attIdx.Index
|
signedIndex := attIdx.Index
|
||||||
signedIndex, err = attestation.UpdateIndexImages(signedIndex, signedManifests, attestation.WithReplacedLayers(tc.replace))
|
signedIndex, err = attestation.UpdateIndexImages(signedIndex, signedManifests, attestation.WithReplacedLayers(tc.replace))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
// output signed attestations
|
spec, err := oci.ParseImageSpec(oci.LocalPrefix + outputLayout)
|
||||||
idx := v1.ImageIndex(empty.Index)
|
|
||||||
idx = mutate.AppendManifests(idx, mutate.IndexAddendum{
|
|
||||||
Add: signedIndex,
|
|
||||||
Descriptor: v1.Descriptor{
|
|
||||||
Annotations: map[string]string{
|
|
||||||
oci.OciReferenceTarget: attIdx.Name,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
_, err = layout.Write(outputLayout, idx)
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
src, err := oci.ParseImageSpec("oci://" + outputLayout)
|
err = oci.SaveIndex([]*oci.ImageSpec{spec}, signedIndex, attIdx.Name)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
policy, err := Verify(ctx, src, policyOpts)
|
policy, err := Verify(ctx, spec, policyOpts)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equalf(t, OutcomeSuccess, policy.Outcome, "Policy should have been found")
|
assert.Equalf(t, OutcomeSuccess, policy.Outcome, "Policy should have been found")
|
||||||
|
|
||||||
@@ -120,7 +106,7 @@ func TestAddSignedLayerAnnotations(t *testing.T) {
|
|||||||
testLayer := static.NewLayer(data, types.MediaType(intoto.PayloadType))
|
testLayer := static.NewLayer(data, types.MediaType(intoto.PayloadType))
|
||||||
mediaType := types.OCIManifestSchema1
|
mediaType := types.OCIManifestSchema1
|
||||||
opts := &attestation.SigningOptions{}
|
opts := &attestation.SigningOptions{}
|
||||||
originalLayer := &attestation.AttestationLayer{
|
originalLayer := &attestation.Layer{
|
||||||
Layer: testLayer,
|
Layer: testLayer,
|
||||||
Statement: &intoto.Statement{
|
Statement: &intoto.Statement{
|
||||||
StatementHeader: intoto.StatementHeader{
|
StatementHeader: intoto.StatementHeader{
|
||||||
@@ -130,11 +116,11 @@ func TestAddSignedLayerAnnotations(t *testing.T) {
|
|||||||
Annotations: map[string]string{"test": "test"},
|
Annotations: map[string]string{"test": "test"},
|
||||||
}
|
}
|
||||||
|
|
||||||
manifest := &attestation.AttestationManifest{
|
manifest := &attestation.Manifest{
|
||||||
OriginalDescriptor: &v1.Descriptor{
|
OriginalDescriptor: &v1.Descriptor{
|
||||||
MediaType: mediaType,
|
MediaType: mediaType,
|
||||||
},
|
},
|
||||||
OriginalLayers: []*attestation.AttestationLayer{
|
OriginalLayers: []*attestation.Layer{
|
||||||
originalLayer,
|
originalLayer,
|
||||||
},
|
},
|
||||||
SubjectDescriptor: &v1.Descriptor{},
|
SubjectDescriptor: &v1.Descriptor{},
|
||||||
@@ -239,7 +225,7 @@ func TestSimpleStatementSigning(t *testing.T) {
|
|||||||
indexName := fmt.Sprintf("%s/repo:root", u.Host)
|
indexName := fmt.Sprintf("%s/repo:root", u.Host)
|
||||||
output, err := oci.ParseImageSpecs(indexName)
|
output, err := oci.ParseImageSpecs(indexName)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
err = mirror.SaveReferrers(manifest, output)
|
err = oci.SaveReferrers(manifest, output)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ func (o Outcome) StringForVSA() (string, error) {
|
|||||||
type VerificationResult struct {
|
type VerificationResult struct {
|
||||||
Outcome Outcome
|
Outcome Outcome
|
||||||
Policy *policy.Policy
|
Policy *policy.Policy
|
||||||
Input *policy.PolicyInput
|
Input *policy.Input
|
||||||
VSA *intoto.Statement
|
VSA *intoto.Statement
|
||||||
Violations []policy.Violation
|
Violations []policy.Violation
|
||||||
SubjectDescriptor *v1.Descriptor
|
SubjectDescriptor *v1.Descriptor
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import (
|
|||||||
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.PolicyOptions) (result *VerificationResult, err error) {
|
func Verify(ctx context.Context, src *oci.ImageSpec, opts *policy.Options) (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 {
|
||||||
@@ -61,16 +61,16 @@ func Verify(ctx context.Context, src *oci.ImageSpec, opts *policy.PolicyOptions)
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func toVerificationResult(p *policy.Policy, input *policy.PolicyInput, 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 {
|
||||||
return nil, fmt.Errorf("failed to split digest: %w", err)
|
return nil, fmt.Errorf("failed to split digest: %w", err)
|
||||||
}
|
}
|
||||||
subject := intoto.Subject{
|
subject := intoto.Subject{
|
||||||
Name: input.Purl,
|
Name: input.PURL,
|
||||||
Digest: dgst,
|
Digest: dgst,
|
||||||
}
|
}
|
||||||
resourceUri, err := attestation.ToVSAResourceURI(subject)
|
resourceURI, err := attestation.ToVSAResourceURI(subject)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create resource uri: %w", err)
|
return nil, fmt.Errorf("failed to create resource uri: %w", err)
|
||||||
}
|
}
|
||||||
@@ -103,7 +103,7 @@ func toVerificationResult(p *policy.Policy, input *policy.PolicyInput, result *p
|
|||||||
ID: result.Summary.Verifier,
|
ID: result.Summary.Verifier,
|
||||||
},
|
},
|
||||||
TimeVerified: time.Now().UTC().Format(time.RFC3339),
|
TimeVerified: time.Now().UTC().Format(time.RFC3339),
|
||||||
ResourceUri: resourceUri,
|
ResourceURI: resourceURI,
|
||||||
Policy: attestation.VSAPolicy{URI: result.Summary.PolicyURI},
|
Policy: attestation.VSAPolicy{URI: result.Summary.PolicyURI},
|
||||||
VerificationResult: outcomeStr,
|
VerificationResult: outcomeStr,
|
||||||
VerifiedLevels: result.Summary.SLSALevels,
|
VerifiedLevels: result.Summary.SLSALevels,
|
||||||
@@ -143,9 +143,9 @@ func VerifyAttestations(ctx context.Context, resolver oci.AttestationResolver, p
|
|||||||
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)
|
||||||
}
|
}
|
||||||
input := &policy.PolicyInput{
|
input := &policy.Input{
|
||||||
Digest: digest,
|
Digest: digest,
|
||||||
Purl: purl,
|
PURL: purl,
|
||||||
IsCanonical: canonical,
|
IsCanonical: canonical,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,12 +165,12 @@ func VerifyAttestations(ctx context.Context, resolver oci.AttestationResolver, p
|
|||||||
return verificationResult, nil
|
return verificationResult, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAttestationManifest(subject *v1.Descriptor) (*attestation.AttestationManifest, error) {
|
func NewAttestationManifest(subject *v1.Descriptor) (*attestation.Manifest, error) {
|
||||||
return &attestation.AttestationManifest{
|
return &attestation.Manifest{
|
||||||
OriginalDescriptor: &v1.Descriptor{
|
OriginalDescriptor: &v1.Descriptor{
|
||||||
MediaType: "application/vnd.oci.image.manifest.v1+json",
|
MediaType: "application/vnd.oci.image.manifest.v1+json",
|
||||||
},
|
},
|
||||||
OriginalLayers: []*attestation.AttestationLayer{},
|
OriginalLayers: []*attestation.Layer{},
|
||||||
SubjectDescriptor: subject,
|
SubjectDescriptor: subject,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,20 +10,15 @@ import (
|
|||||||
|
|
||||||
"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/oci"
|
"github.com/docker/attest/pkg/oci"
|
||||||
"github.com/docker/attest/pkg/policy"
|
"github.com/docker/attest/pkg/policy"
|
||||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
|
||||||
"github.com/google/go-containerregistry/pkg/v1/empty"
|
|
||||||
"github.com/google/go-containerregistry/pkg/v1/layout"
|
|
||||||
"github.com/google/go-containerregistry/pkg/v1/mutate"
|
|
||||||
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"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var ExampleAttestation = filepath.Join("..", "..", "test", "testdata", "example_attestation.json")
|
||||||
ExampleAttestation = filepath.Join("..", "..", "test", "testdata", "example_attestation.json")
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
LinuxAMD64 = "linux/amd64"
|
LinuxAMD64 = "linux/amd64"
|
||||||
@@ -33,10 +28,10 @@ func TestVerifyAttestations(t *testing.T) {
|
|||||||
ex, err := os.ReadFile(ExampleAttestation)
|
ex, err := os.ReadFile(ExampleAttestation)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
var env = new(attestation.Envelope)
|
env := new(attestation.Envelope)
|
||||||
err = json.Unmarshal(ex, env)
|
err = json.Unmarshal(ex, env)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
resolver := &test.MockResolver{
|
resolver := &oci.MockResolver{
|
||||||
Envs: []*attestation.Envelope{env},
|
Envs: []*attestation.Envelope{env},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,9 +46,8 @@ func TestVerifyAttestations(t *testing.T) {
|
|||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
|
||||||
mockPE := policy.MockPolicyEvaluator{
|
mockPE := policy.MockPolicyEvaluator{
|
||||||
EvaluateFunc: func(ctx context.Context, resolver oci.AttestationResolver, pctx *policy.Policy, input *policy.PolicyInput) (*policy.Result, error) {
|
EvaluateFunc: func(_ context.Context, _ oci.AttestationResolver, _ *policy.Policy, _ *policy.Input) (*policy.Result, error) {
|
||||||
return policy.AllowedResult(), tc.policyEvaluationError
|
return policy.AllowedResult(), tc.policyEvaluationError
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -87,25 +81,17 @@ func TestVSA(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// output signed attestations
|
// output signed attestations
|
||||||
idx := v1.ImageIndex(empty.Index)
|
spec, err := oci.ParseImageSpec(oci.LocalPrefix+outputLayout, oci.WithPlatform(LinuxAMD64))
|
||||||
idx = mutate.AppendManifests(idx, mutate.IndexAddendum{
|
require.NoError(t, err)
|
||||||
Add: signedIndex,
|
err = oci.SaveIndex([]*oci.ImageSpec{spec}, signedIndex, attIdx.Name)
|
||||||
Descriptor: v1.Descriptor{
|
|
||||||
Annotations: map[string]string{
|
|
||||||
oci.OciReferenceTarget: attIdx.Name,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
_, err = layout.Write(outputLayout, idx)
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
// mocked vsa query should pass
|
// mocked vsa query should pass
|
||||||
policyOpts := &policy.PolicyOptions{
|
policyOpts := &policy.Options{
|
||||||
LocalPolicyDir: PassPolicyDir,
|
LocalPolicyDir: PassPolicyDir,
|
||||||
|
AttestationStyle: config.AttestationStyleAttached,
|
||||||
}
|
}
|
||||||
src, err := oci.ParseImageSpec("oci://"+outputLayout, oci.WithPlatform(LinuxAMD64))
|
results, err := Verify(ctx, spec, policyOpts)
|
||||||
require.NoError(t, err)
|
|
||||||
results, err := Verify(ctx, src, policyOpts)
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, OutcomeSuccess, results.Outcome)
|
assert.Equal(t, OutcomeSuccess, results.Outcome)
|
||||||
assert.Empty(t, results.Violations)
|
assert.Empty(t, results.Violations)
|
||||||
@@ -120,7 +106,8 @@ func TestVSA(t *testing.T) {
|
|||||||
assert.Len(t, results.VSA.Subject, 1)
|
assert.Len(t, results.VSA.Subject, 1)
|
||||||
|
|
||||||
require.IsType(t, attestation.VSAPredicate{}, results.VSA.Predicate)
|
require.IsType(t, attestation.VSAPredicate{}, results.VSA.Predicate)
|
||||||
attestationPredicate := results.VSA.Predicate.(attestation.VSAPredicate)
|
attestationPredicate, ok := results.VSA.Predicate.(attestation.VSAPredicate)
|
||||||
|
require.True(t, ok)
|
||||||
|
|
||||||
assert.Equal(t, "PASSED", attestationPredicate.VerificationResult)
|
assert.Equal(t, "PASSED", attestationPredicate.VerificationResult)
|
||||||
assert.Equal(t, "docker-official-images", attestationPredicate.Verifier.ID)
|
assert.Equal(t, "docker-official-images", attestationPredicate.Verifier.ID)
|
||||||
@@ -144,25 +131,17 @@ func TestVerificationFailure(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// output signed attestations
|
// output signed attestations
|
||||||
idx := v1.ImageIndex(empty.Index)
|
spec, err := oci.ParseImageSpec(oci.LocalPrefix+outputLayout, oci.WithPlatform(LinuxAMD64))
|
||||||
idx = mutate.AppendManifests(idx, mutate.IndexAddendum{
|
require.NoError(t, err)
|
||||||
Add: signedIndex,
|
err = oci.SaveIndex([]*oci.ImageSpec{spec}, signedIndex, attIdx.Name)
|
||||||
Descriptor: v1.Descriptor{
|
|
||||||
Annotations: map[string]string{
|
|
||||||
oci.OciReferenceTarget: attIdx.Name,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
_, err = layout.Write(outputLayout, idx)
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
// mocked vsa query should fail
|
// mocked vsa query should fail
|
||||||
policyOpts := &policy.PolicyOptions{
|
policyOpts := &policy.Options{
|
||||||
LocalPolicyDir: FailPolicyDir,
|
LocalPolicyDir: FailPolicyDir,
|
||||||
|
AttestationStyle: config.AttestationStyleAttached,
|
||||||
}
|
}
|
||||||
src, err := oci.ParseImageSpec("oci://"+outputLayout, oci.WithPlatform(LinuxAMD64))
|
results, err := Verify(ctx, spec, policyOpts)
|
||||||
require.NoError(t, err)
|
|
||||||
results, err := Verify(ctx, src, policyOpts)
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, OutcomeFailure, results.Outcome)
|
assert.Equal(t, OutcomeFailure, results.Outcome)
|
||||||
assert.Len(t, results.Violations, 1)
|
assert.Len(t, results.Violations, 1)
|
||||||
@@ -177,7 +156,8 @@ func TestVerificationFailure(t *testing.T) {
|
|||||||
assert.Len(t, results.VSA.Subject, 1)
|
assert.Len(t, results.VSA.Subject, 1)
|
||||||
|
|
||||||
require.IsType(t, attestation.VSAPredicate{}, results.VSA.Predicate)
|
require.IsType(t, attestation.VSAPredicate{}, results.VSA.Predicate)
|
||||||
attestationPredicate := results.VSA.Predicate.(attestation.VSAPredicate)
|
attestationPredicate, ok := results.VSA.Predicate.(attestation.VSAPredicate)
|
||||||
|
require.True(t, ok)
|
||||||
|
|
||||||
assert.Equal(t, "FAILED", attestationPredicate.VerificationResult)
|
assert.Equal(t, "FAILED", attestationPredicate.VerificationResult)
|
||||||
assert.Equal(t, "docker-official-images", attestationPredicate.Verifier.ID)
|
assert.Equal(t, "docker-official-images", attestationPredicate.Verifier.ID)
|
||||||
@@ -225,24 +205,15 @@ func TestSignVerify(t *testing.T) {
|
|||||||
imageName = attIdx.Name
|
imageName = attIdx.Name
|
||||||
}
|
}
|
||||||
// output signed attestations
|
// output signed attestations
|
||||||
idx := v1.ImageIndex(empty.Index)
|
spec, err := oci.ParseImageSpec(oci.LocalPrefix+outputLayout, oci.WithPlatform(LinuxAMD64))
|
||||||
idx = mutate.AppendManifests(idx, mutate.IndexAddendum{
|
require.NoError(t, err)
|
||||||
Add: signedIndex,
|
err = oci.SaveIndex([]*oci.ImageSpec{spec}, signedIndex, imageName)
|
||||||
Descriptor: v1.Descriptor{
|
require.NoError(t, err)
|
||||||
Annotations: map[string]string{
|
|
||||||
oci.OciReferenceTarget: imageName,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
_, err = layout.Write(outputLayout, idx)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
policyOpts := &policy.PolicyOptions{
|
policyOpts := &policy.Options{
|
||||||
LocalPolicyDir: tc.policyDir,
|
LocalPolicyDir: tc.policyDir,
|
||||||
}
|
}
|
||||||
src, err := oci.ParseImageSpec("oci://"+outputLayout, oci.WithPlatform(LinuxAMD64))
|
results, err := Verify(ctx, spec, policyOpts)
|
||||||
require.NoError(t, err)
|
|
||||||
results, err := Verify(ctx, src, policyOpts)
|
|
||||||
if tc.expectError {
|
if tc.expectError {
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
return
|
return
|
||||||
@@ -253,7 +224,7 @@ func TestSignVerify(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
expectedPURL, _, err := oci.RefToPURL(attIdx.Name, platform)
|
expectedPURL, _, err := oci.RefToPURL(attIdx.Name, platform)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, expectedPURL, results.Input.Purl)
|
assert.Equal(t, expectedPURL, results.Input.PURL)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package attestation
|
package attestation
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -17,19 +18,21 @@ import (
|
|||||||
"github.com/secure-systems-lab/go-securesystemslib/dsse"
|
"github.com/secure-systems-lab/go-securesystemslib/dsse"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetAttestationManifestsFromIndex extracts all attestation manifests from an index
|
// GetAttestationManifestsFromIndex extracts all attestation manifests from an index.
|
||||||
func GetAttestationManifestsFromIndex(index v1.ImageIndex) ([]*AttestationManifest, error) {
|
func GetAttestationManifestsFromIndex(index v1.ImageIndex) ([]*Manifest, error) {
|
||||||
idx, err := index.IndexManifest()
|
idx, err := index.IndexManifest()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to extract IndexManifest from ImageIndex: %w", err)
|
return nil, fmt.Errorf("failed to extract IndexManifest from ImageIndex: %w", err)
|
||||||
}
|
}
|
||||||
subjects := make(map[string]*v1.Descriptor)
|
subjects := make(map[string]*v1.Descriptor)
|
||||||
for _, subject := range idx.Manifests {
|
for i := range idx.Manifests {
|
||||||
subjects[subject.Digest.String()] = &subject
|
subject := &idx.Manifests[i]
|
||||||
|
subjects[subject.Digest.String()] = subject
|
||||||
}
|
}
|
||||||
|
|
||||||
var attestationManifests []*AttestationManifest
|
var attestationManifests []*Manifest
|
||||||
for _, desc := range idx.Manifests {
|
for i := range idx.Manifests {
|
||||||
|
desc := idx.Manifests[i]
|
||||||
if desc.Annotations[DockerReferenceType] == AttestationManifestType {
|
if desc.Annotations[DockerReferenceType] == AttestationManifestType {
|
||||||
subject := subjects[desc.Annotations[DockerReferenceDigest]]
|
subject := subjects[desc.Annotations[DockerReferenceDigest]]
|
||||||
if subject == nil {
|
if subject == nil {
|
||||||
@@ -44,22 +47,23 @@ func GetAttestationManifestsFromIndex(index v1.ImageIndex) ([]*AttestationManife
|
|||||||
return nil, fmt.Errorf("failed to get attestations from image: %w", err)
|
return nil, fmt.Errorf("failed to get attestations from image: %w", err)
|
||||||
}
|
}
|
||||||
attestationManifests = append(attestationManifests,
|
attestationManifests = append(attestationManifests,
|
||||||
&AttestationManifest{
|
&Manifest{
|
||||||
OriginalDescriptor: &desc,
|
OriginalDescriptor: &desc,
|
||||||
SubjectDescriptor: subject,
|
SubjectDescriptor: subject,
|
||||||
OriginalLayers: attestationLayers})
|
OriginalLayers: attestationLayers,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return attestationManifests, nil
|
return attestationManifests, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAttestationsFromImage extracts all attestation layers from an image
|
// GetAttestationsFromImage extracts all attestation layers from an image.
|
||||||
func GetAttestationsFromImage(image v1.Image) ([]*AttestationLayer, error) {
|
func GetAttestationsFromImage(image v1.Image) ([]*Layer, error) {
|
||||||
layers, err := image.Layers()
|
layers, err := image.Layers()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to extract layers from image: %w", err)
|
return nil, fmt.Errorf("failed to extract layers from image: %w", err)
|
||||||
}
|
}
|
||||||
var attestationLayers []*AttestationLayer
|
var attestationLayers []*Layer
|
||||||
for _, layer := range layers {
|
for _, layer := range layers {
|
||||||
// parse layer blob as json
|
// parse layer blob as json
|
||||||
r, err := layer.Uncompressed()
|
r, err := layer.Uncompressed()
|
||||||
@@ -78,19 +82,19 @@ func GetAttestationsFromImage(image v1.Image) ([]*AttestationLayer, error) {
|
|||||||
// copy original annotations
|
// copy original annotations
|
||||||
ann := maps.Clone(layerDesc.Annotations)
|
ann := maps.Clone(layerDesc.Annotations)
|
||||||
// only decode intoto statements
|
// only decode intoto statements
|
||||||
var stmt = new(intoto.Statement)
|
stmt := new(intoto.Statement)
|
||||||
if mt == types.MediaType(intoto.PayloadType) {
|
if mt == types.MediaType(intoto.PayloadType) {
|
||||||
err = json.NewDecoder(r).Decode(&stmt)
|
err = json.NewDecoder(r).Decode(&stmt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to decode statement layer contents: %w", err)
|
return nil, fmt.Errorf("failed to decode statement layer contents: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
attestationLayers = append(attestationLayers, &AttestationLayer{Layer: layer, Statement: stmt, Annotations: ann})
|
attestationLayers = append(attestationLayers, &Layer{Layer: layer, Statement: stmt, Annotations: ann})
|
||||||
}
|
}
|
||||||
return attestationLayers, nil
|
return attestationLayers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (manifest *AttestationManifest) AddAttestation(ctx context.Context, signer dsse.SignerVerifier, statement *intoto.Statement, opts *SigningOptions) error {
|
func (manifest *Manifest) AddAttestation(ctx context.Context, signer dsse.SignerVerifier, statement *intoto.Statement, opts *SigningOptions) error {
|
||||||
layer, err := createSignedImageLayer(ctx, statement, signer, opts)
|
layer, err := createSignedImageLayer(ctx, statement, signer, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create signed layer: %w", err)
|
return fmt.Errorf("failed to create signed layer: %w", err)
|
||||||
@@ -99,7 +103,7 @@ func (manifest *AttestationManifest) AddAttestation(ctx context.Context, signer
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func createSignedImageLayer(ctx context.Context, statement *intoto.Statement, signer dsse.SignerVerifier, opts *SigningOptions) (*AttestationLayer, error) {
|
func createSignedImageLayer(ctx context.Context, statement *intoto.Statement, signer dsse.SignerVerifier, opts *SigningOptions) (*Layer, error) {
|
||||||
// sign the statement
|
// sign the statement
|
||||||
env, err := SignInTotoStatement(ctx, statement, signer, opts)
|
env, err := SignInTotoStatement(ctx, statement, signer, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -114,7 +118,7 @@ func createSignedImageLayer(ctx context.Context, statement *intoto.Statement, si
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to marshal envelope: %w", err)
|
return nil, fmt.Errorf("failed to marshal envelope: %w", err)
|
||||||
}
|
}
|
||||||
return &AttestationLayer{
|
return &Layer{
|
||||||
Statement: statement,
|
Statement: statement,
|
||||||
Annotations: map[string]string{
|
Annotations: map[string]string{
|
||||||
InTotoPredicateType: statement.PredicateType,
|
InTotoPredicateType: statement.PredicateType,
|
||||||
@@ -138,10 +142,10 @@ func SignInTotoStatement(ctx context.Context, statement *intoto.Statement, signe
|
|||||||
|
|
||||||
func UpdateIndexImage(
|
func UpdateIndexImage(
|
||||||
idx v1.ImageIndex,
|
idx v1.ImageIndex,
|
||||||
manifest *AttestationManifest,
|
manifest *Manifest,
|
||||||
options ...func(*AttestationManifestImageOptions) error) (v1.ImageIndex, error) {
|
options ...func(*ManifestImageOptions) error,
|
||||||
|
) (v1.ImageIndex, error) {
|
||||||
image, err := manifest.BuildAttestationImage(options...)
|
image, err := manifest.BuildAttestationImage(options...)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to build image: %w", err)
|
return nil, fmt.Errorf("failed to build image: %w", err)
|
||||||
}
|
}
|
||||||
@@ -163,7 +167,7 @@ func UpdateIndexImage(
|
|||||||
return idx, nil
|
return idx, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func UpdateIndexImages(idx v1.ImageIndex, manifest []*AttestationManifest, options ...func(*AttestationManifestImageOptions) error) (v1.ImageIndex, error) {
|
func UpdateIndexImages(idx v1.ImageIndex, manifest []*Manifest, options ...func(*ManifestImageOptions) error) (v1.ImageIndex, error) {
|
||||||
var err error
|
var err error
|
||||||
for _, m := range manifest {
|
for _, m := range manifest {
|
||||||
idx, err = UpdateIndexImage(idx, m, options...)
|
idx, err = UpdateIndexImage(idx, m, options...)
|
||||||
@@ -174,8 +178,8 @@ func UpdateIndexImages(idx v1.ImageIndex, manifest []*AttestationManifest, optio
|
|||||||
return idx, nil
|
return idx, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func newOptions(options ...func(*AttestationManifestImageOptions) error) (*AttestationManifestImageOptions, error) {
|
func newOptions(options ...func(*ManifestImageOptions) error) (*ManifestImageOptions, error) {
|
||||||
opts := &AttestationManifestImageOptions{}
|
opts := &ManifestImageOptions{}
|
||||||
for _, opt := range options {
|
for _, opt := range options {
|
||||||
err := opt(opts)
|
err := opt(opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -185,22 +189,22 @@ func newOptions(options ...func(*AttestationManifestImageOptions) error) (*Attes
|
|||||||
return opts, nil
|
return opts, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func WithoutSubject(skipSubject bool) func(*AttestationManifestImageOptions) error {
|
func WithoutSubject(skipSubject bool) func(*ManifestImageOptions) error {
|
||||||
return func(r *AttestationManifestImageOptions) error {
|
return func(r *ManifestImageOptions) error {
|
||||||
r.skipSubject = skipSubject
|
r.skipSubject = skipSubject
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func WithReplacedLayers(replaceLayers bool) func(*AttestationManifestImageOptions) error {
|
func WithReplacedLayers(replaceLayers bool) func(*ManifestImageOptions) error {
|
||||||
return func(r *AttestationManifestImageOptions) error {
|
return func(r *ManifestImageOptions) error {
|
||||||
r.replaceLayers = replaceLayers
|
r.replaceLayers = replaceLayers
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// build an image with signed attestations, optionally replacing existing layers with signed layers
|
// build an image with signed attestations, optionally replacing existing layers with signed layers.
|
||||||
func (manifest *AttestationManifest) BuildAttestationImage(options ...func(*AttestationManifestImageOptions) error) (v1.Image, error) {
|
func (manifest *Manifest) BuildAttestationImage(options ...func(*ManifestImageOptions) error) (v1.Image, error) {
|
||||||
opts, err := newOptions(options...)
|
opts, err := newOptions(options...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create options: %w", err)
|
return nil, fmt.Errorf("failed to create options: %w", err)
|
||||||
@@ -218,12 +222,12 @@ func (manifest *AttestationManifest) BuildAttestationImage(options ...func(*Atte
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//add existing layers if they've not been signed or we're not replacing them
|
// add existing layers if they've not been signed or we're not replacing them
|
||||||
if !found || !opts.replaceLayers {
|
if !found || !opts.replaceLayers {
|
||||||
resultLayers = append(resultLayers, existingLayer)
|
resultLayers = append(resultLayers, existingLayer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// so taht we attach all attestations to a single attestations image - as per current buildkit
|
// so that we attach all attestations to a single attestations image - as per current buildkit
|
||||||
opts.laxReferrers = true
|
opts.laxReferrers = true
|
||||||
newImg, err := buildImage(resultLayers, manifest.OriginalDescriptor, manifest.SubjectDescriptor, opts)
|
newImg, err := buildImage(resultLayers, manifest.OriginalDescriptor, manifest.SubjectDescriptor, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -232,12 +236,12 @@ func (manifest *AttestationManifest) BuildAttestationImage(options ...func(*Atte
|
|||||||
return newImg, nil
|
return newImg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// build an image per attestation (layer) suitable for use as Referrers
|
// build an image per attestation (layer) suitable for use as Referrers.
|
||||||
func (manifest *AttestationManifest) BuildReferringArtifacts() ([]v1.Image, error) {
|
func (manifest *Manifest) BuildReferringArtifacts() ([]v1.Image, error) {
|
||||||
var images []v1.Image
|
var images []v1.Image
|
||||||
for _, layer := range manifest.SignedLayers {
|
for _, layer := range manifest.SignedLayers {
|
||||||
opts := &AttestationManifestImageOptions{}
|
opts := &ManifestImageOptions{}
|
||||||
newImg, err := buildImage([]*AttestationLayer{layer}, manifest.OriginalDescriptor, manifest.SubjectDescriptor, opts)
|
newImg, err := buildImage([]*Layer{layer}, manifest.OriginalDescriptor, manifest.SubjectDescriptor, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to build image: %w", err)
|
return nil, fmt.Errorf("failed to build image: %w", err)
|
||||||
}
|
}
|
||||||
@@ -246,15 +250,15 @@ func (manifest *AttestationManifest) BuildReferringArtifacts() ([]v1.Image, erro
|
|||||||
return images, nil
|
return images, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// build and image containing only layers
|
// build an image containing only layers.
|
||||||
func buildImage(layers []*AttestationLayer, manifest *v1.Descriptor, subject *v1.Descriptor, opts *AttestationManifestImageOptions) (v1.Image, error) {
|
func buildImage(layers []*Layer, manifest *v1.Descriptor, subject *v1.Descriptor, opts *ManifestImageOptions) (v1.Image, error) {
|
||||||
newImg := empty.Image
|
newImg := empty.Image
|
||||||
var err error
|
var err error
|
||||||
if len(layers) == 0 {
|
if len(layers) == 0 {
|
||||||
return nil, fmt.Errorf("no layers supplied to build image")
|
return nil, fmt.Errorf("no layers supplied to build image")
|
||||||
}
|
}
|
||||||
// NB: if we add the subject before the layers, it does not end up being computed/serialised in the output for some reason
|
// NB: if we add the subject before the layers, it does not end up being computed/serialized in the output for some reason
|
||||||
//TODO - recreate this bug and push upstream
|
// TODO - recreate this bug and push upstream
|
||||||
for _, layer := range layers {
|
for _, layer := range layers {
|
||||||
add := mutate.Addendum{
|
add := mutate.Addendum{
|
||||||
Layer: layer.Layer,
|
Layer: layer.Layer,
|
||||||
@@ -284,7 +288,11 @@ func buildImage(layers []*AttestationLayer, manifest *v1.Descriptor, subject *v1
|
|||||||
// see note above - must be added after the layers!
|
// see note above - must be added after the layers!
|
||||||
if !opts.skipSubject {
|
if !opts.skipSubject {
|
||||||
subject.Platform = nil
|
subject.Platform = nil
|
||||||
newImg = mutate.Subject(newImg, *subject).(v1.Image)
|
ok := false
|
||||||
|
newImg, ok = mutate.Subject(newImg, *subject).(v1.Image)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("failed to set subject: %w", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if !opts.laxReferrers {
|
if !opts.laxReferrers {
|
||||||
// as per https://github.com/opencontainers/image-spec/blob/main/manifest.md#guidance-for-an-empty-descriptor
|
// as per https://github.com/opencontainers/image-spec/blob/main/manifest.md#guidance-for-an-empty-descriptor
|
||||||
@@ -322,3 +330,12 @@ func (i *EmptyConfigImage) RawManifest() ([]byte, error) {
|
|||||||
}
|
}
|
||||||
return json.Marshal(mf)
|
return json.Marshal(mf)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *EmptyConfigImage) Digest() (v1.Hash, error) {
|
||||||
|
mb, err := i.RawManifest()
|
||||||
|
if err != nil {
|
||||||
|
return v1.Hash{}, err
|
||||||
|
}
|
||||||
|
digest, _, err := v1.SHA256(bytes.NewReader(mb))
|
||||||
|
return digest, err
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
|
|
||||||
"github.com/docker/attest/pkg/attest"
|
"github.com/docker/attest/pkg/attest"
|
||||||
"github.com/docker/attest/pkg/attestation"
|
"github.com/docker/attest/pkg/attestation"
|
||||||
"github.com/docker/attest/pkg/mirror"
|
|
||||||
"github.com/docker/attest/pkg/oci"
|
"github.com/docker/attest/pkg/oci"
|
||||||
"github.com/docker/attest/pkg/signerverifier"
|
"github.com/docker/attest/pkg/signerverifier"
|
||||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||||
@@ -14,7 +13,7 @@ import (
|
|||||||
"github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/common"
|
"github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ExampleAttestationManifest() {
|
func ExampleManifest() {
|
||||||
// configure signerverifier
|
// configure signerverifier
|
||||||
// local signer (unsafe for production)
|
// local signer (unsafe for production)
|
||||||
signer, err := signerverifier.GenKeyPair()
|
signer, err := signerverifier.GenKeyPair()
|
||||||
@@ -55,7 +54,7 @@ func ExampleAttestationManifest() {
|
|||||||
ID: "test-verifier",
|
ID: "test-verifier",
|
||||||
},
|
},
|
||||||
TimeVerified: time.Now().UTC().Format(time.RFC3339),
|
TimeVerified: time.Now().UTC().Format(time.RFC3339),
|
||||||
ResourceUri: "some-uri",
|
ResourceURI: "some-uri",
|
||||||
Policy: attestation.VSAPolicy{URI: "some-uri"},
|
Policy: attestation.VSAPolicy{URI: "some-uri"},
|
||||||
VerificationResult: "PASSED",
|
VerificationResult: "PASSED",
|
||||||
VerifiedLevels: []string{"SLSA_BUILD_LEVEL_1"},
|
VerifiedLevels: []string{"SLSA_BUILD_LEVEL_1"},
|
||||||
@@ -80,7 +79,7 @@ func ExampleAttestationManifest() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// save the manifest to the registry as a referrers artifact
|
// save the manifest to the registry as a referrers artifact
|
||||||
err = mirror.SaveReferrers(manifest, output)
|
err = oci.SaveReferrers(manifest, output)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,14 +8,15 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/attest/internal/test"
|
"github.com/docker/attest/internal/test"
|
||||||
|
"github.com/docker/attest/internal/util"
|
||||||
"github.com/docker/attest/pkg/attest"
|
"github.com/docker/attest/pkg/attest"
|
||||||
"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/mirror"
|
|
||||||
"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/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/empty"
|
||||||
"github.com/google/go-containerregistry/pkg/v1/remote"
|
"github.com/google/go-containerregistry/pkg/v1/remote"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@@ -57,7 +58,7 @@ func TestAttestationReferenceTypes(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "attached attestations, referrers repo (mismatched args)",
|
name: "attached attestations, referrers repo (mismatched args)",
|
||||||
server: httptest.NewServer(registry.New(registry.WithReferrersSupport(true))),
|
server: httptest.NewServer(registry.New(registry.WithReferrersSupport(true))),
|
||||||
expectFailure: true, //mismatched args
|
expectFailure: true, // mismatched args
|
||||||
attestationSource: config.AttestationStyleAttached,
|
attestationSource: config.AttestationStyleAttached,
|
||||||
referrersRepo: "referrers",
|
referrersRepo: "referrers",
|
||||||
},
|
},
|
||||||
@@ -112,14 +113,14 @@ func TestAttestationReferenceTypes(t *testing.T) {
|
|||||||
|
|
||||||
// push subject image so that it can be resolved
|
// push subject image so that it can be resolved
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
err = mirror.PushIndexToRegistry(attIdx.Index, indexName)
|
err = oci.PushIndexToRegistry(attIdx.Index, indexName)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// upload referrers
|
// upload referrers
|
||||||
output, err := oci.ParseImageSpec(outputRepo)
|
output, err := oci.ParseImageSpec(outputRepo)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
for _, attIdx := range signedManifests {
|
for _, attIdx := range signedManifests {
|
||||||
err = mirror.SaveReferrers(attIdx, []*oci.ImageSpec{output})
|
err = oci.SaveReferrers(attIdx, []*oci.ImageSpec{output})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,7 +138,7 @@ func TestAttestationReferenceTypes(t *testing.T) {
|
|||||||
ref = fmt.Sprintf("%s/repo@%s", u.Host, idxDigest.String())
|
ref = fmt.Sprintf("%s/repo@%s", u.Host, idxDigest.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
policyOpts := &policy.PolicyOptions{
|
policyOpts := &policy.Options{
|
||||||
LocalPolicyDir: LocalPolicy,
|
LocalPolicyDir: LocalPolicy,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,12 +202,12 @@ func TestReferencesInDifferentRepo(t *testing.T) {
|
|||||||
} {
|
} {
|
||||||
server := tc.server
|
server := tc.server
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
serverUrl, err := url.Parse(server.URL)
|
serverURL, err := url.Parse(server.URL)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
refServer := tc.refServer
|
refServer := tc.refServer
|
||||||
defer refServer.Close()
|
defer refServer.Close()
|
||||||
refServerUrl, err := url.Parse(refServer.URL)
|
refServerURL, err := url.Parse(refServer.URL)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
opts := &attestation.SigningOptions{
|
opts := &attestation.SigningOptions{
|
||||||
@@ -215,8 +216,8 @@ func TestReferencesInDifferentRepo(t *testing.T) {
|
|||||||
attIdx, err := oci.IndexFromPath(UnsignedTestImage)
|
attIdx, err := oci.IndexFromPath(UnsignedTestImage)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
indexName := fmt.Sprintf("%s/%s:latest", serverUrl.Host, repoName)
|
indexName := fmt.Sprintf("%s/%s:latest", serverURL.Host, repoName)
|
||||||
err = mirror.PushIndexToRegistry(attIdx.Index, indexName)
|
err = oci.PushIndexToRegistry(attIdx.Index, indexName)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
signedManifests, err := attest.SignStatements(ctx, attIdx.Index, signer, opts)
|
signedManifests, err := attest.SignStatements(ctx, attIdx.Index, signer, opts)
|
||||||
@@ -227,12 +228,12 @@ func TestReferencesInDifferentRepo(t *testing.T) {
|
|||||||
// push references using subject-digest.att convention
|
// push references using subject-digest.att convention
|
||||||
image, err := signedManifest.BuildAttestationImage()
|
image, err := signedManifest.BuildAttestationImage()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
err = mirror.PushImageToRegistry(image, fmt.Sprintf("%s/%s:tag-does-not-matter", refServerUrl.Host, repoName))
|
err = oci.PushImageToRegistry(image, fmt.Sprintf("%s/%s:tag-does-not-matter", refServerURL.Host, repoName))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
refServer := tc.refServer
|
refServer := tc.refServer
|
||||||
defer refServer.Close()
|
defer refServer.Close()
|
||||||
refServerUrl, err := url.Parse(refServer.URL)
|
refServerURL, err := url.Parse(refServer.URL)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
opts := &attestation.SigningOptions{
|
opts := &attestation.SigningOptions{
|
||||||
@@ -241,8 +242,8 @@ func TestReferencesInDifferentRepo(t *testing.T) {
|
|||||||
attIdx, err := oci.IndexFromPath(UnsignedTestImage)
|
attIdx, err := oci.IndexFromPath(UnsignedTestImage)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
indexName := fmt.Sprintf("%s/%s:latest", serverUrl.Host, repoName)
|
indexName := fmt.Sprintf("%s/%s:latest", serverURL.Host, repoName)
|
||||||
err = mirror.PushIndexToRegistry(attIdx.Index, indexName)
|
err = oci.PushIndexToRegistry(attIdx.Index, indexName)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
signedManifests, err := attest.SignStatements(ctx, attIdx.Index, signer, opts)
|
signedManifests, err := attest.SignStatements(ctx, attIdx.Index, signer, opts)
|
||||||
@@ -254,20 +255,20 @@ func TestReferencesInDifferentRepo(t *testing.T) {
|
|||||||
imgs, err := mf.BuildReferringArtifacts()
|
imgs, err := mf.BuildReferringArtifacts()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
for _, img := range imgs {
|
for _, img := range imgs {
|
||||||
err = mirror.PushImageToRegistry(img, fmt.Sprintf("%s/%s:tag-does-not-matter", refServerUrl.Host, repoName))
|
err = oci.PushImageToRegistry(img, fmt.Sprintf("%s/%s:tag-does-not-matter", refServerURL.Host, repoName))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mfs2, err := attIdx.Index.IndexManifest()
|
mfs2, err := attIdx.Index.IndexManifest()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
for _, mf := range mfs2.Manifests {
|
for _, mf := range mfs2.Manifests {
|
||||||
//skip signed/unsigned attestations
|
// skip signed/unsigned attestations
|
||||||
if mf.Annotations[attestation.DockerReferenceType] == attestation.AttestationManifestType {
|
if mf.Annotations[attestation.DockerReferenceType] == attestation.AttestationManifestType {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// can evaluate policy using referrers in a different repo
|
// can evaluate policy using referrers in a different repo
|
||||||
referencedImage := fmt.Sprintf("%s@%s", indexName, mf.Digest.String())
|
referencedImage := fmt.Sprintf("%s@%s", indexName, mf.Digest.String())
|
||||||
policyOpts := &policy.PolicyOptions{
|
policyOpts := &policy.Options{
|
||||||
LocalPolicyDir: PassPolicyDir,
|
LocalPolicyDir: PassPolicyDir,
|
||||||
}
|
}
|
||||||
src, err := oci.ParseImageSpec(referencedImage)
|
src, err := oci.ParseImageSpec(referencedImage)
|
||||||
@@ -285,7 +286,7 @@ func TestCorrectArtifactTypeInTagFallback(t *testing.T) {
|
|||||||
server := httptest.NewServer(registry.New())
|
server := httptest.NewServer(registry.New())
|
||||||
|
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
serverUrl, err := url.Parse(server.URL)
|
serverURL, err := url.Parse(server.URL)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
repoName := "repo"
|
repoName := "repo"
|
||||||
@@ -296,8 +297,8 @@ func TestCorrectArtifactTypeInTagFallback(t *testing.T) {
|
|||||||
attIdx, err := oci.IndexFromPath(UnsignedTestImage)
|
attIdx, err := oci.IndexFromPath(UnsignedTestImage)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
indexName := fmt.Sprintf("%s/%s:latest", serverUrl.Host, repoName)
|
indexName := fmt.Sprintf("%s/%s:latest", serverURL.Host, repoName)
|
||||||
err = mirror.PushIndexToRegistry(attIdx.Index, indexName)
|
err = oci.PushIndexToRegistry(attIdx.Index, indexName)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
signedManifests, err := attest.SignStatements(ctx, attIdx.Index, signer, opts)
|
signedManifests, err := attest.SignStatements(ctx, attIdx.Index, signer, opts)
|
||||||
@@ -308,12 +309,12 @@ func TestCorrectArtifactTypeInTagFallback(t *testing.T) {
|
|||||||
imgs, err := mf.BuildReferringArtifacts()
|
imgs, err := mf.BuildReferringArtifacts()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
for _, img := range imgs {
|
for _, img := range imgs {
|
||||||
err = mirror.PushImageToRegistry(img, fmt.Sprintf("%s/%s:tag-does-not-matter", serverUrl.Host, repoName))
|
err = oci.PushImageToRegistry(img, fmt.Sprintf("%s/%s:tag-does-not-matter", serverURL.Host, repoName))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
mf, err := img.Manifest()
|
mf, err := img.Manifest()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
subject := mf.Subject
|
subject := mf.Subject
|
||||||
subjectRef, err := name.ParseReference(fmt.Sprintf("%s/%s:sha256-%s", serverUrl.Host, repoName, subject.Digest.Hex))
|
subjectRef, err := name.ParseReference(fmt.Sprintf("%s/%s:sha256-%s", serverURL.Host, repoName, subject.Digest.Hex))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
idx, err := remote.Index(subjectRef)
|
idx, err := remote.Index(subjectRef)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -326,3 +327,14 @@ func TestCorrectArtifactTypeInTagFallback(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEmptyConfigImageDigest(t *testing.T) {
|
||||||
|
empty := empty.Image
|
||||||
|
img := attestation.EmptyConfigImage{empty}
|
||||||
|
mf, err := img.RawManifest()
|
||||||
|
require.NoError(t, err)
|
||||||
|
hash := util.SHA256Hex(mf)
|
||||||
|
digest, err := img.Digest()
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, digest.Hex, hash)
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
"github.com/secure-systems-lab/go-securesystemslib/dsse"
|
"github.com/secure-systems-lab/go-securesystemslib/dsse"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SignDSSE signs a payload with a given signer and uploads the signature to the transparency log
|
// SignDSSE signs a payload with a given signer and uploads the signature to the transparency log.
|
||||||
func SignDSSE(ctx context.Context, payload []byte, signer dsse.SignerVerifier, opts *SigningOptions) (*Envelope, error) {
|
func SignDSSE(ctx context.Context, payload []byte, signer dsse.SignerVerifier, opts *SigningOptions) (*Envelope, error) {
|
||||||
payloadType := intoto.PayloadType
|
payloadType := intoto.PayloadType
|
||||||
env := new(Envelope)
|
env := new(Envelope)
|
||||||
@@ -28,13 +28,13 @@ func SignDSSE(ctx context.Context, payload []byte, signer dsse.SignerVerifier, o
|
|||||||
}
|
}
|
||||||
|
|
||||||
// get Key ID from signer
|
// get Key ID from signer
|
||||||
keyId, err := signer.KeyID()
|
keyID, err := signer.KeyID()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error getting public key ID: %w", err)
|
return nil, fmt.Errorf("error getting public key ID: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
dsseSig := Signature{
|
dsseSig := &Signature{
|
||||||
KeyID: keyId,
|
KeyID: keyID,
|
||||||
Sig: base64Encoding.EncodeToString(sig),
|
Sig: base64Encoding.EncodeToString(sig),
|
||||||
}
|
}
|
||||||
if !opts.SkipTL {
|
if !opts.SkipTL {
|
||||||
@@ -42,22 +42,22 @@ func SignDSSE(ctx context.Context, payload []byte, signer dsse.SignerVerifier, o
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to log to rekor: %w", err)
|
return nil, fmt.Errorf("failed to log to rekor: %w", err)
|
||||||
}
|
}
|
||||||
dsseSig.Extension = *ext
|
dsseSig.Extension = ext
|
||||||
}
|
}
|
||||||
// add signature to dsse envelope
|
// add signature to dsse envelope
|
||||||
env.Signatures = []Signature{dsseSig}
|
env.Signatures = []*Signature{dsseSig}
|
||||||
|
|
||||||
return env, nil
|
return env, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns a new envelope with the transparency log entry added to the signature extension
|
// returns a new envelope with the transparency log entry added to the signature extension.
|
||||||
func logSignature(ctx context.Context, t tlog.TL, sig *[]byte, encPayload *[]byte, signer dsse.SignerVerifier) (*Extension, error) {
|
func logSignature(ctx context.Context, t tlog.TL, sig *[]byte, encPayload *[]byte, signer dsse.SignerVerifier) (*Extension, error) {
|
||||||
// get Key ID from signer
|
// get Key ID from signer
|
||||||
keyId, err := signer.KeyID()
|
keyID, err := signer.KeyID()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error getting public key ID: %w", err)
|
return nil, fmt.Errorf("error getting public key ID: %w", err)
|
||||||
}
|
}
|
||||||
entry, err := t.UploadLogEntry(ctx, keyId, *encPayload, *sig, signer)
|
entry, err := t.UploadLogEntry(ctx, keyID, *encPayload, *sig, signer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error uploading TL entry: %w", err)
|
return nil, fmt.Errorf("error uploading TL entry: %w", err)
|
||||||
}
|
}
|
||||||
@@ -66,10 +66,10 @@ func logSignature(ctx context.Context, t tlog.TL, sig *[]byte, encPayload *[]byt
|
|||||||
return nil, fmt.Errorf("error unmarshaling tl entry: %w", err)
|
return nil, fmt.Errorf("error unmarshaling tl entry: %w", err)
|
||||||
}
|
}
|
||||||
return &Extension{
|
return &Extension{
|
||||||
Kind: DockerDsseExtKind,
|
Kind: DockerDSSEExtKind,
|
||||||
Ext: DockerDsseExtension{
|
Ext: &DockerDSSEExtension{
|
||||||
Tl: DockerTlExtension{
|
TL: &DockerTLExtension{
|
||||||
Kind: RekorTlExtKind,
|
Kind: RekorTLExtKind,
|
||||||
Data: entryObj, // transparency log entry metadata
|
Data: entryObj, // transparency log entry metadata
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -44,20 +44,20 @@ func TestSignVerifyAttestation(t *testing.T) {
|
|||||||
// signer.Public() used here for test purposes
|
// signer.Public() used here for test purposes
|
||||||
ecPub, ok := signer.Public().(*ecdsa.PublicKey)
|
ecPub, ok := signer.Public().(*ecdsa.PublicKey)
|
||||||
assert.True(t, ok)
|
assert.True(t, ok)
|
||||||
pem, err := signerverifier.ToPEM(ecPub)
|
pem, err := signerverifier.ConvertToPEM(ecPub)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
keyId, err := signerverifier.KeyID(ecPub)
|
keyID, err := signerverifier.KeyID(ecPub)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
badKeyPriv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
badKeyPriv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
badKey := &badKeyPriv.PublicKey
|
badKey := &badKeyPriv.PublicKey
|
||||||
badPEM, err := signerverifier.ToPEM(badKey)
|
badPEM, err := signerverifier.ConvertToPEM(badKey)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
keyId string
|
keyID string
|
||||||
pem []byte
|
pem []byte
|
||||||
distrust bool
|
distrust bool
|
||||||
from time.Time
|
from time.Time
|
||||||
@@ -67,7 +67,7 @@ func TestSignVerifyAttestation(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "all OK",
|
name: "all OK",
|
||||||
keyId: keyId,
|
keyID: keyID,
|
||||||
pem: pem,
|
pem: pem,
|
||||||
distrust: false,
|
distrust: false,
|
||||||
from: time.Time{},
|
from: time.Time{},
|
||||||
@@ -77,17 +77,17 @@ func TestSignVerifyAttestation(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "key not found",
|
name: "key not found",
|
||||||
keyId: "someotherkey",
|
keyID: "someotherkey",
|
||||||
pem: pem,
|
pem: pem,
|
||||||
distrust: false,
|
distrust: false,
|
||||||
from: time.Time{},
|
from: time.Time{},
|
||||||
to: nil,
|
to: nil,
|
||||||
status: "active",
|
status: "active",
|
||||||
expectedError: fmt.Sprintf("key not found: %s", keyId),
|
expectedError: fmt.Sprintf("key not found: %s", keyID),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "key distrusted",
|
name: "key distrusted",
|
||||||
keyId: keyId,
|
keyID: keyID,
|
||||||
pem: pem,
|
pem: pem,
|
||||||
distrust: true,
|
distrust: true,
|
||||||
from: time.Time{},
|
from: time.Time{},
|
||||||
@@ -97,7 +97,7 @@ func TestSignVerifyAttestation(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "key not yet valid",
|
name: "key not yet valid",
|
||||||
keyId: keyId,
|
keyID: keyID,
|
||||||
pem: pem,
|
pem: pem,
|
||||||
distrust: false,
|
distrust: false,
|
||||||
from: time.Now().Add(time.Hour),
|
from: time.Now().Add(time.Hour),
|
||||||
@@ -107,7 +107,7 @@ func TestSignVerifyAttestation(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "key already revoked",
|
name: "key already revoked",
|
||||||
keyId: keyId,
|
keyID: keyID,
|
||||||
pem: pem,
|
pem: pem,
|
||||||
distrust: false,
|
distrust: false,
|
||||||
from: time.Time{},
|
from: time.Time{},
|
||||||
@@ -117,7 +117,7 @@ func TestSignVerifyAttestation(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "bad key",
|
name: "bad key",
|
||||||
keyId: keyId,
|
keyID: keyID,
|
||||||
pem: badPEM,
|
pem: badPEM,
|
||||||
distrust: false,
|
distrust: false,
|
||||||
from: time.Time{},
|
from: time.Time{},
|
||||||
@@ -129,8 +129,8 @@ func TestSignVerifyAttestation(t *testing.T) {
|
|||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
keyMeta := attestation.KeyMetadata{
|
keyMeta := &attestation.KeyMetadata{
|
||||||
ID: tc.keyId,
|
ID: tc.keyID,
|
||||||
PEM: string(tc.pem),
|
PEM: string(tc.pem),
|
||||||
Distrust: tc.distrust,
|
Distrust: tc.distrust,
|
||||||
From: tc.from,
|
From: tc.from,
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ const (
|
|||||||
AttestationManifestType = "attestation-manifest"
|
AttestationManifestType = "attestation-manifest"
|
||||||
InTotoPredicateType = "in-toto.io/predicate-type"
|
InTotoPredicateType = "in-toto.io/predicate-type"
|
||||||
DockerReferenceDigest = "vnd.docker.reference.digest"
|
DockerReferenceDigest = "vnd.docker.reference.digest"
|
||||||
DockerDsseExtKind = "application/vnd.docker.attestation-verification.v1+json"
|
DockerDSSEExtKind = "application/vnd.docker.attestation-verification.v1+json"
|
||||||
RekorTlExtKind = "Rekor"
|
RekorTLExtKind = "Rekor"
|
||||||
OCIDescriptorDSSEMediaType = ociv1.MediaTypeDescriptor + "+dsse"
|
OCIDescriptorDSSEMediaType = ociv1.MediaTypeDescriptor + "+dsse"
|
||||||
InTotoReferenceLifecycleStage = "vnd.docker.lifecycle-stage"
|
InTotoReferenceLifecycleStage = "vnd.docker.lifecycle-stage"
|
||||||
LifecycleStageExperimental = "experimental"
|
LifecycleStageExperimental = "experimental"
|
||||||
@@ -24,58 +24,58 @@ const (
|
|||||||
|
|
||||||
var base64Encoding = base64.StdEncoding.Strict()
|
var base64Encoding = base64.StdEncoding.Strict()
|
||||||
|
|
||||||
type AttestationLayer struct {
|
type Layer struct {
|
||||||
Statement *intoto.Statement
|
Statement *intoto.Statement
|
||||||
Layer v1.Layer
|
Layer v1.Layer
|
||||||
Annotations map[string]string
|
Annotations map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
type AttestationManifest struct {
|
type Manifest struct {
|
||||||
OriginalDescriptor *v1.Descriptor
|
OriginalDescriptor *v1.Descriptor
|
||||||
OriginalLayers []*AttestationLayer
|
OriginalLayers []*Layer
|
||||||
|
|
||||||
// accumulated during signing
|
// accumulated during signing
|
||||||
SignedLayers []*AttestationLayer
|
SignedLayers []*Layer
|
||||||
// details of subect image
|
// details of subject image
|
||||||
SubjectName string
|
SubjectName string
|
||||||
SubjectDescriptor *v1.Descriptor
|
SubjectDescriptor *v1.Descriptor
|
||||||
}
|
}
|
||||||
|
|
||||||
type AttestationManifestImageOptions struct {
|
type ManifestImageOptions struct {
|
||||||
// how to output the image
|
// how to output the image
|
||||||
skipSubject bool
|
skipSubject bool
|
||||||
replaceLayers bool
|
replaceLayers bool
|
||||||
laxReferrers bool
|
laxReferrers bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// the following types are needed until https://github.com/secure-systems-lab/dsse/pull/61 is merged
|
// the following types are needed until https://github.com/secure-systems-lab/dsse/pull/61 is merged.
|
||||||
type Envelope struct {
|
type Envelope struct {
|
||||||
PayloadType string `json:"payloadType"`
|
PayloadType string `json:"payloadType"`
|
||||||
Payload string `json:"payload"`
|
Payload string `json:"payload"`
|
||||||
Signatures []Signature `json:"signatures"`
|
Signatures []*Signature `json:"signatures"`
|
||||||
}
|
}
|
||||||
type Signature struct {
|
type Signature struct {
|
||||||
KeyID string `json:"keyid"`
|
KeyID string `json:"keyid"`
|
||||||
Sig string `json:"sig"`
|
Sig string `json:"sig"`
|
||||||
Extension Extension `json:"extension,omitempty"`
|
Extension *Extension `json:"extension,omitempty"`
|
||||||
}
|
}
|
||||||
type Extension struct {
|
type Extension struct {
|
||||||
Kind string `json:"kind"`
|
Kind string `json:"kind"`
|
||||||
Ext DockerDsseExtension `json:"ext"`
|
Ext *DockerDSSEExtension `json:"ext"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type DockerDsseExtension struct {
|
type DockerDSSEExtension struct {
|
||||||
Tl DockerTlExtension `json:"tl"`
|
TL *DockerTLExtension `json:"tl"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type DockerTlExtension struct {
|
type DockerTLExtension struct {
|
||||||
Kind string `json:"kind"`
|
Kind string `json:"kind"`
|
||||||
Data any `json:"data"`
|
Data any `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type VerifyOptions struct {
|
type VerifyOptions struct {
|
||||||
Keys []KeyMetadata `json:"keys"`
|
Keys []*KeyMetadata `json:"keys"`
|
||||||
SkipTL bool `json:"skip_tl"`
|
SkipTL bool `json:"skip_tl"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SigningOptions struct {
|
type SigningOptions struct {
|
||||||
|
|||||||
@@ -27,8 +27,10 @@ type KeyMetadata struct {
|
|||||||
Distrust bool `json:"distrust,omitempty"`
|
Distrust bool `json:"distrust,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Keys []KeyMetadata
|
type (
|
||||||
type KeysMap map[string]KeyMetadata
|
Keys []*KeyMetadata
|
||||||
|
KeysMap map[string]*KeyMetadata
|
||||||
|
)
|
||||||
|
|
||||||
func VerifyDSSE(ctx context.Context, env *Envelope, opts *VerifyOptions) ([]byte, error) {
|
func VerifyDSSE(ctx context.Context, env *Envelope, opts *VerifyOptions) ([]byte, error) {
|
||||||
// enforce payload type
|
// enforce payload type
|
||||||
@@ -58,8 +60,8 @@ func VerifyDSSE(ctx context.Context, env *Envelope, opts *VerifyOptions) ([]byte
|
|||||||
return payload, nil
|
return payload, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func verifySignature(ctx context.Context, sig Signature, payload []byte, opts *VerifyOptions) error {
|
func verifySignature(ctx context.Context, sig *Signature, payload []byte, opts *VerifyOptions) error {
|
||||||
keys := make(map[string]KeyMetadata, len(opts.Keys))
|
keys := make(map[string]*KeyMetadata, len(opts.Keys))
|
||||||
for _, key := range opts.Keys {
|
for _, key := range opts.Keys {
|
||||||
keys[key.ID] = key
|
keys[key.ID] = key
|
||||||
}
|
}
|
||||||
@@ -72,26 +74,25 @@ func verifySignature(ctx context.Context, sig Signature, payload []byte, opts *V
|
|||||||
return fmt.Errorf("key %s is distrusted", keyMeta.ID)
|
return fmt.Errorf("key %s is distrusted", keyMeta.ID)
|
||||||
}
|
}
|
||||||
// TODO: this is unmarshalling with MarshalPKIXPublicKey only for us to marshal it again
|
// TODO: this is unmarshalling with MarshalPKIXPublicKey only for us to marshal it again
|
||||||
publicKey, err := signerverifier.Parse([]byte(keyMeta.PEM))
|
publicKey, err := signerverifier.ParsePublicKey([]byte(keyMeta.PEM))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to parse public key: %w", err)
|
return fmt.Errorf("failed to parse public key: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !opts.SkipTL {
|
if !opts.SkipTL {
|
||||||
t := tlog.GetTL(ctx)
|
t := tlog.GetTL(ctx)
|
||||||
|
if sig.Extension == nil || sig.Extension.Kind == "" {
|
||||||
if sig.Extension.Kind == "" {
|
return fmt.Errorf("error missing signature extension")
|
||||||
return fmt.Errorf("error missing signature extension kind")
|
|
||||||
}
|
}
|
||||||
if sig.Extension.Kind != DockerDsseExtKind {
|
if sig.Extension.Kind != DockerDSSEExtKind {
|
||||||
return fmt.Errorf("error unsupported signature extension kind: %s", sig.Extension.Kind)
|
return fmt.Errorf("error unsupported signature extension kind: %s", sig.Extension.Kind)
|
||||||
}
|
}
|
||||||
|
|
||||||
// verify TL entry
|
// verify TL entry
|
||||||
if sig.Extension.Ext.Tl.Kind != RekorTlExtKind {
|
if sig.Extension.Ext.TL.Kind != RekorTLExtKind {
|
||||||
return fmt.Errorf("error unsupported TL extension kind: %s", sig.Extension.Ext.Tl.Kind)
|
return fmt.Errorf("error unsupported TL extension kind: %s", sig.Extension.Ext.TL.Kind)
|
||||||
}
|
}
|
||||||
entry := sig.Extension.Ext.Tl.Data
|
entry := sig.Extension.Ext.TL.Data
|
||||||
entryBytes, err := json.Marshal(entry)
|
entryBytes, err := json.Marshal(entry)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to marshal TL entry: %w", err)
|
return fmt.Errorf("failed to marshal TL entry: %w", err)
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ func TestVerifyUnsignedAttestation(t *testing.T) {
|
|||||||
payload := []byte("payload")
|
payload := []byte("payload")
|
||||||
env := &attestation.Envelope{
|
env := &attestation.Envelope{
|
||||||
// no signatures
|
// no signatures
|
||||||
Signatures: []attestation.Signature{},
|
Signatures: []*attestation.Signature{},
|
||||||
Payload: base64.StdEncoding.EncodeToString(payload),
|
Payload: base64.StdEncoding.EncodeToString(payload),
|
||||||
PayloadType: intoto.PayloadType,
|
PayloadType: intoto.PayloadType,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ const (
|
|||||||
type VSAPredicate struct {
|
type VSAPredicate struct {
|
||||||
Verifier VSAVerifier `json:"verifier"`
|
Verifier VSAVerifier `json:"verifier"`
|
||||||
TimeVerified string `json:"timeVerified"`
|
TimeVerified string `json:"timeVerified"`
|
||||||
ResourceUri string `json:"resourceUri"`
|
ResourceURI string `json:"resourceUri"`
|
||||||
Policy VSAPolicy `json:"policy"`
|
Policy VSAPolicy `json:"policy"`
|
||||||
InputAttestations []VSAInputAttestation `json:"inputAttestations"`
|
InputAttestations []VSAInputAttestation `json:"inputAttestations"`
|
||||||
VerificationResult string `json:"verificationResult"`
|
VerificationResult string `json:"verificationResult"`
|
||||||
@@ -35,7 +35,7 @@ type VSAInputAttestation struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ToVSAResourceURI(sub intoto.Subject) (string, error) {
|
func ToVSAResourceURI(sub intoto.Subject) (string, error) {
|
||||||
//parse purl
|
// parse purl
|
||||||
purl, err := packageurl.FromString(sub.Name)
|
purl, err := packageurl.FromString(sub.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("failed to parse package url: %w", err)
|
return "", fmt.Errorf("failed to parse package url: %w", err)
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ func LoadLocalMappings(configDir string) (*PolicyMappings, error) {
|
|||||||
return expandMappingFile(mappings)
|
return expandMappingFile(mappings)
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadTufMappings(tufClient tuf.TUFClient, localTargetsDir string) (*PolicyMappings, error) {
|
func LoadTUFMappings(tufClient tuf.Downloader, localTargetsDir string) (*PolicyMappings, error) {
|
||||||
if tufClient == nil {
|
if tufClient == nil {
|
||||||
return nil, fmt.Errorf("tuf client not set")
|
return nil, fmt.Errorf("tuf client not set")
|
||||||
}
|
}
|
||||||
@@ -52,7 +52,7 @@ func LoadTufMappings(tufClient tuf.TUFClient, localTargetsDir string) (*PolicyMa
|
|||||||
func expandMappingFile(mappingFile *policyMappingsFile) (*PolicyMappings, error) {
|
func expandMappingFile(mappingFile *policyMappingsFile) (*PolicyMappings, error) {
|
||||||
policies := make(map[string]*PolicyMapping)
|
policies := make(map[string]*PolicyMapping)
|
||||||
for _, policy := range mappingFile.Policies {
|
for _, policy := range mappingFile.Policies {
|
||||||
policies[policy.Id] = policy
|
policies[policy.ID] = policy
|
||||||
}
|
}
|
||||||
|
|
||||||
var rules []*PolicyRule
|
var rules []*PolicyRule
|
||||||
@@ -63,7 +63,7 @@ func expandMappingFile(mappingFile *policyMappingsFile) (*PolicyMappings, error)
|
|||||||
}
|
}
|
||||||
rules = append(rules, &PolicyRule{
|
rules = append(rules, &PolicyRule{
|
||||||
Pattern: r,
|
Pattern: r,
|
||||||
PolicyId: rule.PolicyId,
|
PolicyID: rule.PolicyID,
|
||||||
Replacement: rule.Replacement,
|
Replacement: rule.Replacement,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ type policyMappingsFile struct {
|
|||||||
|
|
||||||
type policyRuleFile struct {
|
type policyRuleFile struct {
|
||||||
Pattern string `json:"pattern"`
|
Pattern string `json:"pattern"`
|
||||||
PolicyId string `json:"policy-id"`
|
PolicyID string `json:"policy-id"`
|
||||||
Replacement string `json:"rewrite"`
|
Replacement string `json:"rewrite"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type PolicyMapping struct {
|
type PolicyMapping struct {
|
||||||
Id string `json:"id"`
|
ID string `json:"id"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
Files []PolicyMappingFile `json:"files"`
|
Files []PolicyMappingFile `json:"files"`
|
||||||
Attestations *AttestationConfig `json:"attestations"`
|
Attestations *AttestationConfig `json:"attestations"`
|
||||||
@@ -49,6 +49,6 @@ type PolicyMappingFile struct {
|
|||||||
|
|
||||||
type PolicyRule struct {
|
type PolicyRule struct {
|
||||||
Pattern *regexp.Regexp
|
Pattern *regexp.Regexp
|
||||||
PolicyId string
|
PolicyID string
|
||||||
Replacement string
|
Replacement string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/attest/pkg/mirror"
|
|
||||||
"github.com/docker/attest/pkg/oci"
|
"github.com/docker/attest/pkg/oci"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
@@ -25,7 +24,7 @@ func TestRegistryAuth(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.Image, func(t *testing.T) {
|
t.Run(tc.Image, func(t *testing.T) {
|
||||||
err := mirror.PushIndexToRegistry(attIdx.Index, tc.Image)
|
err := oci.PushIndexToRegistry(attIdx.Index, tc.Image)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
_, err = oci.IndexFromRemote(tc.Image)
|
_, err = oci.IndexFromRemote(tc.Image)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|||||||
@@ -8,18 +8,19 @@ import (
|
|||||||
|
|
||||||
"github.com/docker/attest/internal/embed"
|
"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/tuf"
|
"github.com/docker/attest/pkg/tuf"
|
||||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TufMirrorOutput struct {
|
type TufMirrorOutput struct {
|
||||||
metadata v1.Image
|
metadata v1.Image
|
||||||
delegatedMetadata []*mirror.MirrorImage
|
delegatedMetadata []*mirror.Image
|
||||||
targets []*mirror.MirrorImage
|
targets []*mirror.Image
|
||||||
delegatedTargets []*mirror.MirrorIndex
|
delegatedTargets []*mirror.Index
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExampleNewTufMirror() {
|
func ExampleNewTUFMirror() {
|
||||||
home, err := os.UserHomeDir()
|
home, err := os.UserHomeDir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@@ -29,7 +30,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(embed.RootStaging.Data, tufOutputPath, metadataURI, targetsURI, tuf.NewMockVersionChecker())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@@ -46,7 +47,7 @@ func ExampleNewTufMirror() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// create targets manifest
|
// create targets manifest
|
||||||
targets, err := m.GetTufTargetMirrors()
|
targets, err := m.GetTUFTargetMirrors()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@@ -80,7 +81,7 @@ func ExampleNewTufMirror() {
|
|||||||
func mirrorToRegistry(o *TufMirrorOutput) error {
|
func mirrorToRegistry(o *TufMirrorOutput) error {
|
||||||
// push metadata to registry
|
// push metadata to registry
|
||||||
metadataRepo := "registry-1.docker.io/docker/tuf-metadata:latest"
|
metadataRepo := "registry-1.docker.io/docker/tuf-metadata:latest"
|
||||||
err := mirror.PushImageToRegistry(o.metadata, metadataRepo)
|
err := oci.PushImageToRegistry(o.metadata, metadataRepo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -91,7 +92,7 @@ func mirrorToRegistry(o *TufMirrorOutput) error {
|
|||||||
return fmt.Errorf("failed to get repo without tag: %s", metadataRepo)
|
return fmt.Errorf("failed to get repo without tag: %s", metadataRepo)
|
||||||
}
|
}
|
||||||
imageName := fmt.Sprintf("%s:%s", repo, metadata.Tag)
|
imageName := fmt.Sprintf("%s:%s", repo, metadata.Tag)
|
||||||
err = mirror.PushImageToRegistry(metadata.Image, imageName)
|
err = oci.PushImageToRegistry(metadata.Image, imageName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -101,7 +102,7 @@ func mirrorToRegistry(o *TufMirrorOutput) error {
|
|||||||
targetsRepo := "registry-1.docker.io/docker/tuf-targets"
|
targetsRepo := "registry-1.docker.io/docker/tuf-targets"
|
||||||
for _, target := range o.targets {
|
for _, target := range o.targets {
|
||||||
imageName := fmt.Sprintf("%s:%s", targetsRepo, target.Tag)
|
imageName := fmt.Sprintf("%s:%s", targetsRepo, target.Tag)
|
||||||
err = mirror.PushImageToRegistry(target.Image, imageName)
|
err = oci.PushImageToRegistry(target.Image, imageName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -109,7 +110,7 @@ func mirrorToRegistry(o *TufMirrorOutput) error {
|
|||||||
// push delegated targets to registry
|
// push delegated targets to registry
|
||||||
for _, target := range o.delegatedTargets {
|
for _, target := range o.delegatedTargets {
|
||||||
imageName := fmt.Sprintf("%s:%s", targetsRepo, target.Tag)
|
imageName := fmt.Sprintf("%s:%s", targetsRepo, target.Tag)
|
||||||
err = mirror.PushIndexToRegistry(target.Index, imageName)
|
err = oci.PushIndexToRegistry(target.Index, imageName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -119,14 +120,14 @@ func mirrorToRegistry(o *TufMirrorOutput) error {
|
|||||||
|
|
||||||
func mirrorToLocal(o *TufMirrorOutput, outputPath string) error {
|
func mirrorToLocal(o *TufMirrorOutput, outputPath string) error {
|
||||||
// output metadata to local directory
|
// output metadata to local directory
|
||||||
err := mirror.SaveImageAsOCILayout(o.metadata, outputPath)
|
err := oci.SaveImageAsOCILayout(o.metadata, outputPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// output delegated metadata to local directory
|
// output delegated metadata to local directory
|
||||||
for _, metadata := range o.delegatedMetadata {
|
for _, metadata := range o.delegatedMetadata {
|
||||||
path := filepath.Join(outputPath, metadata.Tag)
|
path := filepath.Join(outputPath, metadata.Tag)
|
||||||
err = mirror.SaveImageAsOCILayout(metadata.Image, path)
|
err = oci.SaveImageAsOCILayout(metadata.Image, path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -135,7 +136,7 @@ func mirrorToLocal(o *TufMirrorOutput, outputPath string) error {
|
|||||||
// output top-level targets to local directory
|
// output top-level targets to local directory
|
||||||
for _, target := range o.targets {
|
for _, target := range o.targets {
|
||||||
path := filepath.Join(outputPath, target.Tag)
|
path := filepath.Join(outputPath, target.Tag)
|
||||||
err = mirror.SaveImageAsOCILayout(target.Image, path)
|
err = oci.SaveImageAsOCILayout(target.Image, path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -143,7 +144,7 @@ func mirrorToLocal(o *TufMirrorOutput, outputPath string) error {
|
|||||||
// output delegated targets to local directory
|
// output delegated targets to local directory
|
||||||
for _, target := range o.delegatedTargets {
|
for _, target := range o.delegatedTargets {
|
||||||
path := filepath.Join(outputPath, target.Tag)
|
path := filepath.Join(outputPath, target.Tag)
|
||||||
err = mirror.SaveIndexAsOCILayout(target.Index, path)
|
err = oci.SaveIndexAsOCILayout(target.Index, path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,9 +16,9 @@ import (
|
|||||||
// TUF root metadata
|
// TUF root metadata
|
||||||
// -----------------
|
// -----------------
|
||||||
|
|
||||||
// GetMetadataManifest returns an image with TUF root metadata as layers
|
// GetMetadataManifest returns an image with TUF root metadata as layers.
|
||||||
func (m *TufMirror) GetMetadataManifest(metadataURL string) (v1.Image, error) {
|
func (m *TUFMirror) GetMetadataManifest(metadataURL string) (v1.Image, error) {
|
||||||
metadata, err := m.getTufMetadataMirror(metadataURL)
|
metadata, err := m.getMetadataMirror(metadataURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get metadata: %w", err)
|
return nil, fmt.Errorf("failed to get metadata: %w", err)
|
||||||
}
|
}
|
||||||
@@ -29,16 +29,16 @@ func (m *TufMirror) GetMetadataManifest(metadataURL string) (v1.Image, error) {
|
|||||||
return manifest, nil
|
return manifest, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getTufMetadataMirror returns a TufMetadata struct with TUF metadata as map of file names to bytes
|
// getMetadataMirror returns a TufMetadata struct with TUF metadata as map of file names to bytes.
|
||||||
func (m *TufMirror) getTufMetadataMirror(metadataURL string) (*TufMetadata, error) {
|
func (m *TUFMirror) getMetadataMirror(metadataURL string) (*TUFMetadata, error) {
|
||||||
trustedMetadata := m.TufClient.GetMetadata()
|
trustedMetadata := m.TUFClient.GetMetadata()
|
||||||
|
|
||||||
rootMetadata := map[string][]byte{}
|
rootMetadata := map[string][]byte{}
|
||||||
rootVersion := trustedMetadata.Root.Signed.Version
|
rootVersion := trustedMetadata.Root.Signed.Version
|
||||||
// get the previous versions of root metadata if any
|
// get the previous versions of root metadata if any
|
||||||
if rootVersion != 1 {
|
if rootVersion != 1 {
|
||||||
var err error
|
var err error
|
||||||
rootMetadata, err = m.TufClient.GetPriorRoots(metadataURL)
|
rootMetadata, err = m.TUFClient.GetPriorRoots(metadataURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get prior root metadata: %w", err)
|
return nil, fmt.Errorf("failed to get prior root metadata: %w", err)
|
||||||
}
|
}
|
||||||
@@ -69,7 +69,7 @@ func (m *TufMirror) getTufMetadataMirror(metadataURL string) (*TufMetadata, erro
|
|||||||
snapshotVersion = strconv.FormatInt(trustedMetadata.Snapshot.Signed.Version, 10)
|
snapshotVersion = strconv.FormatInt(trustedMetadata.Snapshot.Signed.Version, 10)
|
||||||
targetsVersion = strconv.FormatInt(trustedMetadata.Targets[metadata.TARGETS].Signed.Version, 10)
|
targetsVersion = strconv.FormatInt(trustedMetadata.Targets[metadata.TARGETS].Signed.Version, 10)
|
||||||
}
|
}
|
||||||
return &TufMetadata{
|
return &TUFMetadata{
|
||||||
Root: rootMetadata,
|
Root: rootMetadata,
|
||||||
Snapshot: map[string][]byte{nameFromRole(metadata.SNAPSHOT, snapshotVersion): snapshotBytes},
|
Snapshot: map[string][]byte{nameFromRole(metadata.SNAPSHOT, snapshotVersion): snapshotBytes},
|
||||||
Targets: map[string][]byte{nameFromRole(metadata.TARGETS, targetsVersion): targetsBytes},
|
Targets: map[string][]byte{nameFromRole(metadata.TARGETS, targetsVersion): targetsBytes},
|
||||||
@@ -77,12 +77,12 @@ func (m *TufMirror) getTufMetadataMirror(metadataURL string) (*TufMetadata, erro
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// buildMetadataManifest returns an OCI image with TUF metadata as layers with annotations
|
// buildMetadataManifest returns an OCI image with TUF metadata as layers with annotations.
|
||||||
func (m *TufMirror) buildMetadataManifest(metadata *TufMetadata) (v1.Image, error) {
|
func (m *TUFMirror) buildMetadataManifest(metadata *TUFMetadata) (v1.Image, error) {
|
||||||
img := empty.Image
|
img := empty.Image
|
||||||
img = mutate.MediaType(img, types.OCIManifestSchema1)
|
img = mutate.MediaType(img, types.OCIManifestSchema1)
|
||||||
img = mutate.ConfigMediaType(img, types.OCIConfigJSON)
|
img = mutate.ConfigMediaType(img, types.OCIConfigJSON)
|
||||||
for _, role := range TufRoles {
|
for _, role := range TUFRoles {
|
||||||
layers, err := m.makeRoleLayers(role, metadata)
|
layers, err := m.makeRoleLayers(role, metadata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to make role layer: %w", err)
|
return nil, fmt.Errorf("failed to make role layer: %w", err)
|
||||||
@@ -95,8 +95,8 @@ func (m *TufMirror) buildMetadataManifest(metadata *TufMetadata) (v1.Image, erro
|
|||||||
return img, nil
|
return img, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// makeRoleLayers returns a list of layers for a given TUF role
|
// makeRoleLayers returns a list of layers for a given TUF role.
|
||||||
func (m *TufMirror) makeRoleLayers(role TufRole, tufMetadata *TufMetadata) ([]mutate.Addendum, error) {
|
func (m *TUFMirror) makeRoleLayers(role TUFRole, tufMetadata *TUFMetadata) ([]mutate.Addendum, error) {
|
||||||
var layers []mutate.Addendum
|
var layers []mutate.Addendum
|
||||||
ann := map[string]string{tufFileAnnotation: ""}
|
ann := map[string]string{tufFileAnnotation: ""}
|
||||||
switch role {
|
switch role {
|
||||||
@@ -115,8 +115,8 @@ func (m *TufMirror) makeRoleLayers(role TufRole, tufMetadata *TufMetadata) ([]mu
|
|||||||
return layers, nil
|
return layers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// annotatedMetaLayers returns a list of layers with annotations for each TUF metadata file
|
// annotatedMetaLayers returns a list of layers with annotations for each TUF metadata file.
|
||||||
func (m *TufMirror) annotatedMetaLayers(meta map[string][]byte) []mutate.Addendum {
|
func (m *TUFMirror) annotatedMetaLayers(meta map[string][]byte) []mutate.Addendum {
|
||||||
var layers []mutate.Addendum
|
var layers []mutate.Addendum
|
||||||
for name, data := range meta {
|
for name, data := range meta {
|
||||||
ann := map[string]string{tufFileAnnotation: name}
|
ann := map[string]string{tufFileAnnotation: name}
|
||||||
@@ -129,8 +129,8 @@ func (m *TufMirror) annotatedMetaLayers(meta map[string][]byte) []mutate.Addendu
|
|||||||
// TUF delegated targets metadata
|
// TUF delegated targets metadata
|
||||||
// ------------------------------
|
// ------------------------------
|
||||||
|
|
||||||
// GetDelegatedMetadataMirrors returns a list of mirrors (image/tag pairs) for each delegated targets role metadata
|
// GetDelegatedMetadataMirrors returns a list of mirrors (image/tag pairs) for each delegated targets role metadata.
|
||||||
func (m *TufMirror) GetDelegatedMetadataMirrors() ([]*MirrorImage, error) {
|
func (m *TUFMirror) GetDelegatedMetadataMirrors() ([]*Image, error) {
|
||||||
// get current delegated targets metadata
|
// get current delegated targets metadata
|
||||||
delegatedTargets, err := m.getDelegatedTargetsMetadata()
|
delegatedTargets, err := m.getDelegatedTargetsMetadata()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -143,12 +143,12 @@ func (m *TufMirror) GetDelegatedMetadataMirrors() ([]*MirrorImage, error) {
|
|||||||
return mirror, nil
|
return mirror, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getDelegatedTargetsMetadata returns delegated targets metadata as a list of DelegatedTargetMetadata (role name and data)
|
// getDelegatedTargetsMetadata returns delegated targets metadata as a list of DelegatedTargetMetadata (role name and data).
|
||||||
func (m *TufMirror) getDelegatedTargetsMetadata() ([]DelegatedTargetMetadata, error) {
|
func (m *TUFMirror) getDelegatedTargetsMetadata() ([]DelegatedTargetMetadata, error) {
|
||||||
var delegatedTargets []DelegatedTargetMetadata
|
var delegatedTargets []DelegatedTargetMetadata
|
||||||
md := m.TufClient.GetMetadata()
|
md := m.TUFClient.GetMetadata()
|
||||||
for _, role := range md.Targets[metadata.TARGETS].Signed.Delegations.Roles {
|
for _, role := range md.Targets[metadata.TARGETS].Signed.Delegations.Roles {
|
||||||
roleMetadata, err := m.TufClient.LoadDelegatedTargets(role.Name, metadata.TARGETS)
|
roleMetadata, err := m.TUFClient.LoadDelegatedTargets(role.Name, metadata.TARGETS)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get delegated role metadata: %w", err)
|
return nil, fmt.Errorf("failed to get delegated role metadata: %w", err)
|
||||||
}
|
}
|
||||||
@@ -170,9 +170,9 @@ func (m *TufMirror) getDelegatedTargetsMetadata() ([]DelegatedTargetMetadata, er
|
|||||||
return delegatedTargets, nil
|
return delegatedTargets, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// buildDelegatedMetadataManifests returns a list of mirrors (image/tag pairs) for each delegated target role metadata
|
// buildDelegatedMetadataManifests returns a list of mirrors (image/tag pairs) for each delegated target role metadata.
|
||||||
func (m *TufMirror) buildDelegatedMetadataManifests(delegated []DelegatedTargetMetadata) ([]*MirrorImage, error) {
|
func (m *TUFMirror) buildDelegatedMetadataManifests(delegated []DelegatedTargetMetadata) ([]*Image, error) {
|
||||||
manifests := []*MirrorImage{}
|
manifests := []*Image{}
|
||||||
for _, role := range delegated {
|
for _, role := range delegated {
|
||||||
img := empty.Image
|
img := empty.Image
|
||||||
img = mutate.MediaType(img, types.OCIManifestSchema1)
|
img = mutate.MediaType(img, types.OCIManifestSchema1)
|
||||||
@@ -183,7 +183,7 @@ func (m *TufMirror) buildDelegatedMetadataManifests(delegated []DelegatedTargetM
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to append delegated targets layer to image: %w", err)
|
return nil, fmt.Errorf("failed to append delegated targets layer to image: %w", err)
|
||||||
}
|
}
|
||||||
manifests = append(manifests, &MirrorImage{Image: img, Tag: role.Name})
|
manifests = append(manifests, &Image{Image: img, Tag: role.Name})
|
||||||
}
|
}
|
||||||
return manifests, nil
|
return manifests, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,10 +21,10 @@ 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+"/metadata", server.URL+"/targets", tuf.NewMockVersionChecker())
|
m, err := NewTUFMirror(embed.RootDev.Data, path, server.URL+"/metadata", server.URL+"/targets", tuf.NewMockVersionChecker())
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
tufMetadata, err := m.getTufMetadataMirror(server.URL + "/metadata")
|
tufMetadata, err := m.getMetadataMirror(server.URL + "/metadata")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
// check that all roles are not empty
|
// check that all roles are not empty
|
||||||
@@ -39,7 +39,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+"/metadata", server.URL+"/targets", tuf.NewMockVersionChecker())
|
m, err := NewTUFMirror(embed.RootDev.Data, path, server.URL+"/metadata", server.URL+"/targets", tuf.NewMockVersionChecker())
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
img, err := m.GetMetadataManifest(server.URL + "/metadata")
|
img, err := m.GetMetadataManifest(server.URL + "/metadata")
|
||||||
@@ -78,7 +78,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+"/metadata", server.URL+"/targets", tuf.NewMockVersionChecker())
|
m, err := NewTUFMirror(embed.RootDev.Data, path, server.URL+"/metadata", server.URL+"/targets", tuf.NewMockVersionChecker())
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
delegations, err := m.GetDelegatedMetadataMirrors()
|
delegations, err := m.GetDelegatedMetadataMirrors()
|
||||||
|
|||||||
@@ -2,156 +2,18 @@ package mirror
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/docker/attest/internal/embed"
|
"github.com/docker/attest/internal/embed"
|
||||||
"github.com/docker/attest/pkg/attestation"
|
|
||||||
"github.com/docker/attest/pkg/oci"
|
|
||||||
"github.com/docker/attest/pkg/tuf"
|
"github.com/docker/attest/pkg/tuf"
|
||||||
"github.com/google/go-containerregistry/pkg/name"
|
|
||||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
|
||||||
"github.com/google/go-containerregistry/pkg/v1/empty"
|
|
||||||
"github.com/google/go-containerregistry/pkg/v1/layout"
|
|
||||||
"github.com/google/go-containerregistry/pkg/v1/mutate"
|
|
||||||
"github.com/google/go-containerregistry/pkg/v1/remote"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
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 = embed.RootDefault.Data
|
||||||
}
|
}
|
||||||
tufClient, err := tuf.NewTufClient(root, tufPath, metadataURL, targetsURL, versionChecker)
|
tufClient, err := tuf.NewClient(root, tufPath, metadataURL, targetsURL, 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)
|
||||||
}
|
}
|
||||||
return &TufMirror{TufClient: tufClient, tufPath: tufPath, metadataURL: metadataURL, targetsURL: targetsURL}, nil
|
return &TUFMirror{TUFClient: tufClient, tufPath: tufPath, metadataURL: metadataURL, targetsURL: targetsURL}, nil
|
||||||
}
|
|
||||||
|
|
||||||
func PushImageToRegistry(image v1.Image, imageName string) error {
|
|
||||||
ref, err := name.ParseReference(imageName)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Failed to parse image name '%s': %w", imageName, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Push the image to the registry
|
|
||||||
return remote.Write(ref, image, oci.MultiKeychainOption())
|
|
||||||
}
|
|
||||||
|
|
||||||
func PushIndexToRegistry(index v1.ImageIndex, imageName string) error {
|
|
||||||
// Parse the index name
|
|
||||||
ref, err := name.ParseReference(imageName)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Failed to parse image name: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Push the index to the registry
|
|
||||||
return remote.WriteIndex(ref, index, oci.MultiKeychainOption())
|
|
||||||
}
|
|
||||||
|
|
||||||
func SaveImageAsOCILayout(image v1.Image, path string) error {
|
|
||||||
// Save the image to the local filesystem
|
|
||||||
err := os.MkdirAll(path, os.ModePerm)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to create directory: %w", err)
|
|
||||||
}
|
|
||||||
index := empty.Index
|
|
||||||
l, err := layout.Write(path, index)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to create index: %w", err)
|
|
||||||
}
|
|
||||||
return l.AppendImage(image)
|
|
||||||
}
|
|
||||||
|
|
||||||
func SaveIndexAsOCILayout(image v1.ImageIndex, path string) error {
|
|
||||||
// Save the index to the local filesystem
|
|
||||||
err := os.MkdirAll(path, os.ModePerm)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to create directory: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = layout.Write(path, image)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to create index: %w", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func SaveIndex(outputs []*oci.ImageSpec, index v1.ImageIndex, indexName string) error {
|
|
||||||
// split output by comma and write or push each one
|
|
||||||
for _, output := range outputs {
|
|
||||||
if output.Type == oci.OCI {
|
|
||||||
idx := v1.ImageIndex(empty.Index)
|
|
||||||
idx = mutate.AppendManifests(idx, mutate.IndexAddendum{
|
|
||||||
Add: index,
|
|
||||||
Descriptor: v1.Descriptor{
|
|
||||||
Annotations: map[string]string{
|
|
||||||
oci.OciReferenceTarget: indexName,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
err := SaveIndexAsOCILayout(idx, output.Identifier)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to write signed image: %w", err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
err := PushIndexToRegistry(index, output.Identifier)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to push signed image: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func SaveImage(output *oci.ImageSpec, image v1.Image, imageName string) error {
|
|
||||||
if output.Type == oci.OCI {
|
|
||||||
idx := v1.ImageIndex(empty.Index)
|
|
||||||
idx = mutate.AppendManifests(idx, mutate.IndexAddendum{
|
|
||||||
Add: image,
|
|
||||||
Descriptor: v1.Descriptor{
|
|
||||||
Annotations: map[string]string{
|
|
||||||
oci.OciReferenceTarget: imageName,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
err := SaveIndexAsOCILayout(idx, output.Identifier)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to write signed image: %w", err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
err := PushImageToRegistry(image, output.Identifier)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to push signed image: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func SaveReferrers(manifest *attestation.AttestationManifest, outputs []*oci.ImageSpec) error {
|
|
||||||
for _, output := range outputs {
|
|
||||||
if output.Type == oci.OCI {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// so that we use the same tag each time to reduce number of tags (tags aren't needed for referrers but we must push one)
|
|
||||||
attOut, err := oci.ReplaceTagInSpec(output, manifest.SubjectDescriptor.Digest)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
//otherwise we end up with the detected platform, though I'm not sure it matters
|
|
||||||
attOut.Platform = &v1.Platform{
|
|
||||||
OS: "unknown",
|
|
||||||
Architecture: "unknown",
|
|
||||||
}
|
|
||||||
images, err := manifest.BuildReferringArtifacts()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to build image: %w", err)
|
|
||||||
}
|
|
||||||
for _, image := range images {
|
|
||||||
err = SaveImage(attOut, image, "")
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to push image: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,16 +13,16 @@ import (
|
|||||||
"github.com/theupdateframework/go-tuf/v2/metadata"
|
"github.com/theupdateframework/go-tuf/v2/metadata"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetTufTargetMirrors returns a list of top-level target files as MirrorImages (image with tag)
|
// GetTUFTargetMirrors returns a list of top-level target files as MirrorImages (image with tag).
|
||||||
func (m *TufMirror) GetTufTargetMirrors() ([]*MirrorImage, error) {
|
func (m *TUFMirror) GetTUFTargetMirrors() ([]*Image, error) {
|
||||||
targetMirrors := []*MirrorImage{}
|
targetMirrors := []*Image{}
|
||||||
md := m.TufClient.GetMetadata()
|
md := m.TUFClient.GetMetadata()
|
||||||
|
|
||||||
// for each top-level target file, create an image with the target file as a layer
|
// for each top-level target file, create an image with the target file as a layer
|
||||||
targets := md.Targets[metadata.TARGETS].Signed.Targets
|
targets := md.Targets[metadata.TARGETS].Signed.Targets
|
||||||
for _, t := range targets {
|
for _, t := range targets {
|
||||||
// download target file
|
// download target file
|
||||||
_, data, err := m.TufClient.DownloadTarget(t.Path, filepath.Join(m.tufPath, "download"))
|
_, data, err := m.TUFClient.DownloadTarget(t.Path, filepath.Join(m.tufPath, "download"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to download target %s: %w", t.Path, err)
|
return nil, fmt.Errorf("failed to download target %s: %w", t.Path, err)
|
||||||
}
|
}
|
||||||
@@ -42,16 +42,16 @@ func (m *TufMirror) GetTufTargetMirrors() ([]*MirrorImage, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to append role layer to image: %w", err)
|
return nil, fmt.Errorf("failed to append role layer to image: %w", err)
|
||||||
}
|
}
|
||||||
targetMirrors = append(targetMirrors, &MirrorImage{Image: img, Tag: name})
|
targetMirrors = append(targetMirrors, &Image{Image: img, Tag: name})
|
||||||
}
|
}
|
||||||
return targetMirrors, nil
|
return targetMirrors, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDelegatedTargetMirrors returns a list of delegated target files as MirrorIndexes (image index with tag)
|
// GetDelegatedTargetMirrors returns a list of delegated target files as MirrorIndexes (image index with tag)
|
||||||
// each image in the index contains a delegated target file
|
// each image in the index contains a delegated target file.
|
||||||
func (m *TufMirror) GetDelegatedTargetMirrors() ([]*MirrorIndex, error) {
|
func (m *TUFMirror) GetDelegatedTargetMirrors() ([]*Index, error) {
|
||||||
mirror := []*MirrorIndex{}
|
mirror := []*Index{}
|
||||||
md := m.TufClient.GetMetadata()
|
md := m.TUFClient.GetMetadata()
|
||||||
|
|
||||||
// for each delegated role, create an image index with target files as images
|
// for each delegated role, create an image index with target files as images
|
||||||
roles := md.Targets[metadata.TARGETS].Signed.Delegations.Roles
|
roles := md.Targets[metadata.TARGETS].Signed.Delegations.Roles
|
||||||
@@ -60,7 +60,7 @@ func (m *TufMirror) GetDelegatedTargetMirrors() ([]*MirrorIndex, error) {
|
|||||||
index := v1.ImageIndex(empty.Index)
|
index := v1.ImageIndex(empty.Index)
|
||||||
|
|
||||||
// get delegated targets metadata for role
|
// get delegated targets metadata for role
|
||||||
roleMeta, err := m.TufClient.LoadDelegatedTargets(role.Name, metadata.TARGETS)
|
roleMeta, err := m.TUFClient.LoadDelegatedTargets(role.Name, metadata.TARGETS)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to load delegated targets metadata: %w", err)
|
return nil, fmt.Errorf("failed to load delegated targets metadata: %w", err)
|
||||||
}
|
}
|
||||||
@@ -68,7 +68,7 @@ func (m *TufMirror) GetDelegatedTargetMirrors() ([]*MirrorIndex, error) {
|
|||||||
// for each target file, create an image with the target file as a layer
|
// for each target file, create an image with the target file as a layer
|
||||||
for _, target := range roleMeta.Signed.Targets {
|
for _, target := range roleMeta.Signed.Targets {
|
||||||
// download target file
|
// download target file
|
||||||
_, data, err := m.TufClient.DownloadTarget(target.Path, filepath.Join(m.tufPath, "download"))
|
_, data, err := m.TUFClient.DownloadTarget(target.Path, filepath.Join(m.tufPath, "download"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to download target %s: %w", target.Path, err)
|
return nil, fmt.Errorf("failed to download target %s: %w", target.Path, err)
|
||||||
}
|
}
|
||||||
@@ -103,7 +103,7 @@ func (m *TufMirror) GetDelegatedTargetMirrors() ([]*MirrorIndex, error) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
mirror = append(mirror, &MirrorIndex{Index: index, Tag: role.Name})
|
mirror = append(mirror, &Index{Index: index, Tag: role.Name})
|
||||||
}
|
}
|
||||||
return mirror, nil
|
return mirror, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,10 +27,10 @@ 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+"/metadata", server.URL+"/targets", tuf.NewMockVersionChecker())
|
m, err := NewTUFMirror(embed.RootDev.Data, path, server.URL+"/metadata", server.URL+"/targets", tuf.NewMockVersionChecker())
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
targets, err := m.GetTufTargetMirrors()
|
targets, err := m.GetTUFTargetMirrors()
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Greater(t, len(targets), 0)
|
assert.Greater(t, len(targets), 0)
|
||||||
|
|
||||||
@@ -61,10 +61,10 @@ 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+"/metadata", server.URL+"/targets", tuf.NewMockVersionChecker())
|
tm, err := NewTUFMirror(embed.RootDev.Data, path, server.URL+"/metadata", server.URL+"/targets", tuf.NewMockVersionChecker())
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
targets, err := tm.TufClient.LoadDelegatedTargets("test-role", "targets")
|
targets, err := tm.TUFClient.LoadDelegatedTargets("test-role", "targets")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Greater(t, len(targets.Signed.Targets), 0)
|
assert.Greater(t, len(targets.Signed.Targets), 0)
|
||||||
}
|
}
|
||||||
@@ -74,7 +74,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+"/metadata", server.URL+"/targets", tuf.NewMockVersionChecker())
|
m, err := NewTUFMirror(embed.RootDev.Data, path, server.URL+"/metadata", server.URL+"/targets", tuf.NewMockVersionChecker())
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
mirrors, err := m.GetDelegatedTargetMirrors()
|
mirrors, err := m.GetDelegatedTargetMirrors()
|
||||||
|
|||||||
@@ -14,11 +14,11 @@ const (
|
|||||||
tufFileAnnotation = "tuf.io/filename"
|
tufFileAnnotation = "tuf.io/filename"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TufRole string
|
type TUFRole string
|
||||||
|
|
||||||
var TufRoles = []TufRole{metadata.ROOT, metadata.SNAPSHOT, metadata.TARGETS, metadata.TIMESTAMP}
|
var TUFRoles = []TUFRole{metadata.ROOT, metadata.SNAPSHOT, metadata.TARGETS, metadata.TIMESTAMP}
|
||||||
|
|
||||||
type TufMetadata struct {
|
type TUFMetadata struct {
|
||||||
Root map[string][]byte
|
Root map[string][]byte
|
||||||
Snapshot map[string][]byte
|
Snapshot map[string][]byte
|
||||||
Targets map[string][]byte
|
Targets map[string][]byte
|
||||||
@@ -31,18 +31,18 @@ type DelegatedTargetMetadata struct {
|
|||||||
Data []byte
|
Data []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
type MirrorImage struct {
|
type Image struct {
|
||||||
Image v1.Image
|
Image v1.Image
|
||||||
Tag string
|
Tag string
|
||||||
}
|
}
|
||||||
|
|
||||||
type MirrorIndex struct {
|
type Index struct {
|
||||||
Index v1.ImageIndex
|
Index v1.ImageIndex
|
||||||
Tag string
|
Tag string
|
||||||
}
|
}
|
||||||
|
|
||||||
type TufMirror struct {
|
type TUFMirror struct {
|
||||||
TufClient *tuf.TufClient
|
TUFClient *tuf.Client
|
||||||
tufPath string
|
tufPath string
|
||||||
metadataURL string
|
metadataURL string
|
||||||
targetsURL string
|
targetsURL string
|
||||||
|
|||||||
@@ -7,21 +7,21 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type userAgentTransporter struct {
|
type userAgentTransporter struct {
|
||||||
ua string
|
userAgent string
|
||||||
rt http.RoundTripper
|
roundTripper http.RoundTripper
|
||||||
}
|
}
|
||||||
|
|
||||||
type Option = func(*http.Client)
|
type Option = func(*http.Client)
|
||||||
|
|
||||||
func (u *userAgentTransporter) RoundTrip(req *http.Request) (*http.Response, error) {
|
func (u *userAgentTransporter) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
req.Header.Set("User-Agent", u.ua)
|
req.Header.Set("User-Agent", u.userAgent)
|
||||||
|
|
||||||
return u.rt.RoundTrip(req)
|
return u.roundTripper.RoundTrip(req)
|
||||||
}
|
}
|
||||||
|
|
||||||
func HttpTransport() http.RoundTripper {
|
func HTTPTransport() http.RoundTripper {
|
||||||
return &userAgentTransporter{
|
return &userAgentTransporter{
|
||||||
ua: "Docker-Client",
|
userAgent: "Docker-Client",
|
||||||
rt: cleanhttp.DefaultTransport(),
|
roundTripper: cleanhttp.DefaultTransport(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,14 +11,14 @@ import (
|
|||||||
"github.com/google/go-containerregistry/pkg/v1/layout"
|
"github.com/google/go-containerregistry/pkg/v1/layout"
|
||||||
)
|
)
|
||||||
|
|
||||||
// implementation of AttestationResolver that closes over attestations from an oci layout
|
// implementation of AttestationResolver that closes over attestations from an oci layout.
|
||||||
type OCILayoutResolver struct {
|
type LayoutResolver struct {
|
||||||
*attestation.AttestationManifest
|
*attestation.Manifest
|
||||||
*ImageSpec
|
*ImageSpec
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewOCILayoutAttestationResolver(src *ImageSpec) (*OCILayoutResolver, error) {
|
func NewOCILayoutAttestationResolver(src *ImageSpec) (*LayoutResolver, error) {
|
||||||
r := &OCILayoutResolver{
|
r := &LayoutResolver{
|
||||||
ImageSpec: src,
|
ImageSpec: src,
|
||||||
}
|
}
|
||||||
_, err := r.fetchAttestationManifest()
|
_, err := r.fetchAttestationManifest()
|
||||||
@@ -28,25 +28,25 @@ func NewOCILayoutAttestationResolver(src *ImageSpec) (*OCILayoutResolver, error)
|
|||||||
return r, nil
|
return r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *OCILayoutResolver) fetchAttestationManifest() (*attestation.AttestationManifest, error) {
|
func (r *LayoutResolver) fetchAttestationManifest() (*attestation.Manifest, error) {
|
||||||
if r.AttestationManifest == nil {
|
if r.Manifest == nil {
|
||||||
m, err := attestationManifestFromOCILayout(r.Identifier, r.ImageSpec.Platform)
|
m, err := attestationManifestFromOCILayout(r.Identifier, r.ImageSpec.Platform)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
r.AttestationManifest = m
|
r.Manifest = m
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.AttestationManifest, nil
|
return r.Manifest, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *OCILayoutResolver) Attestations(ctx context.Context, predicateType string) ([]*att.Envelope, error) {
|
func (r *LayoutResolver) Attestations(_ context.Context, predicateType string) ([]*att.Envelope, error) {
|
||||||
var envs []*att.Envelope
|
var envs []*att.Envelope
|
||||||
dsseMediaType, err := attestation.DSSEMediaType(predicateType)
|
dsseMediaType, err := attestation.DSSEMediaType(predicateType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get DSSE media type for predicate '%s': %w", predicateType, err)
|
return nil, fmt.Errorf("failed to get DSSE media type for predicate '%s': %w", predicateType, err)
|
||||||
}
|
}
|
||||||
for _, attestationLayer := range r.AttestationManifest.OriginalLayers {
|
for _, attestationLayer := range r.Manifest.OriginalLayers {
|
||||||
mt, err := attestationLayer.Layer.MediaType()
|
mt, err := attestationLayer.Layer.MediaType()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get layer media type: %w", err)
|
return nil, fmt.Errorf("failed to get layer media type: %w", err)
|
||||||
@@ -55,10 +55,9 @@ func (r *OCILayoutResolver) Attestations(ctx context.Context, predicateType stri
|
|||||||
if mts != dsseMediaType {
|
if mts != dsseMediaType {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
var env = new(att.Envelope)
|
env := new(att.Envelope)
|
||||||
// parse layer blob as json
|
// parse layer blob as json
|
||||||
r, err := attestationLayer.Layer.Uncompressed()
|
r, err := attestationLayer.Layer.Uncompressed()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get layer contents: %w", err)
|
return nil, fmt.Errorf("failed to get layer contents: %w", err)
|
||||||
}
|
}
|
||||||
@@ -72,19 +71,19 @@ func (r *OCILayoutResolver) Attestations(ctx context.Context, predicateType stri
|
|||||||
return envs, nil
|
return envs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *OCILayoutResolver) ImageName(ctx context.Context) (string, error) {
|
func (r *LayoutResolver) ImageName(_ context.Context) (string, error) {
|
||||||
return r.SubjectName, nil
|
return r.SubjectName, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *OCILayoutResolver) ImageDescriptor(ctx context.Context) (*v1.Descriptor, error) {
|
func (r *LayoutResolver) ImageDescriptor(_ context.Context) (*v1.Descriptor, error) {
|
||||||
return r.SubjectDescriptor, nil
|
return r.SubjectDescriptor, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *OCILayoutResolver) ImagePlatform(ctx context.Context) (*v1.Platform, error) {
|
func (r *LayoutResolver) ImagePlatform(_ context.Context) (*v1.Platform, error) {
|
||||||
return r.ImageSpec.Platform, nil
|
return r.ImageSpec.Platform, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func attestationManifestFromOCILayout(path string, platform *v1.Platform) (*attestation.AttestationManifest, error) {
|
func attestationManifestFromOCILayout(path string, platform *v1.Platform) (*attestation.Manifest, error) {
|
||||||
idx, err := layout.ImageIndexFromPath(path)
|
idx, err := layout.ImageIndexFromPath(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -96,7 +95,6 @@ func attestationManifestFromOCILayout(path string, platform *v1.Platform) (*atte
|
|||||||
}
|
}
|
||||||
|
|
||||||
idxDescriptor := idxm.Manifests[0]
|
idxDescriptor := idxm.Manifests[0]
|
||||||
name := idxDescriptor.Annotations["org.opencontainers.image.ref.name"]
|
|
||||||
idxDigest := idxDescriptor.Digest
|
idxDigest := idxDescriptor.Digest
|
||||||
|
|
||||||
mfs, err := idx.ImageIndex(idxDigest)
|
mfs, err := idx.ImageIndex(idxDigest)
|
||||||
@@ -108,13 +106,20 @@ func attestationManifestFromOCILayout(path string, platform *v1.Platform) (*atte
|
|||||||
return nil, fmt.Errorf("failed to extract IndexManifest from ImageIndex: %w", err)
|
return nil, fmt.Errorf("failed to extract IndexManifest from ImageIndex: %w", err)
|
||||||
}
|
}
|
||||||
var subjectDescriptor *v1.Descriptor
|
var subjectDescriptor *v1.Descriptor
|
||||||
for _, mf := range mfs2.Manifests {
|
for i := range mfs2.Manifests {
|
||||||
if mf.Platform.Equals(*platform) {
|
manifest := &mfs2.Manifests[i]
|
||||||
subjectDescriptor = &mf
|
if manifest.Platform != nil {
|
||||||
break
|
if manifest.Platform.Equals(*platform) {
|
||||||
|
subjectDescriptor = manifest
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, mf := range mfs2.Manifests {
|
if subjectDescriptor == nil {
|
||||||
|
return nil, fmt.Errorf("platform not found in index")
|
||||||
|
}
|
||||||
|
for i := range mfs2.Manifests {
|
||||||
|
mf := &mfs2.Manifests[i]
|
||||||
if mf.Annotations[att.DockerReferenceType] != attestation.AttestationManifestType {
|
if mf.Annotations[att.DockerReferenceType] != attestation.AttestationManifestType {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -131,10 +136,10 @@ func attestationManifestFromOCILayout(path string, platform *v1.Platform) (*atte
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get attestations from image: %w", err)
|
return nil, fmt.Errorf("failed to get attestations from image: %w", err)
|
||||||
}
|
}
|
||||||
attest := &attestation.AttestationManifest{
|
attest := &attestation.Manifest{
|
||||||
OriginalLayers: layers,
|
OriginalLayers: layers,
|
||||||
OriginalDescriptor: &mf,
|
OriginalDescriptor: mf,
|
||||||
SubjectName: name,
|
SubjectName: idxDescriptor.Annotations["org.opencontainers.image.ref.name"],
|
||||||
SubjectDescriptor: subjectDescriptor,
|
SubjectDescriptor: subjectDescriptor,
|
||||||
}
|
}
|
||||||
return attest, nil
|
return attest, nil
|
||||||
|
|||||||
68
pkg/oci/layout_test.go
Normal file
68
pkg/oci/layout_test.go
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
package oci_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/attest/internal/test"
|
||||||
|
"github.com/docker/attest/pkg/attest"
|
||||||
|
"github.com/docker/attest/pkg/attestation"
|
||||||
|
"github.com/docker/attest/pkg/oci"
|
||||||
|
"github.com/docker/attest/pkg/policy"
|
||||||
|
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAttestationFromOCILayout(t *testing.T) {
|
||||||
|
ctx, signer := test.Setup(t)
|
||||||
|
outputLayout := test.CreateTempDir(t, "", "attest-oci-layout")
|
||||||
|
|
||||||
|
invalidPlatform := &v1.Platform{
|
||||||
|
Architecture: "invalid",
|
||||||
|
OS: "invalid",
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := &attestation.SigningOptions{}
|
||||||
|
attIdx, err := oci.IndexFromPath(oci.UnsignedTestImage)
|
||||||
|
require.NoError(t, err)
|
||||||
|
signedManifests, err := attest.SignStatements(ctx, attIdx.Index, signer, opts)
|
||||||
|
require.NoError(t, err)
|
||||||
|
signedIndex := attIdx.Index
|
||||||
|
signedIndex, err = attestation.UpdateIndexImages(signedIndex, signedManifests)
|
||||||
|
require.NoError(t, err)
|
||||||
|
spec, err := oci.ParseImageSpec(oci.LocalPrefix + outputLayout)
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = oci.SaveIndex([]*oci.ImageSpec{spec}, signedIndex, outputLayout)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
platform *v1.Platform
|
||||||
|
errorStr string
|
||||||
|
}{
|
||||||
|
{name: "nominal", platform: spec.Platform},
|
||||||
|
{name: "invalid platform", platform: invalidPlatform, errorStr: "platform not found in index"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
spec := &oci.ImageSpec{
|
||||||
|
Type: oci.OCI,
|
||||||
|
Identifier: outputLayout,
|
||||||
|
Platform: tc.platform,
|
||||||
|
}
|
||||||
|
resolver, err := policy.CreateImageDetailsResolver(spec)
|
||||||
|
if tc.errorStr != "" {
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), tc.errorStr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
require.NoError(t, err)
|
||||||
|
desc, err := resolver.ImageDescriptor(ctx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
digest := desc.Digest.String()
|
||||||
|
assert.True(t, strings.Contains(digest, "sha256:"))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
50
pkg/oci/mock.go
Normal file
50
pkg/oci/mock.go
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
package oci
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/docker/attest/pkg/attestation"
|
||||||
|
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MockResolver struct {
|
||||||
|
Envs []*attestation.Envelope
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r MockResolver) Attestations(_ context.Context, _ string) ([]*attestation.Envelope, error) {
|
||||||
|
return r.Envs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r MockResolver) ImageName(_ context.Context) (string, error) {
|
||||||
|
return "library/alpine:latest", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r MockResolver) ImageDescriptor(_ context.Context) (*v1.Descriptor, error) {
|
||||||
|
digest, err := v1.NewHash("sha256:da8b190665956ea07890a0273e2a9c96bfe291662f08e2860e868eef69c34620")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &v1.Descriptor{
|
||||||
|
Digest: digest,
|
||||||
|
Size: 1234,
|
||||||
|
MediaType: "application/vnd.oci.image.manifest.v1+json",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r MockResolver) ImagePlatform(_ context.Context) (*v1.Platform, error) {
|
||||||
|
return ParsePlatform("linux/amd64")
|
||||||
|
}
|
||||||
|
|
||||||
|
type MockRegistryResolver struct {
|
||||||
|
Subject *v1.Descriptor
|
||||||
|
ImageNameStr string
|
||||||
|
*MockResolver
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *MockRegistryResolver) ImageDescriptor(_ context.Context) (*v1.Descriptor, error) {
|
||||||
|
return r.Subject, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *MockRegistryResolver) ImageName(_ context.Context) (string, error) {
|
||||||
|
return r.ImageNameStr, nil
|
||||||
|
}
|
||||||
@@ -18,7 +18,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// ParsePlatform parses the provided platform string or attempts to obtain
|
// ParsePlatform parses the provided platform string or attempts to obtain
|
||||||
// the platform of the current host system
|
// the platform of the current host system.
|
||||||
func ParsePlatform(platformStr string) (*v1.Platform, error) {
|
func ParsePlatform(platformStr string) (*v1.Platform, error) {
|
||||||
if platformStr == "" {
|
if platformStr == "" {
|
||||||
cdp := platforms.Normalize(platforms.DefaultSpec())
|
cdp := platforms.Normalize(platforms.DefaultSpec())
|
||||||
@@ -30,14 +30,13 @@ func ParsePlatform(platformStr string) (*v1.Platform, error) {
|
|||||||
Architecture: cdp.Architecture,
|
Architecture: cdp.Architecture,
|
||||||
Variant: cdp.Variant,
|
Variant: cdp.Variant,
|
||||||
}, nil
|
}, nil
|
||||||
} else {
|
|
||||||
return v1.ParsePlatform(platformStr)
|
|
||||||
}
|
}
|
||||||
|
return v1.ParsePlatform(platformStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func WithOptions(ctx context.Context, platform *v1.Platform) []remote.Option {
|
func WithOptions(ctx context.Context, platform *v1.Platform) []remote.Option {
|
||||||
// prepare options
|
// prepare options
|
||||||
options := []remote.Option{MultiKeychainOption(), remote.WithTransport(HttpTransport()), remote.WithContext(ctx)}
|
options := []remote.Option{MultiKeychainOption(), remote.WithTransport(HTTPTransport()), remote.WithContext(ctx)}
|
||||||
|
|
||||||
// add in platform into remote Get operation; this might conflict with an explicit digest, but we are trying anyway
|
// add in platform into remote Get operation; this might conflict with an explicit digest, but we are trying anyway
|
||||||
if platform != nil {
|
if platform != nil {
|
||||||
@@ -46,7 +45,7 @@ func WithOptions(ctx context.Context, platform *v1.Platform) []remote.Option {
|
|||||||
return options
|
return options
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExtractEnvelopes(manifest *attestation.AttestationManifest, predicateType string) ([]*att.Envelope, error) {
|
func ExtractEnvelopes(manifest *attestation.Manifest, predicateType string) ([]*att.Envelope, error) {
|
||||||
var envs []*att.Envelope
|
var envs []*att.Envelope
|
||||||
dsseMediaType, err := attestation.DSSEMediaType(predicateType)
|
dsseMediaType, err := attestation.DSSEMediaType(predicateType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -63,7 +62,7 @@ func ExtractEnvelopes(manifest *attestation.AttestationManifest, predicateType s
|
|||||||
return nil, fmt.Errorf("failed to get layer contents: %w", err)
|
return nil, fmt.Errorf("failed to get layer contents: %w", err)
|
||||||
}
|
}
|
||||||
defer reader.Close()
|
defer reader.Close()
|
||||||
var env = new(att.Envelope)
|
env := new(att.Envelope)
|
||||||
err = json.NewDecoder(reader).Decode(&env)
|
err = json.NewDecoder(reader).Decode(&env)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to decode envelope: %w", err)
|
return nil, fmt.Errorf("failed to decode envelope: %w", err)
|
||||||
@@ -76,16 +75,18 @@ func ExtractEnvelopes(manifest *attestation.AttestationManifest, predicateType s
|
|||||||
}
|
}
|
||||||
|
|
||||||
func imageDescriptor(ix *v1.IndexManifest, platform *v1.Platform) (*v1.Descriptor, error) {
|
func imageDescriptor(ix *v1.IndexManifest, platform *v1.Platform) (*v1.Descriptor, error) {
|
||||||
for _, m := range ix.Manifests {
|
for i := range ix.Manifests {
|
||||||
|
m := &ix.Manifests[i]
|
||||||
if (m.MediaType == ocispec.MediaTypeImageManifest || m.MediaType == "application/vnd.docker.distribution.manifest.v2+json") && m.Platform.Equals(*platform) {
|
if (m.MediaType == ocispec.MediaTypeImageManifest || m.MediaType == "application/vnd.docker.distribution.manifest.v2+json") && m.Platform.Equals(*platform) {
|
||||||
return &m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("no image found for platform %v", platform)
|
return nil, fmt.Errorf("no image found for platform %v", platform)
|
||||||
}
|
}
|
||||||
|
|
||||||
func attestationDigestForDigest(ix *v1.IndexManifest, imageDigest string, attestType string) (string, error) {
|
func attestationDigestForDigest(ix *v1.IndexManifest, imageDigest string, attestType string) (string, error) {
|
||||||
for _, m := range ix.Manifests {
|
for i := range ix.Manifests {
|
||||||
|
m := &ix.Manifests[i]
|
||||||
if v, ok := m.Annotations[att.DockerReferenceType]; ok && v == attestType {
|
if v, ok := m.Annotations[att.DockerReferenceType]; ok && v == attestType {
|
||||||
if d, ok := m.Annotations[att.DockerReferenceDigest]; ok && d == imageDigest {
|
if d, ok := m.Annotations[att.DockerReferenceDigest]; ok && d == imageDigest {
|
||||||
return m.Digest.String(), nil
|
return m.Digest.String(), nil
|
||||||
@@ -160,7 +161,7 @@ func ReplaceTagInSpec(src *ImageSpec, digest v1.Hash) (*ImageSpec, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// so that the index tag is replaced with a tag unique to the image digest and doesn't overwrite it
|
// so that the index tag is replaced with a tag unique to the image digest and doesn't overwrite it.
|
||||||
func replaceTag(image string, digest v1.Hash) (string, error) {
|
func replaceTag(image string, digest v1.Hash) (string, error) {
|
||||||
if strings.HasPrefix(image, LocalPrefix) {
|
if strings.HasPrefix(image, LocalPrefix) {
|
||||||
return image, nil
|
return image, nil
|
||||||
@@ -171,3 +172,26 @@ func replaceTag(image string, digest v1.Hash) (string, error) {
|
|||||||
}
|
}
|
||||||
return fmt.Sprintf("%s:%s-%s.att", notag, digest.Algorithm, digest.Hex), nil
|
return fmt.Sprintf("%s:%s-%s.att", notag, digest.Algorithm, digest.Hex), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ReplaceDigestInSpec(src *ImageSpec, digest v1.Hash) (*ImageSpec, error) {
|
||||||
|
newName, err := replaceDigest(src.Identifier, digest)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse repo name: %w", err)
|
||||||
|
}
|
||||||
|
return &ImageSpec{
|
||||||
|
Identifier: newName,
|
||||||
|
Type: src.Type,
|
||||||
|
Platform: src.Platform,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func replaceDigest(image string, digest v1.Hash) (string, error) {
|
||||||
|
if strings.HasPrefix(image, LocalPrefix) {
|
||||||
|
return image, nil
|
||||||
|
}
|
||||||
|
notag, err := WithoutTag(image)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s@%s:%s", notag, digest.Algorithm, digest.Hex), nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var UnsignedTestImage = filepath.Join("..", "..", "test", "testdata", "unsigned-test-image")
|
||||||
|
|
||||||
func TestRefToPurl(t *testing.T) {
|
func TestRefToPurl(t *testing.T) {
|
||||||
arm, err := ParsePlatform("arm64/linux")
|
arm, err := ParsePlatform("arm64/linux")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -54,10 +56,6 @@ func TestRefToPurl(t *testing.T) {
|
|||||||
assert.True(t, canonical)
|
assert.True(t, canonical)
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
|
||||||
UnsignedTestImage = filepath.Join("..", "..", "test", "testdata", "unsigned-test-image")
|
|
||||||
)
|
|
||||||
|
|
||||||
// Test fix for https://github.com/docker/secure-artifacts-team-issues/issues/202
|
// Test fix for https://github.com/docker/secure-artifacts-team-issues/issues/202
|
||||||
func TestImageDigestForPlatform(t *testing.T) {
|
func TestImageDigestForPlatform(t *testing.T) {
|
||||||
idx, err := layout.ImageIndexFromPath(UnsignedTestImage)
|
idx, err := layout.ImageIndexFromPath(UnsignedTestImage)
|
||||||
@@ -97,10 +95,10 @@ func TestWithoutTag(t *testing.T) {
|
|||||||
{name: "image:tag", expected: "index.docker.io/library/image"},
|
{name: "image:tag", expected: "index.docker.io/library/image"},
|
||||||
{name: "image", expected: "index.docker.io/library/image"},
|
{name: "image", expected: "index.docker.io/library/image"},
|
||||||
{name: "image:sha256-digest.att", expected: "index.docker.io/library/image"},
|
{name: "image:sha256-digest.att", expected: "index.docker.io/library/image"},
|
||||||
{name: "docker://image:tag", expected: "docker://index.docker.io/library/image"},
|
{name: RegistryPrefix + "image:tag", expected: RegistryPrefix + "index.docker.io/library/image"},
|
||||||
{name: "image@sha256:166710df254975d4a6c4c407c315951c22753dcaa829e020a3fd5d18fff70dd2", expected: "index.docker.io/library/image"},
|
{name: "image@sha256:166710df254975d4a6c4c407c315951c22753dcaa829e020a3fd5d18fff70dd2", expected: "index.docker.io/library/image"},
|
||||||
{name: "docker://image@sha256:166710df254975d4a6c4c407c315951c22753dcaa829e020a3fd5d18fff70dd2", expected: "docker://index.docker.io/library/image"},
|
{name: RegistryPrefix + "image@sha256:166710df254975d4a6c4c407c315951c22753dcaa829e020a3fd5d18fff70dd2", expected: RegistryPrefix + "index.docker.io/library/image"},
|
||||||
{name: "docker://127.0.0.1:36555/repo:latest", expected: "docker://127.0.0.1:36555/repo"},
|
{name: RegistryPrefix + "127.0.0.1:36555/repo:latest", expected: RegistryPrefix + "127.0.0.1:36555/repo"},
|
||||||
}
|
}
|
||||||
for _, c := range tc {
|
for _, c := range tc {
|
||||||
t.Run(c.name, func(t *testing.T) {
|
t.Run(c.name, func(t *testing.T) {
|
||||||
@@ -118,11 +116,11 @@ func TestReplaceTag(t *testing.T) {
|
|||||||
{name: "image:tag", expected: "index.docker.io/library/image:sha256-digest.att"},
|
{name: "image:tag", expected: "index.docker.io/library/image:sha256-digest.att"},
|
||||||
{name: "image", expected: "index.docker.io/library/image:sha256-digest.att"},
|
{name: "image", expected: "index.docker.io/library/image:sha256-digest.att"},
|
||||||
{name: "image:sha256-digest.att", expected: "index.docker.io/library/image:sha256-digest.att"},
|
{name: "image:sha256-digest.att", expected: "index.docker.io/library/image:sha256-digest.att"},
|
||||||
{name: "docker://image:tag", expected: "docker://index.docker.io/library/image:sha256-digest.att"},
|
{name: RegistryPrefix + "image:tag", expected: RegistryPrefix + "index.docker.io/library/image:sha256-digest.att"},
|
||||||
{name: "image@sha256:166710df254975d4a6c4c407c315951c22753dcaa829e020a3fd5d18fff70dd2", expected: "index.docker.io/library/image:sha256-digest.att"},
|
{name: "image@sha256:166710df254975d4a6c4c407c315951c22753dcaa829e020a3fd5d18fff70dd2", expected: "index.docker.io/library/image:sha256-digest.att"},
|
||||||
{name: "oci://foobar", expected: "oci://foobar"},
|
{name: LocalPrefix + "foobar", expected: LocalPrefix + "foobar"},
|
||||||
{name: "docker://image@sha256:166710df254975d4a6c4c407c315951c22753dcaa829e020a3fd5d18fff70dd2", expected: "docker://index.docker.io/library/image:sha256-digest.att"},
|
{name: RegistryPrefix + "image@sha256:166710df254975d4a6c4c407c315951c22753dcaa829e020a3fd5d18fff70dd2", expected: RegistryPrefix + "index.docker.io/library/image:sha256-digest.att"},
|
||||||
{name: "docker://127.0.0.1:36555/repo:latest", expected: "docker://127.0.0.1:36555/repo:sha256-digest.att"},
|
{name: RegistryPrefix + "127.0.0.1:36555/repo:latest", expected: RegistryPrefix + "127.0.0.1:36555/repo:sha256-digest.att"},
|
||||||
}
|
}
|
||||||
|
|
||||||
digest := v1.Hash{
|
digest := v1.Hash{
|
||||||
|
|||||||
142
pkg/oci/output.go
Normal file
142
pkg/oci/output.go
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
package oci
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/docker/attest/pkg/attestation"
|
||||||
|
"github.com/google/go-containerregistry/pkg/name"
|
||||||
|
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||||
|
"github.com/google/go-containerregistry/pkg/v1/empty"
|
||||||
|
"github.com/google/go-containerregistry/pkg/v1/layout"
|
||||||
|
"github.com/google/go-containerregistry/pkg/v1/mutate"
|
||||||
|
"github.com/google/go-containerregistry/pkg/v1/remote"
|
||||||
|
)
|
||||||
|
|
||||||
|
func PushImageToRegistry(image v1.Image, imageName string) error {
|
||||||
|
ref, err := name.ParseReference(imageName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed to parse image name '%s': %w", imageName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push the image to the registry
|
||||||
|
return remote.Write(ref, image, MultiKeychainOption())
|
||||||
|
}
|
||||||
|
|
||||||
|
func PushIndexToRegistry(index v1.ImageIndex, imageName string) error {
|
||||||
|
// Parse the index name
|
||||||
|
ref, err := name.ParseReference(imageName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed to parse image name: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push the index to the registry
|
||||||
|
return remote.WriteIndex(ref, index, MultiKeychainOption())
|
||||||
|
}
|
||||||
|
|
||||||
|
func SaveImageAsOCILayout(image v1.Image, path string) error {
|
||||||
|
// Save the image to the local filesystem
|
||||||
|
err := os.MkdirAll(path, os.ModePerm)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create directory: %w", err)
|
||||||
|
}
|
||||||
|
index := empty.Index
|
||||||
|
l, err := layout.Write(path, index)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create index: %w", err)
|
||||||
|
}
|
||||||
|
return l.AppendImage(image)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SaveIndexAsOCILayout(image v1.ImageIndex, path string) error {
|
||||||
|
// Save the index to the local filesystem
|
||||||
|
err := os.MkdirAll(path, os.ModePerm)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create directory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = layout.Write(path, image)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create index: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func SaveIndex(outputs []*ImageSpec, index v1.ImageIndex, indexName string) error {
|
||||||
|
// split output by comma and write or push each one
|
||||||
|
for _, output := range outputs {
|
||||||
|
if output.Type == OCI {
|
||||||
|
idx := v1.ImageIndex(empty.Index)
|
||||||
|
idx = mutate.AppendManifests(idx, mutate.IndexAddendum{
|
||||||
|
Add: index,
|
||||||
|
Descriptor: v1.Descriptor{
|
||||||
|
Annotations: map[string]string{
|
||||||
|
OCIReferenceTarget: indexName,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
err := SaveIndexAsOCILayout(idx, output.Identifier)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to write signed image: %w", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err := PushIndexToRegistry(index, output.Identifier)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to push signed image: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func SaveImage(output *ImageSpec, image v1.Image, imageName string) error {
|
||||||
|
if output.Type == OCI {
|
||||||
|
idx := v1.ImageIndex(empty.Index)
|
||||||
|
idx = mutate.AppendManifests(idx, mutate.IndexAddendum{
|
||||||
|
Add: image,
|
||||||
|
Descriptor: v1.Descriptor{
|
||||||
|
Annotations: map[string]string{
|
||||||
|
OCIReferenceTarget: imageName,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
err := SaveIndexAsOCILayout(idx, output.Identifier)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to write signed image: %w", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err := PushImageToRegistry(image, output.Identifier)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to push signed image: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func SaveReferrers(manifest *attestation.Manifest, outputs []*ImageSpec) error {
|
||||||
|
for _, output := range outputs {
|
||||||
|
// OCI layout output for referrers not supported
|
||||||
|
if output.Type == OCI {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
images, err := manifest.BuildReferringArtifacts()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to build image: %w", err)
|
||||||
|
}
|
||||||
|
for _, image := range images {
|
||||||
|
digest, err := image.Digest()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get attestation image digest: %w", err)
|
||||||
|
}
|
||||||
|
attOut, err := ReplaceDigestInSpec(output, digest)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create attestation image spec: %w", err)
|
||||||
|
}
|
||||||
|
err = PushImageToRegistry(image, attOut.Identifier)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to push image: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package mirror
|
package oci_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -33,17 +33,16 @@ func TestSavingIndex(t *testing.T) {
|
|||||||
indexName := fmt.Sprintf("%s/repo:root", u.Host)
|
indexName := fmt.Sprintf("%s/repo:root", u.Host)
|
||||||
output, err := oci.ParseImageSpecs(indexName)
|
output, err := oci.ParseImageSpecs(indexName)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
err = SaveIndex(output, attIdx.Index, indexName)
|
err = oci.SaveIndex(output, attIdx.Index, indexName)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
ociOutput, err := oci.ParseImageSpecs("oci://" + outputLayout)
|
ociOutput, err := oci.ParseImageSpecs(oci.LocalPrefix + outputLayout)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
err = SaveIndex(ociOutput, attIdx.Index, indexName)
|
err = oci.SaveIndex(ociOutput, attIdx.Index, indexName)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSavingImage(t *testing.T) {
|
func TestSavingImage(t *testing.T) {
|
||||||
|
|
||||||
outputLayout := test.CreateTempDir(t, "", "mirror-test")
|
outputLayout := test.CreateTempDir(t, "", "mirror-test")
|
||||||
|
|
||||||
img := empty.Image
|
img := empty.Image
|
||||||
@@ -57,12 +56,12 @@ func TestSavingImage(t *testing.T) {
|
|||||||
indexName := fmt.Sprintf("%s/repo:root", u.Host)
|
indexName := fmt.Sprintf("%s/repo:root", u.Host)
|
||||||
output, err := oci.ParseImageSpec(indexName)
|
output, err := oci.ParseImageSpec(indexName)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
err = SaveImage(output, img, indexName)
|
err = oci.SaveImage(output, img, indexName)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
ociOutput, err := oci.ParseImageSpec("oci://" + outputLayout)
|
ociOutput, err := oci.ParseImageSpec(oci.LocalPrefix + outputLayout)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
err = SaveImage(ociOutput, img, indexName)
|
err = oci.SaveImage(ociOutput, img, indexName)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,12 +93,12 @@ func TestSavingReferrers(t *testing.T) {
|
|||||||
indexName := fmt.Sprintf("%s/repo:root", u.Host)
|
indexName := fmt.Sprintf("%s/repo:root", u.Host)
|
||||||
output, err := oci.ParseImageSpecs(indexName)
|
output, err := oci.ParseImageSpecs(indexName)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
err = SaveReferrers(manifest, output)
|
err = oci.SaveReferrers(manifest, output)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
reg := &test.MockRegistryResolver{
|
reg := &oci.MockRegistryResolver{
|
||||||
Subject: subject,
|
Subject: subject,
|
||||||
MockResolver: &test.MockResolver{},
|
MockResolver: &oci.MockResolver{},
|
||||||
ImageNameStr: indexName,
|
ImageNameStr: indexName,
|
||||||
}
|
}
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -3,6 +3,7 @@ package oci
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/attest/pkg/attestation"
|
"github.com/docker/attest/pkg/attestation"
|
||||||
att "github.com/docker/attest/pkg/attestation"
|
att "github.com/docker/attest/pkg/attestation"
|
||||||
@@ -35,8 +36,7 @@ func WithReferrersRepo(repo string) func(*ReferrersResolver) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ReferrersResolver) resolveAttestations(ctx context.Context, predicateType string) ([]*attestation.AttestationManifest,
|
func (r *ReferrersResolver) resolveAttestations(ctx context.Context, predicateType string) ([]*attestation.Manifest, error) {
|
||||||
error) {
|
|
||||||
dsseMediaType, err := attestation.DSSEMediaType(predicateType)
|
dsseMediaType, err := attestation.DSSEMediaType(predicateType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get DSSE media type for predicate '%s': %w", predicateType, err)
|
return nil, fmt.Errorf("failed to get DSSE media type for predicate '%s': %w", predicateType, err)
|
||||||
@@ -59,7 +59,7 @@ func (r *ReferrersResolver) resolveAttestations(ctx context.Context, predicateTy
|
|||||||
}
|
}
|
||||||
var referrersSubjectRef name.Digest
|
var referrersSubjectRef name.Digest
|
||||||
if r.referrersRepo != "" {
|
if r.referrersRepo != "" {
|
||||||
referrersSubjectRef, err = name.NewDigest(fmt.Sprintf("%s@%s", r.referrersRepo, subjectDigest))
|
referrersSubjectRef, err = name.NewDigest(fmt.Sprintf("%s@%s", strings.TrimPrefix(r.referrersRepo, RegistryPrefix), subjectDigest))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create referrers reference: %w", err)
|
return nil, fmt.Errorf("failed to create referrers reference: %w", err)
|
||||||
}
|
}
|
||||||
@@ -76,8 +76,9 @@ func (r *ReferrersResolver) resolveAttestations(ctx context.Context, predicateTy
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get index manifest: %w", err)
|
return nil, fmt.Errorf("failed to get index manifest: %w", err)
|
||||||
}
|
}
|
||||||
aManifests := make([]*attestation.AttestationManifest, 0)
|
aManifests := make([]*attestation.Manifest, 0)
|
||||||
for _, m := range referrersIndexManifest.Manifests {
|
for i := range referrersIndexManifest.Manifests {
|
||||||
|
m := referrersIndexManifest.Manifests[i]
|
||||||
remoteRef := referrersSubjectRef.Context().Digest(m.Digest.String())
|
remoteRef := referrersSubjectRef.Context().Digest(m.Digest.String())
|
||||||
options = WithOptions(ctx, nil)
|
options = WithOptions(ctx, nil)
|
||||||
attestationImage, err := remote.Image(remoteRef, options...)
|
attestationImage, err := remote.Image(remoteRef, options...)
|
||||||
@@ -98,7 +99,7 @@ func (r *ReferrersResolver) resolveAttestations(ctx context.Context, predicateTy
|
|||||||
if string(mt) != dsseMediaType {
|
if string(mt) != dsseMediaType {
|
||||||
return nil, fmt.Errorf("expected layer media type %s, got %s", dsseMediaType, mt)
|
return nil, fmt.Errorf("expected layer media type %s, got %s", dsseMediaType, mt)
|
||||||
}
|
}
|
||||||
attest := &attestation.AttestationManifest{
|
attest := &attestation.Manifest{
|
||||||
SubjectName: imageName,
|
SubjectName: imageName,
|
||||||
OriginalLayers: layers,
|
OriginalLayers: layers,
|
||||||
OriginalDescriptor: &m,
|
OriginalDescriptor: &m,
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import (
|
|||||||
|
|
||||||
type RegistryResolver struct {
|
type RegistryResolver struct {
|
||||||
*RegistryImageDetailsResolver
|
*RegistryImageDetailsResolver
|
||||||
*attestation.AttestationManifest
|
*attestation.Manifest
|
||||||
}
|
}
|
||||||
|
|
||||||
type RegistryImageDetailsResolver struct {
|
type RegistryImageDetailsResolver struct {
|
||||||
@@ -33,11 +33,11 @@ func NewRegistryAttestationResolver(src *RegistryImageDetailsResolver) (*Registr
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RegistryImageDetailsResolver) ImageName(ctx context.Context) (string, error) {
|
func (r *RegistryImageDetailsResolver) ImageName(_ context.Context) (string, error) {
|
||||||
return r.Identifier, nil
|
return r.Identifier, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RegistryImageDetailsResolver) ImagePlatform(ctx context.Context) (*v1.Platform, error) {
|
func (r *RegistryImageDetailsResolver) ImagePlatform(_ context.Context) (*v1.Platform, error) {
|
||||||
return r.Platform, nil
|
return r.Platform, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,17 +74,17 @@ func (r *RegistryImageDetailsResolver) ImageDescriptor(ctx context.Context) (*v1
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *RegistryResolver) Attestations(ctx context.Context, predicateType string) ([]*att.Envelope, error) {
|
func (r *RegistryResolver) Attestations(ctx context.Context, predicateType string) ([]*att.Envelope, error) {
|
||||||
if r.AttestationManifest == nil {
|
if r.Manifest == nil {
|
||||||
attest, err := FetchAttestationManifest(ctx, r.Identifier, r.ImageSpec.Platform)
|
attest, err := FetchAttestationManifest(ctx, r.Identifier, r.ImageSpec.Platform)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
r.AttestationManifest = attest
|
r.Manifest = attest
|
||||||
}
|
}
|
||||||
return ExtractEnvelopes(r.AttestationManifest, predicateType)
|
return ExtractEnvelopes(r.Manifest, predicateType)
|
||||||
}
|
}
|
||||||
|
|
||||||
func FetchAttestationManifest(ctx context.Context, image string, platform *v1.Platform) (*attestation.AttestationManifest, error) {
|
func FetchAttestationManifest(ctx context.Context, image string, platform *v1.Platform) (*attestation.Manifest, error) {
|
||||||
// we want to get to the image index, so ignoring platform for now
|
// we want to get to the image index, so ignoring platform for now
|
||||||
options := WithOptions(ctx, nil)
|
options := WithOptions(ctx, nil)
|
||||||
ref, err := name.ParseReference(image)
|
ref, err := name.ParseReference(image)
|
||||||
@@ -131,7 +131,7 @@ func FetchAttestationManifest(ctx context.Context, image string, platform *v1.Pl
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get attestations from image: %w", err)
|
return nil, fmt.Errorf("failed to get attestations from image: %w", err)
|
||||||
}
|
}
|
||||||
attest := &attestation.AttestationManifest{
|
attest := &attestation.Manifest{
|
||||||
OriginalLayers: layers,
|
OriginalLayers: layers,
|
||||||
OriginalDescriptor: &remoteDescriptor.Descriptor,
|
OriginalDescriptor: &remoteDescriptor.Descriptor,
|
||||||
SubjectName: image,
|
SubjectName: image,
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import (
|
|||||||
"github.com/docker/attest/internal/test"
|
"github.com/docker/attest/internal/test"
|
||||||
"github.com/docker/attest/pkg/attest"
|
"github.com/docker/attest/pkg/attest"
|
||||||
"github.com/docker/attest/pkg/attestation"
|
"github.com/docker/attest/pkg/attestation"
|
||||||
"github.com/docker/attest/pkg/mirror"
|
|
||||||
"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/google/go-containerregistry/pkg/registry"
|
"github.com/google/go-containerregistry/pkg/registry"
|
||||||
@@ -36,7 +35,7 @@ func TestRegistry(t *testing.T) {
|
|||||||
|
|
||||||
indexName := fmt.Sprintf("%s/repo:root", u.Host)
|
indexName := fmt.Sprintf("%s/repo:root", u.Host)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
err = mirror.PushIndexToRegistry(signedIndex, indexName)
|
err = oci.PushIndexToRegistry(signedIndex, indexName)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
spec, err := oci.ParseImageSpec(indexName)
|
spec, err := oci.ParseImageSpec(indexName)
|
||||||
|
|||||||
@@ -11,18 +11,20 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
OciReferenceTarget = "org.opencontainers.image.ref.name"
|
OCIReferenceTarget = "org.opencontainers.image.ref.name"
|
||||||
LocalPrefix = "oci://"
|
LocalPrefix = "oci://"
|
||||||
RegistryPrefix = "docker://"
|
RegistryPrefix = "docker://"
|
||||||
OCI SourceType = "OCI"
|
OCI SourceType = "OCI"
|
||||||
Docker SourceType = "Docker"
|
Docker SourceType = "Docker"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SourceType string
|
type (
|
||||||
type NamedIndex struct {
|
SourceType string
|
||||||
Index v1.ImageIndex
|
NamedIndex struct {
|
||||||
Name string
|
Index v1.ImageIndex
|
||||||
}
|
Name string
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
type AttestationOptions struct {
|
type AttestationOptions struct {
|
||||||
NoReferrers bool
|
NoReferrers bool
|
||||||
@@ -50,7 +52,7 @@ func IndexFromPath(path string) (*NamedIndex, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get digest: %w", err)
|
return nil, fmt.Errorf("failed to get digest: %w", err)
|
||||||
}
|
}
|
||||||
imageName := idxm.Manifests[0].Annotations[OciReferenceTarget]
|
imageName := idxm.Manifests[0].Annotations[OCIReferenceTarget]
|
||||||
idxDigest := idxm.Manifests[0].Digest
|
idxDigest := idxm.Manifests[0].Digest
|
||||||
|
|
||||||
idx, err := wrapperIdx.ImageIndex(idxDigest)
|
idx, err := wrapperIdx.ImageIndex(idxDigest)
|
||||||
@@ -83,9 +85,8 @@ func IndexFromRemote(image string) (*NamedIndex, error) {
|
|||||||
func LoadIndex(input *ImageSpec) (*NamedIndex, error) {
|
func LoadIndex(input *ImageSpec) (*NamedIndex, error) {
|
||||||
if input.Type == OCI {
|
if input.Type == OCI {
|
||||||
return IndexFromPath(input.Identifier)
|
return IndexFromPath(input.Identifier)
|
||||||
} else {
|
|
||||||
return IndexFromRemote(input.Identifier)
|
|
||||||
}
|
}
|
||||||
|
return IndexFromRemote(input.Identifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *ImageSpec) ForPlatforms(platform string) ([]*ImageSpec, error) {
|
func (i *ImageSpec) ForPlatforms(platform string) ([]*ImageSpec, error) {
|
||||||
|
|||||||
@@ -11,20 +11,20 @@ type policyEvaluatorCtxKeyType struct{}
|
|||||||
|
|
||||||
var PolicyEvaluatorCtxKey policyEvaluatorCtxKeyType
|
var PolicyEvaluatorCtxKey policyEvaluatorCtxKeyType
|
||||||
|
|
||||||
// sets PolicyEvaluator in context
|
// sets PolicyEvaluator in context.
|
||||||
func WithPolicyEvaluator(ctx context.Context, pe PolicyEvaluator) context.Context {
|
func WithPolicyEvaluator(ctx context.Context, pe Evaluator) context.Context {
|
||||||
return context.WithValue(ctx, PolicyEvaluatorCtxKey, pe)
|
return context.WithValue(ctx, PolicyEvaluatorCtxKey, pe)
|
||||||
}
|
}
|
||||||
|
|
||||||
// gets PolicyEvaluator from context, defaults to Rego PolicyEvaluator if not set
|
// gets PolicyEvaluator from context, defaults to Rego PolicyEvaluator if not set.
|
||||||
func GetPolicyEvaluator(ctx context.Context) (PolicyEvaluator, error) {
|
func GetPolicyEvaluator(ctx context.Context) (Evaluator, error) {
|
||||||
t, ok := ctx.Value(PolicyEvaluatorCtxKey).(PolicyEvaluator)
|
t, ok := ctx.Value(PolicyEvaluatorCtxKey).(Evaluator)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("no policy evaluator client set on context (set one with policy.WithPolicyEvaluator)")
|
return nil, fmt.Errorf("no policy evaluator client set on context (set one with policy.WithPolicyEvaluator)")
|
||||||
}
|
}
|
||||||
return t, nil
|
return t, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type PolicyEvaluator interface {
|
type Evaluator interface {
|
||||||
Evaluate(ctx context.Context, resolver oci.AttestationResolver, pctx *Policy, input *PolicyInput) (*Result, error)
|
Evaluate(ctx context.Context, resolver oci.AttestationResolver, pctx *Policy, input *Input) (*Result, error)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,19 +7,19 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type MockPolicyEvaluator struct {
|
type MockPolicyEvaluator struct {
|
||||||
EvaluateFunc func(ctx context.Context, resolver oci.AttestationResolver, pctx *Policy, input *PolicyInput) (*Result, error)
|
EvaluateFunc func(ctx context.Context, resolver oci.AttestationResolver, pctx *Policy, input *Input) (*Result, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pe *MockPolicyEvaluator) Evaluate(ctx context.Context, resolver oci.AttestationResolver, pctx *Policy, input *PolicyInput) (*Result, error) {
|
func (pe *MockPolicyEvaluator) Evaluate(ctx context.Context, resolver oci.AttestationResolver, pctx *Policy, input *Input) (*Result, error) {
|
||||||
if pe.EvaluateFunc != nil {
|
if pe.EvaluateFunc != nil {
|
||||||
return pe.EvaluateFunc(ctx, resolver, pctx, input)
|
return pe.EvaluateFunc(ctx, resolver, pctx, input)
|
||||||
}
|
}
|
||||||
return AllowedResult(), nil
|
return AllowedResult(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetMockPolicy() PolicyEvaluator {
|
func GetMockPolicy() Evaluator {
|
||||||
return &MockPolicyEvaluator{
|
return &MockPolicyEvaluator{
|
||||||
EvaluateFunc: func(ctx context.Context, resolver oci.AttestationResolver, pctx *Policy, input *PolicyInput) (*Result, error) {
|
EvaluateFunc: func(_ context.Context, _ oci.AttestationResolver, _ *Policy, _ *Input) (*Result, error) {
|
||||||
return AllowedResult(), nil
|
return AllowedResult(), nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,11 +12,11 @@ import (
|
|||||||
"github.com/docker/attest/pkg/oci"
|
"github.com/docker/attest/pkg/oci"
|
||||||
)
|
)
|
||||||
|
|
||||||
func resolveLocalPolicy(opts *PolicyOptions, mapping *config.PolicyMapping, imageName string, matchedName string) (*Policy, error) {
|
func resolveLocalPolicy(opts *Options, mapping *config.PolicyMapping, imageName string, matchedName string) (*Policy, error) {
|
||||||
if opts.LocalPolicyDir == "" {
|
if opts.LocalPolicyDir == "" {
|
||||||
return nil, fmt.Errorf("local policy dir not set")
|
return nil, fmt.Errorf("local policy dir not set")
|
||||||
}
|
}
|
||||||
files := make([]*PolicyFile, 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
|
||||||
filePath := path.Join(opts.LocalPolicyDir, filename)
|
filePath := path.Join(opts.LocalPolicyDir, filename)
|
||||||
@@ -24,7 +24,7 @@ func resolveLocalPolicy(opts *PolicyOptions, mapping *config.PolicyMapping, imag
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to read policy file %s: %w", filename, err)
|
return nil, fmt.Errorf("failed to read policy file %s: %w", filename, err)
|
||||||
}
|
}
|
||||||
files = append(files, &PolicyFile{
|
files = append(files, &File{
|
||||||
Path: filename,
|
Path: filename,
|
||||||
Content: fileContents,
|
Content: fileContents,
|
||||||
})
|
})
|
||||||
@@ -39,15 +39,15 @@ func resolveLocalPolicy(opts *PolicyOptions, mapping *config.PolicyMapping, imag
|
|||||||
return policy, nil
|
return policy, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func resolveTufPolicy(opts *PolicyOptions, mapping *config.PolicyMapping, imageName string, matchedName string) (*Policy, error) {
|
func resolveTUFPolicy(opts *Options, mapping *config.PolicyMapping, imageName string, matchedName string) (*Policy, error) {
|
||||||
files := make([]*PolicyFile, 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
|
||||||
_, fileContents, err := opts.TufClient.DownloadTarget(filename, filepath.Join(opts.LocalTargetsDir, filename))
|
_, fileContents, err := opts.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)
|
||||||
}
|
}
|
||||||
files = append(files, &PolicyFile{
|
files = append(files, &File{
|
||||||
Path: filename,
|
Path: filename,
|
||||||
Content: fileContents,
|
Content: fileContents,
|
||||||
})
|
})
|
||||||
@@ -88,12 +88,12 @@ func findPolicyMatchImpl(imageName string, mappings *config.PolicyMappings, matc
|
|||||||
for _, rule := range mappings.Rules {
|
for _, rule := range mappings.Rules {
|
||||||
if rule.Pattern.MatchString(imageName) {
|
if rule.Pattern.MatchString(imageName) {
|
||||||
switch {
|
switch {
|
||||||
case rule.PolicyId == "" && rule.Replacement == "":
|
case rule.PolicyID == "" && rule.Replacement == "":
|
||||||
return nil, fmt.Errorf("rule %s has neither policy-id nor rewrite", rule.Pattern)
|
return nil, fmt.Errorf("rule %s has neither policy-id nor rewrite", rule.Pattern)
|
||||||
case rule.PolicyId != "" && rule.Replacement != "":
|
case rule.PolicyID != "" && rule.Replacement != "":
|
||||||
return nil, fmt.Errorf("rule %s has both policy-id and rewrite", rule.Pattern)
|
return nil, fmt.Errorf("rule %s has both policy-id and rewrite", rule.Pattern)
|
||||||
case rule.PolicyId != "":
|
case rule.PolicyID != "":
|
||||||
policy := mappings.Policies[rule.PolicyId]
|
policy := mappings.Policies[rule.PolicyID]
|
||||||
if policy != nil {
|
if policy != nil {
|
||||||
return &policyMatch{
|
return &policyMatch{
|
||||||
matchType: matchTypePolicy,
|
matchType: matchTypePolicy,
|
||||||
@@ -120,35 +120,35 @@ 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 *PolicyOptions) (*Policy, error) {
|
func resolvePolicyByID(opts *Options) (*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 {
|
||||||
return nil, fmt.Errorf("failed to load local policy mappings: %w", err)
|
return nil, fmt.Errorf("failed to load local policy mappings: %w", err)
|
||||||
}
|
}
|
||||||
if localMappings != nil {
|
if localMappings != nil {
|
||||||
policy := localMappings.Policies[opts.PolicyId]
|
policy := localMappings.Policies[opts.PolicyID]
|
||||||
if policy != nil {
|
if policy != nil {
|
||||||
return resolveLocalPolicy(opts, policy, "", "")
|
return resolveLocalPolicy(opts, policy, "", "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// must check tuf
|
// must check tuf
|
||||||
tufMappings, err := config.LoadTufMappings(opts.TufClient, opts.LocalTargetsDir)
|
tufMappings, err := config.LoadTUFMappings(opts.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, 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 *PolicyOptions) (*Policy, error) {
|
func ResolvePolicy(ctx context.Context, detailsResolver oci.ImageDetailsResolver, opts *Options) (*Policy, error) {
|
||||||
p, err := resolvePolicyById(opts)
|
p, err := resolvePolicyByID(opts)
|
||||||
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)
|
||||||
}
|
}
|
||||||
@@ -175,7 +175,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(opts.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)
|
||||||
}
|
}
|
||||||
@@ -183,8 +183,8 @@ func ResolvePolicy(ctx context.Context, detailsResolver oci.ImageDetailsResolver
|
|||||||
// it's a mirror of a tuf policy
|
// it's a mirror of a tuf policy
|
||||||
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, mapping, imageName, match.matchedName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -195,7 +195,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, match.policy, imageName, match.matchedName)
|
||||||
}
|
}
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
@@ -219,20 +219,20 @@ func CreateImageDetailsResolver(imageSource *oci.ImageSpec) (oci.ImageDetailsRes
|
|||||||
}
|
}
|
||||||
|
|
||||||
func CreateAttestationResolver(resolver oci.ImageDetailsResolver, mapping *config.PolicyMapping) (oci.AttestationResolver, error) {
|
func CreateAttestationResolver(resolver oci.ImageDetailsResolver, mapping *config.PolicyMapping) (oci.AttestationResolver, error) {
|
||||||
switch resolver := resolver.(type) {
|
if mapping.Attestations != nil {
|
||||||
case *oci.RegistryImageDetailsResolver:
|
if mapping.Attestations.Style == config.AttestationStyleAttached {
|
||||||
if mapping.Attestations != nil && mapping.Attestations.Style == config.AttestationStyleAttached {
|
switch resolver := resolver.(type) {
|
||||||
return oci.NewRegistryAttestationResolver(resolver)
|
case *oci.RegistryImageDetailsResolver:
|
||||||
} else {
|
return oci.NewRegistryAttestationResolver(resolver)
|
||||||
if mapping.Attestations != nil && mapping.Attestations.Repo != "" {
|
case *oci.LayoutResolver:
|
||||||
return oci.NewReferrersAttestationResolver(resolver, oci.WithReferrersRepo(mapping.Attestations.Repo))
|
return resolver, nil
|
||||||
} else {
|
default:
|
||||||
return oci.NewReferrersAttestationResolver(resolver)
|
return nil, fmt.Errorf("unsupported image details resolver type: %T", resolver)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case *oci.OCILayoutResolver:
|
if mapping.Attestations.Repo != "" {
|
||||||
return resolver, nil
|
return oci.NewReferrersAttestationResolver(resolver, oci.WithReferrersRepo(mapping.Attestations.Repo))
|
||||||
default:
|
}
|
||||||
return nil, fmt.Errorf("unsupported image details resolver type: %T", resolver)
|
|
||||||
}
|
}
|
||||||
|
return oci.NewReferrersAttestationResolver(resolver)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ func TestFindPolicyMatch(t *testing.T) {
|
|||||||
assert.Equal(t, tc.expectedMatchType, match.matchType)
|
assert.Equal(t, tc.expectedMatchType, match.matchType)
|
||||||
if match.matchType == matchTypePolicy {
|
if match.matchType == matchTypePolicy {
|
||||||
if assert.NotNil(t, match.policy) {
|
if assert.NotNil(t, match.policy) {
|
||||||
assert.Equal(t, tc.expectedPolicyID, match.policy.Id)
|
assert.Equal(t, tc.expectedPolicyID, match.policy.ID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
assert.Equal(t, tc.expectedImageName, match.matchedName)
|
assert.Equal(t, tc.expectedImageName, match.matchedName)
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ func loadAttestation(t *testing.T, path string) *attestation.Envelope {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var env = new(attestation.Envelope)
|
env := new(attestation.Envelope)
|
||||||
err = json.Unmarshal(ex, env)
|
err = json.Unmarshal(ex, env)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -38,7 +38,7 @@ func TestRegoEvaluator_Evaluate(t *testing.T) {
|
|||||||
|
|
||||||
re := policy.NewRegoEvaluator(true)
|
re := policy.NewRegoEvaluator(true)
|
||||||
|
|
||||||
defaultResolver := test.MockResolver{
|
defaultResolver := oci.MockResolver{
|
||||||
Envs: []*attestation.Envelope{loadAttestation(t, ExampleAttestation)},
|
Envs: []*attestation.Envelope{loadAttestation(t, ExampleAttestation)},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,13 +47,13 @@ func TestRegoEvaluator_Evaluate(t *testing.T) {
|
|||||||
expectSuccess bool
|
expectSuccess bool
|
||||||
isCanonical bool
|
isCanonical bool
|
||||||
resolver oci.AttestationResolver
|
resolver oci.AttestationResolver
|
||||||
policy *policy.PolicyOptions
|
policy *policy.Options
|
||||||
policyId string
|
policyID string
|
||||||
errorStr string
|
errorStr string
|
||||||
}{
|
}{
|
||||||
{repo: "testdata/mock-tuf-allow", expectSuccess: true, isCanonical: false, resolver: defaultResolver},
|
{repo: "testdata/mock-tuf-allow", expectSuccess: true, isCanonical: false, resolver: defaultResolver},
|
||||||
{repo: "testdata/mock-tuf-allow", expectSuccess: true, isCanonical: false, resolver: defaultResolver, policyId: "docker-official-images"},
|
{repo: "testdata/mock-tuf-allow", expectSuccess: true, isCanonical: false, resolver: defaultResolver, policyID: "docker-official-images"},
|
||||||
{repo: "testdata/mock-tuf-allow", expectSuccess: false, isCanonical: false, resolver: defaultResolver, policyId: "non-existent-policy-id", errorStr: errorStr},
|
{repo: "testdata/mock-tuf-allow", expectSuccess: false, isCanonical: false, resolver: defaultResolver, policyID: "non-existent-policy-id", errorStr: errorStr},
|
||||||
{repo: "testdata/mock-tuf-deny", expectSuccess: false, isCanonical: false, resolver: defaultResolver},
|
{repo: "testdata/mock-tuf-deny", expectSuccess: false, isCanonical: false, resolver: defaultResolver},
|
||||||
{repo: "testdata/mock-tuf-verify-sig", expectSuccess: true, isCanonical: false, resolver: defaultResolver},
|
{repo: "testdata/mock-tuf-verify-sig", expectSuccess: true, isCanonical: false, resolver: defaultResolver},
|
||||||
{repo: "testdata/mock-tuf-wrong-key", expectSuccess: false, isCanonical: false, resolver: defaultResolver},
|
{repo: "testdata/mock-tuf-wrong-key", expectSuccess: false, isCanonical: false, resolver: defaultResolver},
|
||||||
@@ -63,18 +63,18 @@ func TestRegoEvaluator_Evaluate(t *testing.T) {
|
|||||||
|
|
||||||
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.PolicyInput{
|
input := &policy.Input{
|
||||||
Digest: "sha256:test-digest",
|
Digest: "sha256:test-digest",
|
||||||
Purl: "test-purl",
|
PURL: "test-purl",
|
||||||
IsCanonical: tc.isCanonical,
|
IsCanonical: tc.isCanonical,
|
||||||
}
|
}
|
||||||
|
|
||||||
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.PolicyOptions{
|
tc.policy = &policy.Options{
|
||||||
TufClient: tufClient,
|
TUFClient: tufClient,
|
||||||
LocalTargetsDir: test.CreateTempDir(t, "", "tuf-targets"),
|
LocalTargetsDir: test.CreateTempDir(t, "", "tuf-targets"),
|
||||||
PolicyId: tc.policyId,
|
PolicyID: tc.policyID,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
imageName, err := tc.resolver.ImageName(ctx)
|
imageName, err := tc.resolver.ImageName(ctx)
|
||||||
@@ -103,7 +103,6 @@ func TestRegoEvaluator_Evaluate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadingMappings(t *testing.T) {
|
func TestLoadingMappings(t *testing.T) {
|
||||||
@@ -111,8 +110,69 @@ func TestLoadingMappings(t *testing.T) {
|
|||||||
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 {
|
||||||
if mirror.PolicyId != "" {
|
if mirror.PolicyID != "" {
|
||||||
assert.Equal(t, "docker-official-images", mirror.PolicyId)
|
assert.Equal(t, "docker-official-images", mirror.PolicyID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCreateAttestationResolver(t *testing.T) {
|
||||||
|
mockResolver := oci.MockResolver{
|
||||||
|
Envs: []*attestation.Envelope{},
|
||||||
|
}
|
||||||
|
layoutResolver := &oci.LayoutResolver{}
|
||||||
|
registryResolver := &oci.RegistryImageDetailsResolver{}
|
||||||
|
|
||||||
|
nilRepoReferrers := &config.PolicyMapping{
|
||||||
|
Attestations: &config.AttestationConfig{
|
||||||
|
Style: config.AttestationStyleReferrers,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
referrers := &config.PolicyMapping{
|
||||||
|
Attestations: &config.AttestationConfig{
|
||||||
|
Repo: "localhost:5000/repo",
|
||||||
|
Style: config.AttestationStyleReferrers,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
attached := &config.PolicyMapping{
|
||||||
|
Attestations: &config.AttestationConfig{
|
||||||
|
Style: config.AttestationStyleAttached,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
resolver oci.ImageDetailsResolver
|
||||||
|
mapping *config.PolicyMapping
|
||||||
|
errorStr string
|
||||||
|
}{
|
||||||
|
{name: "referrers", resolver: layoutResolver, mapping: referrers},
|
||||||
|
{name: "referrers (no mapped repo)", resolver: layoutResolver, mapping: nilRepoReferrers},
|
||||||
|
{name: "referrers (no mapping)", resolver: layoutResolver, mapping: &config.PolicyMapping{Attestations: nil}},
|
||||||
|
{name: "attached (registry)", resolver: registryResolver, mapping: attached},
|
||||||
|
{name: "attached (layout)", resolver: layoutResolver, mapping: attached},
|
||||||
|
{name: "attached (unsupported)", resolver: mockResolver, mapping: attached, errorStr: "unsupported image details resolver type"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
resolver, err := policy.CreateAttestationResolver(tc.resolver, tc.mapping)
|
||||||
|
if tc.errorStr == "" {
|
||||||
|
require.NoError(t, err)
|
||||||
|
} else {
|
||||||
|
assert.Contains(t, err.Error(), tc.errorStr)
|
||||||
|
}
|
||||||
|
if tc.mapping.Attestations == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch resolver.(type) {
|
||||||
|
case *oci.ReferrersResolver:
|
||||||
|
assert.Equal(t, tc.mapping.Attestations.Style, config.AttestationStyleReferrers)
|
||||||
|
case *oci.RegistryResolver:
|
||||||
|
assert.Equal(t, tc.mapping.Attestations.Style, config.AttestationStyleAttached)
|
||||||
|
case *oci.LayoutResolver:
|
||||||
|
assert.Equal(t, tc.mapping.Attestations.Style, config.AttestationStyleAttached)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -30,13 +30,13 @@ const (
|
|||||||
resultBinding = "result"
|
resultBinding = "result"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewRegoEvaluator(debug bool) PolicyEvaluator {
|
func NewRegoEvaluator(debug bool) Evaluator {
|
||||||
return ®oEvaluator{
|
return ®oEvaluator{
|
||||||
debug: debug,
|
debug: debug,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (re *regoEvaluator) Evaluate(ctx context.Context, resolver oci.AttestationResolver, pctx *Policy, input *PolicyInput) (*Result, error) {
|
func (re *regoEvaluator) Evaluate(ctx context.Context, resolver oci.AttestationResolver, pctx *Policy, input *Input) (*Result, error) {
|
||||||
var regoOpts []func(*rego.Rego)
|
var regoOpts []func(*rego.Rego)
|
||||||
|
|
||||||
// Create a new in-memory store
|
// Create a new in-memory store
|
||||||
@@ -113,7 +113,7 @@ func (re *regoEvaluator) Evaluate(ctx context.Context, resolver oci.AttestationR
|
|||||||
}
|
}
|
||||||
|
|
||||||
func jsonGenerator[T any]() func(t *ast.Term, ec *rego.EvalContext) (any, error) {
|
func jsonGenerator[T any]() func(t *ast.Term, ec *rego.EvalContext) (any, error) {
|
||||||
return func(t *ast.Term, ec *rego.EvalContext) (any, error) {
|
return func(t *ast.Term, _ *rego.EvalContext) (any, error) {
|
||||||
// TODO: this is horrible - we're converting the AST to JSON and then back to AST, then using ast.As to convert it to a struct
|
// TODO: this is horrible - we're converting the AST to JSON and then back to AST, then using ast.As to convert it to a struct
|
||||||
// We can't use ast.As directly because it fails if the AST contains a set
|
// We can't use ast.As directly because it fails if the AST contains a set
|
||||||
json, err := ast.JSON(t.Value)
|
json, err := ast.JSON(t.Value)
|
||||||
@@ -140,6 +140,7 @@ var verifyDecl = &ast.Builtin{
|
|||||||
Decl: types.NewFunction(types.Args(dynamicObj, dynamicObj), dynamicObj),
|
Decl: types.NewFunction(types.Args(dynamicObj, dynamicObj), dynamicObj),
|
||||||
Nondeterministic: true,
|
Nondeterministic: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
var attestDecl = &ast.Builtin{
|
var attestDecl = &ast.Builtin{
|
||||||
Name: "attest.fetch",
|
Name: "attest.fetch",
|
||||||
Decl: types.NewFunction(types.Args(types.S), dynamicObj),
|
Decl: types.NewFunction(types.Args(types.S), dynamicObj),
|
||||||
@@ -163,9 +164,9 @@ func handleErrors1(f func(rCtx rego.BuiltinContext, a *ast.Term) (*ast.Term, err
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleErrors2(f func(rCtx rego.BuiltinContext, a, b *ast.Term) (*ast.Term, error)) rego.Builtin2 {
|
func handleErrors2(f func(rCtx *rego.BuiltinContext, a, b *ast.Term) (*ast.Term, error)) rego.Builtin2 {
|
||||||
return func(rCtx rego.BuiltinContext, a, b *ast.Term) (*ast.Term, error) {
|
return func(rCtx rego.BuiltinContext, a, b *ast.Term) (*ast.Term, error) {
|
||||||
return wrapFunctionResult(f(rCtx, a, b))
|
return wrapFunctionResult(f(&rCtx, a, b))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,7 +181,7 @@ func RegoFunctions(resolver oci.AttestationResolver) []*tester.Builtin {
|
|||||||
Memoize: true,
|
Memoize: true,
|
||||||
Nondeterministic: verifyDecl.Nondeterministic,
|
Nondeterministic: verifyDecl.Nondeterministic,
|
||||||
},
|
},
|
||||||
handleErrors2(verifyIntotoEnvelope)),
|
handleErrors2(verifyInTotoEnvelope)),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Decl: attestDecl,
|
Decl: attestDecl,
|
||||||
@@ -191,12 +192,12 @@ func RegoFunctions(resolver oci.AttestationResolver) []*tester.Builtin {
|
|||||||
Memoize: true,
|
Memoize: true,
|
||||||
Nondeterministic: attestDecl.Nondeterministic,
|
Nondeterministic: attestDecl.Nondeterministic,
|
||||||
},
|
},
|
||||||
handleErrors1(fetchIntotoAttestations(resolver))),
|
handleErrors1(fetchInTotoAttestations(resolver))),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchIntotoAttestations(resolver oci.AttestationResolver) rego.Builtin1 {
|
func fetchInTotoAttestations(resolver oci.AttestationResolver) rego.Builtin1 {
|
||||||
return func(rCtx rego.BuiltinContext, predicateTypeTerm *ast.Term) (*ast.Term, error) {
|
return func(rCtx rego.BuiltinContext, predicateTypeTerm *ast.Term) (*ast.Term, error) {
|
||||||
predicateTypeStr, ok := predicateTypeTerm.Value.(ast.String)
|
predicateTypeStr, ok := predicateTypeTerm.Value.(ast.String)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -226,7 +227,7 @@ func fetchIntotoAttestations(resolver oci.AttestationResolver) rego.Builtin1 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func verifyIntotoEnvelope(rCtx rego.BuiltinContext, envTerm, optsTerm *ast.Term) (*ast.Term, error) {
|
func verifyInTotoEnvelope(rCtx *rego.BuiltinContext, envTerm, optsTerm *ast.Term) (*ast.Term, error) {
|
||||||
env := new(att.Envelope)
|
env := new(att.Envelope)
|
||||||
opts := new(att.VerifyOptions)
|
opts := new(att.VerifyOptions)
|
||||||
err := ast.As(envTerm.Value, env)
|
err := ast.As(envTerm.Value, env)
|
||||||
|
|||||||
@@ -26,29 +26,29 @@ type Result struct {
|
|||||||
Summary Summary `json:"summary"`
|
Summary Summary `json:"summary"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type PolicyOptions struct {
|
type Options struct {
|
||||||
TufClient tuf.TUFClient
|
TUFClient tuf.Downloader
|
||||||
LocalTargetsDir string
|
LocalTargetsDir string
|
||||||
LocalPolicyDir string
|
LocalPolicyDir string
|
||||||
PolicyId string
|
PolicyID string
|
||||||
ReferrersRepo string
|
ReferrersRepo string
|
||||||
AttestationStyle config.AttestationStyle
|
AttestationStyle config.AttestationStyle
|
||||||
}
|
}
|
||||||
|
|
||||||
type Policy struct {
|
type Policy struct {
|
||||||
InputFiles []*PolicyFile
|
InputFiles []*File
|
||||||
Query string
|
Query string
|
||||||
Mapping *config.PolicyMapping
|
Mapping *config.PolicyMapping
|
||||||
ResolvedName string
|
ResolvedName string
|
||||||
}
|
}
|
||||||
|
|
||||||
type PolicyInput struct {
|
type Input struct {
|
||||||
Digest string `json:"digest"`
|
Digest string `json:"digest"`
|
||||||
Purl string `json:"purl"`
|
PURL string `json:"purl"`
|
||||||
IsCanonical bool `json:"isCanonical"`
|
IsCanonical bool `json:"isCanonical"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type PolicyFile struct {
|
type File struct {
|
||||||
Path string
|
Path string
|
||||||
Content []byte
|
Content []byte
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,18 +9,18 @@ import (
|
|||||||
awssigner "github.com/sigstore/sigstore/pkg/signature/kms/aws"
|
awssigner "github.com/sigstore/sigstore/pkg/signature/kms/aws"
|
||||||
)
|
)
|
||||||
|
|
||||||
// using AWS KMS
|
// using AWS KMS.
|
||||||
func GetAWSSigner(ctx context.Context, keyArn string, region string) (dsse.SignerVerifier, error) {
|
func GetAWSSigner(ctx context.Context, keyARN string, region string) (dsse.SignerVerifier, error) {
|
||||||
keypath := fmt.Sprintf("awskms:///%s", keyArn)
|
keyPath := fmt.Sprintf("awskms:///%s", keyARN)
|
||||||
sv, err := awssigner.LoadSignerVerifier(ctx, keypath, config.WithRegion(region))
|
sv, err := awssigner.LoadSignerVerifier(ctx, keyPath, config.WithRegion(region))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error loading aws signer verifier: %w", err)
|
return nil, fmt.Errorf("error loading aws signer verifier: %w", err)
|
||||||
}
|
}
|
||||||
cs, _, err := sv.CryptoSigner(context.Background(), func(err error) {})
|
cs, _, err := sv.CryptoSigner(context.Background(), func(_ error) {})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error getting aws crypto signer: %w", err)
|
return nil, fmt.Errorf("error getting aws crypto signer: %w", err)
|
||||||
}
|
}
|
||||||
signer := &ECDSA256_SignerVerifier{
|
signer := &ECDSA256SignerVerifier{
|
||||||
Signer: cs,
|
Signer: cs,
|
||||||
}
|
}
|
||||||
return signer, nil
|
return signer, nil
|
||||||
|
|||||||
@@ -14,12 +14,12 @@ import (
|
|||||||
"github.com/secure-systems-lab/go-securesystemslib/dsse"
|
"github.com/secure-systems-lab/go-securesystemslib/dsse"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ECDSA256_SignerVerifier struct {
|
type ECDSA256SignerVerifier struct {
|
||||||
crypto.Signer
|
crypto.Signer
|
||||||
}
|
}
|
||||||
|
|
||||||
// implement keyid function
|
// implement keyid function.
|
||||||
func (s *ECDSA256_SignerVerifier) KeyID() (string, error) {
|
func (s *ECDSA256SignerVerifier) KeyID() (string, error) {
|
||||||
keyid, err := KeyID(s.Signer.Public())
|
keyid, err := KeyID(s.Signer.Public())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("error getting keyid: %w", err)
|
return "", fmt.Errorf("error getting keyid: %w", err)
|
||||||
@@ -27,15 +27,15 @@ func (s *ECDSA256_SignerVerifier) KeyID() (string, error) {
|
|||||||
return keyid, nil
|
return keyid, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ECDSA256_SignerVerifier) Public() crypto.PublicKey {
|
func (s *ECDSA256SignerVerifier) Public() crypto.PublicKey {
|
||||||
return s.Signer.Public()
|
return s.Signer.Public()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ECDSA256_SignerVerifier) Sign(ctx context.Context, data []byte) ([]byte, error) {
|
func (s *ECDSA256SignerVerifier) Sign(_ context.Context, data []byte) ([]byte, error) {
|
||||||
return s.Signer.Sign(rand.Reader, data, crypto.SHA256)
|
return s.Signer.Sign(rand.Reader, data, crypto.SHA256)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ECDSA256_SignerVerifier) Verify(ctx context.Context, data []byte, sig []byte) error {
|
func (s *ECDSA256SignerVerifier) Verify(_ context.Context, data []byte, sig []byte) error {
|
||||||
pub, ok := s.Signer.Public().(*ecdsa.PublicKey)
|
pub, ok := s.Signer.Public().(*ecdsa.PublicKey)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("public key is not ecdsa")
|
return fmt.Errorf("public key is not ecdsa")
|
||||||
@@ -52,7 +52,7 @@ func LoadKeyPair(priv []byte) (dsse.SignerVerifier, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &ECDSA256_SignerVerifier{
|
return &ECDSA256SignerVerifier{
|
||||||
Signer: privateKey,
|
Signer: privateKey,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@@ -78,7 +78,7 @@ func GenKeyPair() (dsse.SignerVerifier, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &ECDSA256_SignerVerifier{
|
return &ECDSA256SignerVerifier{
|
||||||
Signer: signer,
|
Signer: signer,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,18 +10,18 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// using GCP KMS
|
// using GCP KMS
|
||||||
// reference should be in the format projects/[PROJECT_ID]/locations/[LOCATION]/keyRings/[KEY_RING]/cryptoKeys/[KEY]/cryptoKeyVersions/[VERSION]
|
// reference should be in the format projects/[PROJECT_ID]/locations/[LOCATION]/keyRings/[KEY_RING]/cryptoKeys/[KEY]/cryptoKeyVersions/[VERSION].
|
||||||
func GetGCPSigner(ctx context.Context, reference string, opts ...option.ClientOption) (dsse.SignerVerifier, error) {
|
func GetGCPSigner(ctx context.Context, reference string, opts ...option.ClientOption) (dsse.SignerVerifier, error) {
|
||||||
reference = fmt.Sprintf("gcpkms://%s", reference)
|
reference = fmt.Sprintf("gcpkms://%s", reference)
|
||||||
sv, err := gcpsigner.LoadSignerVerifier(ctx, reference, opts...)
|
sv, err := gcpsigner.LoadSignerVerifier(ctx, reference, opts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error loading gcp signer verifier: %w", err)
|
return nil, fmt.Errorf("error loading gcp signer verifier: %w", err)
|
||||||
}
|
}
|
||||||
cs, _, err := sv.CryptoSigner(ctx, func(err error) {})
|
cs, _, err := sv.CryptoSigner(ctx, func(_ error) {})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error getting gcp crypto signer: %w", err)
|
return nil, fmt.Errorf("error getting gcp crypto signer: %w", err)
|
||||||
}
|
}
|
||||||
signer := &ECDSA256_SignerVerifier{
|
signer := &ECDSA256SignerVerifier{
|
||||||
Signer: cs,
|
Signer: cs,
|
||||||
}
|
}
|
||||||
return signer, nil
|
return signer, nil
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ func TestGCPKMS_Signer(t *testing.T) {
|
|||||||
keyId, err := signer.KeyID()
|
keyId, err := signer.KeyID()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.NotEmpty(t, keyId)
|
assert.NotEmpty(t, keyId)
|
||||||
publicKey, err := Parse([]byte(publicKeyPEM))
|
publicKey, err := ParsePublicKey([]byte(publicKeyPEM))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
// verify payload ecdsa signature
|
// verify payload ecdsa signature
|
||||||
ok := ecdsa.VerifyASN1(publicKey, hash, sig)
|
ok := ecdsa.VerifyASN1(publicKey, hash, sig)
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
func KeyID(pubKey crypto.PublicKey) (string, error) {
|
func KeyID(pubKey crypto.PublicKey) (string, error) {
|
||||||
pub, err := x509.MarshalPKIXPublicKey(pubKey)
|
pub, err := x509.MarshalPKIXPublicKey(pubKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("error marshalling public key: %w", err)
|
return "", fmt.Errorf("error marshaling public key: %w", err)
|
||||||
}
|
}
|
||||||
return util.SHA256Hex(pub), nil
|
return util.SHA256Hex(pub), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
|
|
||||||
const pemType = "PUBLIC KEY"
|
const pemType = "PUBLIC KEY"
|
||||||
|
|
||||||
func Parse(pubkeyBytes []byte) (*ecdsa.PublicKey, error) {
|
func ParsePublicKey(pubkeyBytes []byte) (*ecdsa.PublicKey, error) {
|
||||||
p, _ := pem.Decode(pubkeyBytes)
|
p, _ := pem.Decode(pubkeyBytes)
|
||||||
if p == nil {
|
if p == nil {
|
||||||
return nil, fmt.Errorf("pubkey file does not contain any PEM data")
|
return nil, fmt.Errorf("pubkey file does not contain any PEM data")
|
||||||
@@ -29,7 +29,7 @@ func Parse(pubkeyBytes []byte) (*ecdsa.PublicKey, error) {
|
|||||||
return ecdsaPubKey, nil
|
return ecdsaPubKey, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ToPEM(ecdsaPubKey *ecdsa.PublicKey) ([]byte, error) {
|
func ConvertToPEM(ecdsaPubKey *ecdsa.PublicKey) ([]byte, error) {
|
||||||
pubKeyBytes, err := x509.MarshalPKIXPublicKey(ecdsaPubKey)
|
pubKeyBytes, err := x509.MarshalPKIXPublicKey(ecdsaPubKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error failed to marshal public key: %w", err)
|
return nil, fmt.Errorf("error failed to marshal public key: %w", err)
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
USE_MOCK_TL = true
|
UseMockTL = true
|
||||||
|
|
||||||
TestEntry = `{"body":"eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiI5Zjg2ZDA4MTg4NGM3ZDY1OWEyZmVhYTBjNTVhZDAxNWEzYmY0ZjFiMmIwYjgyMmNkMTVkNmMxNWIwZjAwYTA4In19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FUUNJQUlyVUZGUzBIYmNzZjc5L08yajVXdHl2R2Vvd1NVSXpZcDlBM2IwWnREVUFpQVQxZU42ZjFyVmVWa011REFlN3dxWkJ2bE5LY2VsajNVVDNmaWhyQjZSY2c9PSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVSlZla05DSzJGQlJFRm5SVU5CWjBWQ1RVRnZSME5EY1VkVFRUUTVRa0ZOUTAxQk9IaEVWRUZNUW1kT1ZrSkJUVlJDU0ZKc1l6TlJkMGhvWTA0S1RXcE5lRTFxU1ROTlZHdDVUWHBWTlZkb1kwNU5hbEY0VFdwSk1rMVVhM2xOZWxVMVYycEJVRTFSTUhkRGQxbEVWbEZSUkVWM1VqQmFXRTR3VFVacmR3cEZkMWxJUzI5YVNYcHFNRU5CVVZsSlMyOWFTWHBxTUVSQlVXTkVVV2RCUlVRMFZpdFNSV2g0SzJGeFYwZzNlV3hOVFVSSVlXaE9UVzVOVEZOUFNsQXZDamxyUVcwNWJIQXJNMjF4V1ZSQmFGVlNjbUUyVDBRMVVYZzRXbUprSzJWMVVIbFFhemw1SzNjdloxZEhSRUk1ZW00dlNXd3hTMDVIVFVWUmQwUm5XVVFLVmxJd1VFRlJTQzlDUVZGRVFXZGxRVTFDVFVkQk1WVmtTbEZSVFUxQmIwZERRM05IUVZGVlJrSjNUVVJOUVhkSFFURlZaRVYzUlVJdmQxRkRUVUZCZHdwRWQxbEVWbEl3VWtKQlozZENiMGxGWkVkV2VtUkVRVXRDWjJkeGFHdHFUMUJSVVVSQlowNUtRVVJDUjBGcFJVRTNOMjFFTDFSbVJtRlJVemxrWlhRMENqbFhaRk41YURKT1VTOUZiMVJtYVVGdFFtaHVWblpEVTNSUVowTkpVVU1yZDNSdllpOU9iMUp4T0c5cU4wZDNibTVKYUZKVGRDOVJNbmtyVXpoUkwzSUthRkpVYW5GaE9HZExRVDA5Q2kwdExTMHRSVTVFSUVORlVsUkpSa2xEUVZSRkxTMHRMUzBLIn19fX0=","integratedTime":1703705039,"logID":"c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d","logIndex":59674396,"verification":{"inclusionProof":{"checkpoint":"rekor.sigstore.dev - 2605736670972794746\n55510966\nJCi1O53Xmdi9lXnui4Q5SQ+MJSMnWr1Bxn+Q2Qf22tU=\nTimestamp: 1703705040158839214\n\n— rekor.sigstore.dev wNI9ajBFAiAXgtjFDVqCSgiSP04TQzELrz4+EyBwyYVL2EEULTCy0AIhAI9peLU76ZUD1tvU8qvzBJBo77IYD1rc+A1MPc35AeVK\n","hashes":["fb77ee213b48f4b18dc81c6e634c570abf99b257713561f174f2e0f4c039af67","6cb113bbefadecbbb8b89b1c08232438a6125071790b6a062cff8c1ccfdcb91e","6fbe1424e264e4590ca502d671b7a036c87f7a90d1f57534b98eb781144160bf","077b606720a6478200f6c3ed08a68e9b01b1cae192cb120888ddcc95521601bd","b6f8e8bc21ae0cde82b92422a4b4f37b28a43185821e468a4e65b6c79ed8f5b7","89332533fac54e9bc68c7353c42f6ebb9fe38039f67910332ff95082072068d4","0814d6f707a75fb3334bab14ab5466bd8b9a64ae7be7cd4d53a428c64932bc66","e883e826f10329c63a4a2ed21156037a050df43b9d74079296beac6968ed4150","d79230703257b7e4a8a61b032b6980d1a0bdbc7ae96ca838b525b3751785fe48","2f4a77e5288462cd3b75084d37f1502dcbe0943d18dd95cb247fc1ebbabc0aad","38562c253d3536d0d00e3547c880b6b0251a25ac69605b50c9eaa1a27186cc7a","9dea192350ff8b3c0f5ccda38261cb38ebd61869281c3928912332d1144e0a04","2c4d25ba59aa573ab2c79c2d3cd9e1d74789b10632432724d63112ce50b44874","98c486feb5d87092a78a46c4b5be04868654900affc2e86ffb20074dc73a883a","6969c49bd73f19bf28a5eaeabd331ddd60502defb2cd3d96e17b741c80adec6c"],"logIndex":55510965,"rootHash":"2428b53b9dd799d8bd9579ee8b8439490f8c2523275abd41c67f90d907f6dad5","treeSize":55510966},"signedEntryTimestamp":"MEUCIQCG9PRI8PcvtJyE9pbcculZipze6NEWR1Nk8EYocto3BwIgYu5gqgjW80HMjSjUxUNJLp0wlVTesnJCeByUBySc59w="}}`
|
TestEntry = `{"body":"eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiI5Zjg2ZDA4MTg4NGM3ZDY1OWEyZmVhYTBjNTVhZDAxNWEzYmY0ZjFiMmIwYjgyMmNkMTVkNmMxNWIwZjAwYTA4In19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FUUNJQUlyVUZGUzBIYmNzZjc5L08yajVXdHl2R2Vvd1NVSXpZcDlBM2IwWnREVUFpQVQxZU42ZjFyVmVWa011REFlN3dxWkJ2bE5LY2VsajNVVDNmaWhyQjZSY2c9PSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVSlZla05DSzJGQlJFRm5SVU5CWjBWQ1RVRnZSME5EY1VkVFRUUTVRa0ZOUTAxQk9IaEVWRUZNUW1kT1ZrSkJUVlJDU0ZKc1l6TlJkMGhvWTA0S1RXcE5lRTFxU1ROTlZHdDVUWHBWTlZkb1kwNU5hbEY0VFdwSk1rMVVhM2xOZWxVMVYycEJVRTFSTUhkRGQxbEVWbEZSUkVWM1VqQmFXRTR3VFVacmR3cEZkMWxJUzI5YVNYcHFNRU5CVVZsSlMyOWFTWHBxTUVSQlVXTkVVV2RCUlVRMFZpdFNSV2g0SzJGeFYwZzNlV3hOVFVSSVlXaE9UVzVOVEZOUFNsQXZDamxyUVcwNWJIQXJNMjF4V1ZSQmFGVlNjbUUyVDBRMVVYZzRXbUprSzJWMVVIbFFhemw1SzNjdloxZEhSRUk1ZW00dlNXd3hTMDVIVFVWUmQwUm5XVVFLVmxJd1VFRlJTQzlDUVZGRVFXZGxRVTFDVFVkQk1WVmtTbEZSVFUxQmIwZERRM05IUVZGVlJrSjNUVVJOUVhkSFFURlZaRVYzUlVJdmQxRkRUVUZCZHdwRWQxbEVWbEl3VWtKQlozZENiMGxGWkVkV2VtUkVRVXRDWjJkeGFHdHFUMUJSVVVSQlowNUtRVVJDUjBGcFJVRTNOMjFFTDFSbVJtRlJVemxrWlhRMENqbFhaRk41YURKT1VTOUZiMVJtYVVGdFFtaHVWblpEVTNSUVowTkpVVU1yZDNSdllpOU9iMUp4T0c5cU4wZDNibTVKYUZKVGRDOVJNbmtyVXpoUkwzSUthRkpVYW5GaE9HZExRVDA5Q2kwdExTMHRSVTVFSUVORlVsUkpSa2xEUVZSRkxTMHRMUzBLIn19fX0=","integratedTime":1703705039,"logID":"c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d","logIndex":59674396,"verification":{"inclusionProof":{"checkpoint":"rekor.sigstore.dev - 2605736670972794746\n55510966\nJCi1O53Xmdi9lXnui4Q5SQ+MJSMnWr1Bxn+Q2Qf22tU=\nTimestamp: 1703705040158839214\n\n— rekor.sigstore.dev wNI9ajBFAiAXgtjFDVqCSgiSP04TQzELrz4+EyBwyYVL2EEULTCy0AIhAI9peLU76ZUD1tvU8qvzBJBo77IYD1rc+A1MPc35AeVK\n","hashes":["fb77ee213b48f4b18dc81c6e634c570abf99b257713561f174f2e0f4c039af67","6cb113bbefadecbbb8b89b1c08232438a6125071790b6a062cff8c1ccfdcb91e","6fbe1424e264e4590ca502d671b7a036c87f7a90d1f57534b98eb781144160bf","077b606720a6478200f6c3ed08a68e9b01b1cae192cb120888ddcc95521601bd","b6f8e8bc21ae0cde82b92422a4b4f37b28a43185821e468a4e65b6c79ed8f5b7","89332533fac54e9bc68c7353c42f6ebb9fe38039f67910332ff95082072068d4","0814d6f707a75fb3334bab14ab5466bd8b9a64ae7be7cd4d53a428c64932bc66","e883e826f10329c63a4a2ed21156037a050df43b9d74079296beac6968ed4150","d79230703257b7e4a8a61b032b6980d1a0bdbc7ae96ca838b525b3751785fe48","2f4a77e5288462cd3b75084d37f1502dcbe0943d18dd95cb247fc1ebbabc0aad","38562c253d3536d0d00e3547c880b6b0251a25ac69605b50c9eaa1a27186cc7a","9dea192350ff8b3c0f5ccda38261cb38ebd61869281c3928912332d1144e0a04","2c4d25ba59aa573ab2c79c2d3cd9e1d74789b10632432724d63112ce50b44874","98c486feb5d87092a78a46c4b5be04868654900affc2e86ffb20074dc73a883a","6969c49bd73f19bf28a5eaeabd331ddd60502defb2cd3d96e17b741c80adec6c"],"logIndex":55510965,"rootHash":"2428b53b9dd799d8bd9579ee8b8439490f8c2523275abd41c67f90d907f6dad5","treeSize":55510966},"signedEntryTimestamp":"MEUCIQCG9PRI8PcvtJyE9pbcculZipze6NEWR1Nk8EYocto3BwIgYu5gqgjW80HMjSjUxUNJLp0wlVTesnJCeByUBySc59w="}}`
|
||||||
)
|
)
|
||||||
@@ -26,10 +26,10 @@ func GetMockTL() TL {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return &MockTL{
|
return &MockTL{
|
||||||
UploadLogEntryFunc: func(ctx context.Context, subject string, payload []byte, signature []byte, signer dsse.SignerVerifier) ([]byte, error) {
|
UploadLogEntryFunc: func(_ context.Context, _ string, _ []byte, _ []byte, _ dsse.SignerVerifier) ([]byte, error) {
|
||||||
return []byte(TestEntry), nil
|
return []byte(TestEntry), nil
|
||||||
},
|
},
|
||||||
VerifyLogEntryFunc: func(ctx context.Context, entryBytes []byte) (time.Time, error) {
|
VerifyLogEntryFunc: func(_ context.Context, entryBytes []byte) (time.Time, error) {
|
||||||
// return the integrated time in the log entry without any checking
|
// return the integrated time in the log entry without any checking
|
||||||
le, err := unmarshalEntry(entryBytes)
|
le, err := unmarshalEntry(entryBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -40,7 +40,7 @@ func GetMockTL() TL {
|
|||||||
}
|
}
|
||||||
return time.Unix(*le.IntegratedTime, 0), nil
|
return time.Unix(*le.IntegratedTime, 0), nil
|
||||||
},
|
},
|
||||||
VerifyEntryPayloadFunc: func(entryBytes, payload, pkToken []byte) error {
|
VerifyEntryPayloadFunc: func(_, _, _ []byte) error {
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
UnmarshalEntryFunc: func(entry []byte) (any, error) {
|
UnmarshalEntryFunc: func(entry []byte) (any, error) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package tlog
|
package tlog
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
@@ -31,23 +32,23 @@ const (
|
|||||||
|
|
||||||
type tlCtxKeyType struct{}
|
type tlCtxKeyType struct{}
|
||||||
|
|
||||||
var TlCtxKey tlCtxKeyType
|
var TLCtxKey tlCtxKeyType
|
||||||
|
|
||||||
// sets TL in context
|
// sets TL in context.
|
||||||
func WithTL(ctx context.Context, tl TL) context.Context {
|
func WithTL(ctx context.Context, tl TL) context.Context {
|
||||||
return context.WithValue(ctx, TlCtxKey, tl)
|
return context.WithValue(ctx, TLCtxKey, tl)
|
||||||
}
|
}
|
||||||
|
|
||||||
// gets TL from context, defaults to Rekor TL if not set
|
// gets TL from context, defaults to Rekor TL if not set.
|
||||||
func GetTL(ctx context.Context) TL {
|
func GetTL(ctx context.Context) TL {
|
||||||
t, ok := ctx.Value(TlCtxKey).(TL)
|
t, ok := ctx.Value(TLCtxKey).(TL)
|
||||||
if !ok {
|
if !ok {
|
||||||
t = &RekorTL{}
|
t = &RekorTL{}
|
||||||
}
|
}
|
||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
type TlPayload struct {
|
type TLPayload struct {
|
||||||
Algorithm string
|
Algorithm string
|
||||||
Hash string
|
Hash string
|
||||||
Signature string
|
Signature string
|
||||||
@@ -98,7 +99,7 @@ func (tl *MockTL) UnmarshalEntry(entryBytes []byte) (any, error) {
|
|||||||
|
|
||||||
type RekorTL struct{}
|
type RekorTL struct{}
|
||||||
|
|
||||||
// UploadLogEntry submits a PK token signature to the transparency log
|
// UploadLogEntry submits a PK token signature to the transparency log.
|
||||||
func (tl *RekorTL) UploadLogEntry(ctx context.Context, subject string, payload, signature []byte, signer dsse.SignerVerifier) ([]byte, error) {
|
func (tl *RekorTL) UploadLogEntry(ctx context.Context, subject string, payload, signature []byte, signer dsse.SignerVerifier) ([]byte, error) {
|
||||||
// generate self-signed x509 cert
|
// generate self-signed x509 cert
|
||||||
pubCert, err := CreateX509Cert(subject, signer)
|
pubCert, err := CreateX509Cert(subject, signer)
|
||||||
@@ -121,12 +122,12 @@ func (tl *RekorTL) UploadLogEntry(ctx context.Context, subject string, payload,
|
|||||||
}
|
}
|
||||||
entryBytes, err := entry.MarshalBinary()
|
entryBytes, err := entry.MarshalBinary()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error marshalling TL entry: %w", err)
|
return nil, fmt.Errorf("error marshaling TL entry: %w", err)
|
||||||
}
|
}
|
||||||
return entryBytes, nil
|
return entryBytes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// VerifyLogEntry verifies a transparency log entry
|
// VerifyLogEntry verifies a transparency log entry.
|
||||||
func (tl *RekorTL) VerifyLogEntry(ctx context.Context, entryBytes []byte) (time.Time, error) {
|
func (tl *RekorTL) VerifyLogEntry(ctx context.Context, entryBytes []byte) (time.Time, error) {
|
||||||
zeroTime := time.Time{}
|
zeroTime := time.Time{}
|
||||||
entry, err := tl.UnmarshalEntry(entryBytes)
|
entry, err := tl.UnmarshalEntry(entryBytes)
|
||||||
@@ -157,12 +158,12 @@ func (tl *RekorTL) VerifyLogEntry(ctx context.Context, entryBytes []byte) (time.
|
|||||||
return integratedTime, nil
|
return integratedTime, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateX509Cert generates a self-signed x509 cert for TL submission
|
// CreateX509Cert generates a self-signed x509 cert for TL submission.
|
||||||
func CreateX509Cert(subject string, signer dsse.SignerVerifier) ([]byte, error) {
|
func CreateX509Cert(subject string, signer dsse.SignerVerifier) ([]byte, error) {
|
||||||
// encode ephemeral public key
|
// encode ephemeral public key
|
||||||
ecPub, err := x509.MarshalPKIXPublicKey(signer.Public())
|
ecPub, err := x509.MarshalPKIXPublicKey(signer.Public())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error marshalling public key: %w", err)
|
return nil, fmt.Errorf("error marshaling public key: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
template := x509.Certificate{
|
template := x509.Certificate{
|
||||||
@@ -180,7 +181,7 @@ func CreateX509Cert(subject string, signer dsse.SignerVerifier) ([]byte, error)
|
|||||||
|
|
||||||
// dsse.SignerVerifier doesn't implement cypto.Signer exactly
|
// dsse.SignerVerifier doesn't implement cypto.Signer exactly
|
||||||
|
|
||||||
csigner, ok := signer.(*signerverifier.ECDSA256_SignerVerifier)
|
csigner, ok := signer.(*signerverifier.ECDSA256SignerVerifier)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("expected signer to be of type *signerverifier.ECDSA_SignerVerifier, got %T", signer)
|
return nil, fmt.Errorf("expected signer to be of type *signerverifier.ECDSA_SignerVerifier, got %T", signer)
|
||||||
}
|
}
|
||||||
@@ -193,7 +194,7 @@ func CreateX509Cert(subject string, signer dsse.SignerVerifier) ([]byte, error)
|
|||||||
return pem.EncodeToMemory(certBlock), nil
|
return pem.EncodeToMemory(certBlock), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// VerifyEntryPayload checks that the TL entry payload matches envelope payload
|
// VerifyEntryPayload checks that the TL entry payload matches envelope payload.
|
||||||
func (tl *RekorTL) VerifyEntryPayload(entryBytes, payload, publicKey []byte) error {
|
func (tl *RekorTL) VerifyEntryPayload(entryBytes, payload, publicKey []byte) error {
|
||||||
entry, err := tl.UnmarshalEntry(entryBytes)
|
entry, err := tl.UnmarshalEntry(entryBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -228,7 +229,7 @@ func (tl *RekorTL) VerifyEntryPayload(entryBytes, payload, publicKey []byte) err
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to parse certificate: %w", err)
|
return fmt.Errorf("failed to parse certificate: %w", err)
|
||||||
}
|
}
|
||||||
if string(result.RawSubjectPublicKeyInfo) != string(publicKey) {
|
if !bytes.Equal(result.RawSubjectPublicKeyInfo, publicKey) {
|
||||||
return fmt.Errorf("error payload and tl entry public key mismatch")
|
return fmt.Errorf("error payload and tl entry public key mismatch")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -243,9 +244,9 @@ func (tl *RekorTL) UnmarshalEntry(entry []byte) (any, error) {
|
|||||||
return le, nil
|
return le, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func extractHashedRekord(Body string) (*TlPayload, error) {
|
func extractHashedRekord(body string) (*TLPayload, error) {
|
||||||
sig := new(TlPayload)
|
sig := new(TLPayload)
|
||||||
pe, err := models.UnmarshalProposedEntry(base64.NewDecoder(base64.StdEncoding, strings.NewReader(Body)), runtime.JSONConsumer())
|
pe, err := models.UnmarshalProposedEntry(base64.NewDecoder(base64.StdEncoding, strings.NewReader(body)), runtime.JSONConsumer())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// test artifacts
|
// test artifacts.
|
||||||
TestPayload = "test"
|
TestPayload = "test"
|
||||||
TestPublicKey = "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAED4V+REhx+aqWH7ylMMDHahNMnMLS\nOJP/9kAm9lp+3mqYTAhURra6OD5Qx8Zbd+euPyPk9y+w/gWGDB9zn/Il1A==\n-----END PUBLIC KEY-----"
|
TestPublicKey = "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAED4V+REhx+aqWH7ylMMDHahNMnMLS\nOJP/9kAm9lp+3mqYTAhURra6OD5Qx8Zbd+euPyPk9y+w/gWGDB9zn/Il1A==\n-----END PUBLIC KEY-----"
|
||||||
)
|
)
|
||||||
@@ -53,15 +53,15 @@ func TestUploadAndVerifyLogEntry(t *testing.T) {
|
|||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
var tl TL
|
var tl TL
|
||||||
if USE_MOCK_TL {
|
if UseMockTL {
|
||||||
tl = &MockTL{
|
tl = &MockTL{
|
||||||
UploadLogEntryFunc: func(ctx context.Context, subject string, payload []byte, signature []byte, signer dsse.SignerVerifier) ([]byte, error) {
|
UploadLogEntryFunc: func(_ context.Context, _ string, _ []byte, _ []byte, _ dsse.SignerVerifier) ([]byte, error) {
|
||||||
return []byte(TestEntry), nil
|
return []byte(TestEntry), nil
|
||||||
},
|
},
|
||||||
VerifyLogEntryFunc: func(ctx context.Context, entryBytes []byte) (time.Time, error) {
|
VerifyLogEntryFunc: func(_ context.Context, _ []byte) (time.Time, error) {
|
||||||
return time.Time{}, nil
|
return time.Time{}, nil
|
||||||
},
|
},
|
||||||
VerifyEntryPayloadFunc: func(entryBytes, payload, publicKey []byte) error {
|
VerifyEntryPayloadFunc: func(_, _, _ []byte) error {
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
"github.com/theupdateframework/go-tuf/v2/metadata"
|
"github.com/theupdateframework/go-tuf/v2/metadata"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ExampleNewTufClient_registry() {
|
func ExampleNewClient_registry() {
|
||||||
// create a tuf client
|
// create a tuf client
|
||||||
home, err := os.UserHomeDir()
|
home, err := os.UserHomeDir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -21,7 +21,7 @@ func ExampleNewTufClient_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.NewTufClient(embed.RootStaging.Data, tufOutputPath, metadataURI, targetsURI, tuf.NewMockVersionChecker())
|
registryClient, err := tuf.NewClient(embed.RootStaging.Data, tufOutputPath, metadataURI, targetsURI, tuf.NewMockVersionChecker())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,25 +6,25 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
)
|
)
|
||||||
|
|
||||||
type mockTufClient struct {
|
type MockTufClient struct {
|
||||||
srcPath string
|
srcPath string
|
||||||
dstPath string
|
dstPath string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMockTufClient(srcPath string, dstPath string) *mockTufClient {
|
func NewMockTufClient(srcPath string, dstPath string) *MockTufClient {
|
||||||
if srcPath == "" {
|
if srcPath == "" {
|
||||||
panic("srcPath must be set")
|
panic("srcPath must be set")
|
||||||
}
|
}
|
||||||
if dstPath == "" {
|
if dstPath == "" {
|
||||||
panic("dstPath must be set")
|
panic("dstPath must be set")
|
||||||
}
|
}
|
||||||
return &mockTufClient{
|
return &MockTufClient{
|
||||||
srcPath: srcPath,
|
srcPath: srcPath,
|
||||||
dstPath: dstPath,
|
dstPath: dstPath,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dc *mockTufClient) DownloadTarget(target string, filePath string) (actualFilePath string, data []byte, err error) {
|
func (dc *MockTufClient) DownloadTarget(target string, filePath string) (actualFilePath string, data []byte, err error) {
|
||||||
src, err := os.Open(filepath.Join(dc.srcPath, target))
|
src, err := os.Open(filepath.Join(dc.srcPath, target))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, err
|
return "", nil, err
|
||||||
@@ -38,7 +38,7 @@ func (dc *mockTufClient) DownloadTarget(target string, filePath string) (actualF
|
|||||||
dstFilePath = filePath
|
dstFilePath = filePath
|
||||||
}
|
}
|
||||||
|
|
||||||
err = os.MkdirAll(filepath.Dir(dstFilePath), 0755)
|
err = os.MkdirAll(filepath.Dir(dstFilePath), os.ModePerm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, err
|
return "", nil, err
|
||||||
}
|
}
|
||||||
@@ -59,14 +59,14 @@ func (dc *mockTufClient) DownloadTarget(target string, filePath string) (actualF
|
|||||||
return dstFilePath, b, nil
|
return dstFilePath, b, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type mockVersionChecker struct {
|
type MockVersionChecker struct {
|
||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMockVersionChecker() *mockVersionChecker {
|
func NewMockVersionChecker() *MockVersionChecker {
|
||||||
return &mockVersionChecker{}
|
return &MockVersionChecker{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (vc *mockVersionChecker) CheckVersion(client TUFClient) error {
|
func (vc *MockVersionChecker) CheckVersion(_ Downloader) error {
|
||||||
return vc.err
|
return vc.err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,14 +19,14 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
TufFileNameAnnotation = "tuf.io/filename"
|
TUFFileNameAnnotation = "tuf.io/filename"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TufRole string
|
type Role string
|
||||||
|
|
||||||
var TufRoles = []TufRole{metadata.ROOT, metadata.SNAPSHOT, metadata.TARGETS, metadata.TIMESTAMP}
|
var Roles = []Role{metadata.ROOT, metadata.SNAPSHOT, metadata.TARGETS, metadata.TIMESTAMP}
|
||||||
|
|
||||||
// RegistryFetcher implements Fetcher
|
// RegistryFetcher implements Fetcher.
|
||||||
type RegistryFetcher struct {
|
type RegistryFetcher struct {
|
||||||
httpUserAgent string
|
httpUserAgent string
|
||||||
metadataRepo string
|
metadataRepo string
|
||||||
@@ -46,13 +46,13 @@ func NewImageCache() *ImageCache {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get image from cache
|
// Get image from cache.
|
||||||
func (c *ImageCache) Get(imgRef string) ([]byte, bool) {
|
func (c *ImageCache) Get(imgRef string) ([]byte, bool) {
|
||||||
img, found := c.cache[imgRef]
|
img, found := c.cache[imgRef]
|
||||||
return img, found
|
return img, found
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add image to cache
|
// Add image to cache.
|
||||||
func (c *ImageCache) Put(imgRef string, img []byte) {
|
func (c *ImageCache) Put(imgRef string, img []byte) {
|
||||||
c.cache[imgRef] = img
|
c.cache[imgRef] = img
|
||||||
}
|
}
|
||||||
@@ -113,7 +113,7 @@ func (d *RegistryFetcher) DownloadFile(urlPath string, maxLength int64, timeout
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// getManifest returns the manifest for an image or index
|
// getManifest returns the manifest for an image or index.
|
||||||
func (d *RegistryFetcher) getManifest(ref string) ([]byte, error) {
|
func (d *RegistryFetcher) getManifest(ref string) ([]byte, error) {
|
||||||
// Pull image manifest
|
// Pull image manifest
|
||||||
var err error
|
var err error
|
||||||
@@ -135,7 +135,7 @@ func (d *RegistryFetcher) getManifest(ref string) ([]byte, error) {
|
|||||||
return mf, nil
|
return mf, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// pullFileLayer pulls a layer for an image or index and returns its data
|
// pullFileLayer pulls a layer for an image or index and returns its data.
|
||||||
func (d *RegistryFetcher) pullFileLayer(ref string, maxLength int64) ([]byte, error) {
|
func (d *RegistryFetcher) pullFileLayer(ref string, maxLength int64) ([]byte, error) {
|
||||||
var data []byte
|
var data []byte
|
||||||
var found bool
|
var found bool
|
||||||
@@ -159,7 +159,7 @@ func (d *RegistryFetcher) pullFileLayer(ref string, maxLength int64) ([]byte, er
|
|||||||
return data, nil
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getDataFromLayer returns the data from a layer in an image
|
// getDataFromLayer returns the data from a layer in an image.
|
||||||
func getDataFromLayer(fileLayer v1.Layer, maxLength int64) ([]byte, error) {
|
func getDataFromLayer(fileLayer v1.Layer, maxLength int64) ([]byte, error) {
|
||||||
length, err := fileLayer.Size()
|
length, err := fileLayer.Size()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -185,7 +185,7 @@ func getDataFromLayer(fileLayer v1.Layer, maxLength int64) ([]byte, error) {
|
|||||||
return data, nil
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseImgRef maintains the Fetcher interface by parsing a URL path to an image reference and file name
|
// parseImgRef maintains the Fetcher interface by parsing a URL path to an image reference and file name.
|
||||||
func (d *RegistryFetcher) parseImgRef(urlPath string) (imgRef, fileName string, err error) {
|
func (d *RegistryFetcher) parseImgRef(urlPath string) (imgRef, fileName string, err error) {
|
||||||
// Check if repo is target or metadata
|
// Check if repo is target or metadata
|
||||||
if strings.Contains(urlPath, d.targetsRepo) {
|
if strings.Contains(urlPath, d.targetsRepo) {
|
||||||
@@ -208,12 +208,11 @@ func (d *RegistryFetcher) parseImgRef(urlPath string) (imgRef, fileName string,
|
|||||||
return fmt.Sprintf("%s:%s", d.metadataRepo, role), fileName, nil
|
return fmt.Sprintf("%s:%s", d.metadataRepo, role), fileName, nil
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("%s:%s", d.metadataRepo, d.metadataTag), fileName, nil
|
return fmt.Sprintf("%s:%s", d.metadataRepo, d.metadataTag), fileName, nil
|
||||||
} else {
|
|
||||||
return "", "", fmt.Errorf("urlPath: %s must be in metadata or targets repo", urlPath)
|
|
||||||
}
|
}
|
||||||
|
return "", "", fmt.Errorf("urlPath: %s must be in metadata or targets repo", urlPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
// findFileInManifest searches the image or index manifest for a file with the given name and returns its digest
|
// findFileInManifest searches the image or index manifest for a file with the given name and returns its digest.
|
||||||
func (d *RegistryFetcher) findFileInManifest(mf []byte, name string) (*v1.Hash, error) {
|
func (d *RegistryFetcher) findFileInManifest(mf []byte, name string) (*v1.Hash, error) {
|
||||||
var index bool
|
var index bool
|
||||||
|
|
||||||
@@ -226,20 +225,21 @@ func (d *RegistryFetcher) findFileInManifest(mf []byte, name string) (*v1.Hash,
|
|||||||
|
|
||||||
// determine image or index manifest
|
// determine image or index manifest
|
||||||
var layers []Layer
|
var layers []Layer
|
||||||
if l.MediaType == string(types.OCIImageIndex) {
|
switch l.MediaType {
|
||||||
|
case string(types.OCIImageIndex):
|
||||||
layers = l.Manifests
|
layers = l.Manifests
|
||||||
index = true
|
index = true
|
||||||
} else if l.MediaType == string(types.OCIManifestSchema1) {
|
case string(types.OCIManifestSchema1):
|
||||||
layers = l.Layers
|
layers = l.Layers
|
||||||
index = false
|
index = false
|
||||||
} else {
|
default:
|
||||||
return nil, fmt.Errorf("invalid manifest media type: %s", l.MediaType)
|
return nil, fmt.Errorf("invalid manifest media type: %s", l.MediaType)
|
||||||
}
|
}
|
||||||
|
|
||||||
// find annotation with file name
|
// find annotation with file name
|
||||||
var digest string
|
var digest string
|
||||||
for _, layer := range layers {
|
for _, layer := range layers {
|
||||||
if layer.Annotations[TufFileNameAnnotation] == name {
|
if layer.Annotations[TUFFileNameAnnotation] == name {
|
||||||
digest = layer.Digest
|
digest = layer.Digest
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -267,7 +267,7 @@ func (d *RegistryFetcher) findFileInManifest(mf []byte, name string) (*v1.Hash,
|
|||||||
return hash, nil
|
return hash, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// transportWithTimeout returns a http.RoundTripper with a specified timeout
|
// transportWithTimeout returns a http.RoundTripper with a specified timeout.
|
||||||
func transportWithTimeout(timeout time.Duration) http.RoundTripper {
|
func transportWithTimeout(timeout time.Duration) http.RoundTripper {
|
||||||
// transport is based on go-containerregistry remote.DefaultTransport
|
// transport is based on go-containerregistry remote.DefaultTransport
|
||||||
// with modifications to include a specified timeout
|
// with modifications to include a specified timeout
|
||||||
@@ -286,9 +286,9 @@ func transportWithTimeout(timeout time.Duration) http.RoundTripper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// isDelegatedRole returns true if the role is a delegated role
|
// isDelegatedRole returns true if the role is a delegated role.
|
||||||
func isDelegatedRole(role string) bool {
|
func isDelegatedRole(role string) bool {
|
||||||
for _, r := range TufRoles {
|
for _, r := range Roles {
|
||||||
if role == string(r) {
|
if role == string(r) {
|
||||||
return false // role is not a delegated role
|
return false // role is not a delegated role
|
||||||
}
|
}
|
||||||
@@ -296,7 +296,7 @@ func isDelegatedRole(role string) bool {
|
|||||||
return true // role is a delegated role
|
return true // role is a delegated role
|
||||||
}
|
}
|
||||||
|
|
||||||
// roleFromConsistentName returns the role name from a consistent snapshot file name
|
// roleFromConsistentName returns the role name from a consistent snapshot file name.
|
||||||
func roleFromConsistentName(filename string) string {
|
func roleFromConsistentName(filename string) string {
|
||||||
name := strings.TrimSuffix(filename, ".json")
|
name := strings.TrimSuffix(filename, ".json")
|
||||||
role := strings.Split(name, ".")
|
role := strings.Split(name, ".")
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
tufTargetMediaType = "application/vnd.tuf.target"
|
tufTargetMediaType = "application/vnd.tuf.target"
|
||||||
|
testRole = "test-role"
|
||||||
|
tufMetadataRepo = "tuf-metadata"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRegistryFetcher(t *testing.T) {
|
func TestRegistryFetcher(t *testing.T) {
|
||||||
@@ -40,13 +42,13 @@ func TestRegistryFetcher(t *testing.T) {
|
|||||||
t.Fatalf("failed to terminate container: %s", err) // nolint:gocritic
|
t.Fatalf("failed to terminate container: %s", err) // nolint:gocritic
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
LoadRegistryTestData(t, regAddr, OciTufTestDataPath)
|
LoadRegistryTestData(t, regAddr, OCITUFTestDataPath)
|
||||||
|
|
||||||
metadataRepo := regAddr.Host + "/tuf-metadata"
|
metadataRepo := regAddr.Host + "/tuf-metadata"
|
||||||
metadataImgTag := "latest"
|
metadataImgTag := LatestTag
|
||||||
targetsRepo := regAddr.Host + "/tuf-targets"
|
targetsRepo := regAddr.Host + "/tuf-targets"
|
||||||
targetFile := "test.txt"
|
targetFile := "test.txt"
|
||||||
delegatedRole := "test-role"
|
delegatedRole := testRole
|
||||||
dir := CreateTempDir(t, "", "tuf_temp")
|
dir := CreateTempDir(t, "", "tuf_temp")
|
||||||
delegatedDir := CreateTempDir(t, dir, delegatedRole)
|
delegatedDir := CreateTempDir(t, dir, delegatedRole)
|
||||||
delegatedTargetFile := fmt.Sprintf("%s/%s", delegatedRole, targetFile)
|
delegatedTargetFile := fmt.Sprintf("%s/%s", delegatedRole, targetFile)
|
||||||
@@ -126,11 +128,11 @@ func TestFindFileInManifest(t *testing.T) {
|
|||||||
img = mutate.ConfigMediaType(img, types.OCIConfigJSON)
|
img = mutate.ConfigMediaType(img, types.OCIConfigJSON)
|
||||||
// add test layer
|
// add test layer
|
||||||
name := strings.Join([]string{hash.Hex, file}, ".")
|
name := strings.Join([]string{hash.Hex, file}, ".")
|
||||||
ann := map[string]string{TufFileNameAnnotation: name}
|
ann := map[string]string{TUFFileNameAnnotation: name}
|
||||||
layer := mutate.Addendum{Layer: static.NewLayer(data, tufTargetMediaType), Annotations: ann}
|
layer := mutate.Addendum{Layer: static.NewLayer(data, tufTargetMediaType), Annotations: ann}
|
||||||
img, err := mutate.Append(img, layer)
|
img, err := mutate.Append(img, layer)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
image_manifest, err := img.RawManifest()
|
imageManifest, err := img.RawManifest()
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
// make test index manifest
|
// make test index manifest
|
||||||
@@ -141,11 +143,11 @@ func TestFindFileInManifest(t *testing.T) {
|
|||||||
Add: img,
|
Add: img,
|
||||||
Descriptor: v1.Descriptor{
|
Descriptor: v1.Descriptor{
|
||||||
Annotations: map[string]string{
|
Annotations: map[string]string{
|
||||||
TufFileNameAnnotation: name,
|
TUFFileNameAnnotation: name,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
index_manifest, err := idx.RawManifest()
|
indexManifest, err := idx.RawManifest()
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
// cache image layer
|
// cache image layer
|
||||||
targetsRepo := "test/tuf-targets"
|
targetsRepo := "test/tuf-targets"
|
||||||
@@ -155,7 +157,7 @@ func TestFindFileInManifest(t *testing.T) {
|
|||||||
}
|
}
|
||||||
imgHash, err := img.Digest()
|
imgHash, err := img.Digest()
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
d.cache.Put(fmt.Sprintf("%s@%s", targetsRepo, imgHash.String()), image_manifest)
|
d.cache.Put(fmt.Sprintf("%s@%s", targetsRepo, imgHash.String()), imageManifest)
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
@@ -163,9 +165,9 @@ func TestFindFileInManifest(t *testing.T) {
|
|||||||
file string
|
file string
|
||||||
expected string
|
expected string
|
||||||
}{
|
}{
|
||||||
{"consistent filename image", image_manifest, fmt.Sprintf("%s.%s", hash.Hex, file), hash.Hex},
|
{"consistent filename image", imageManifest, fmt.Sprintf("%s.%s", hash.Hex, file), hash.Hex},
|
||||||
{"filename image", image_manifest, file, ""},
|
{"filename image", imageManifest, file, ""},
|
||||||
{"consistent filename index", index_manifest, fmt.Sprintf("%s.%s", hash.Hex, file), hash.Hex},
|
{"consistent filename index", indexManifest, fmt.Sprintf("%s.%s", hash.Hex, file), hash.Hex},
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
@@ -182,9 +184,9 @@ func TestFindFileInManifest(t *testing.T) {
|
|||||||
|
|
||||||
func TestParseImgRef(t *testing.T) {
|
func TestParseImgRef(t *testing.T) {
|
||||||
metadataRepo := "test/tuf-metadata"
|
metadataRepo := "test/tuf-metadata"
|
||||||
metadataTag := "latest"
|
metadataTag := LatestTag
|
||||||
targetsRepo := "test/tuf-targets"
|
targetsRepo := "test/tuf-targets"
|
||||||
delegatedRole := "test-role"
|
delegatedRole := testRole
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
ref string
|
ref string
|
||||||
@@ -200,7 +202,7 @@ func TestParseImgRef(t *testing.T) {
|
|||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
d := &RegistryFetcher{
|
d := &RegistryFetcher{
|
||||||
metadataRepo: metadataRepo,
|
metadataRepo: metadataRepo,
|
||||||
metadataTag: "latest",
|
metadataTag: LatestTag,
|
||||||
targetsRepo: targetsRepo,
|
targetsRepo: targetsRepo,
|
||||||
}
|
}
|
||||||
imgRef, file, err := d.parseImgRef(tc.ref)
|
imgRef, file, err := d.parseImgRef(tc.ref)
|
||||||
@@ -246,7 +248,7 @@ func TestPullFileLayer(t *testing.T) {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
// make test layer
|
// make test layer
|
||||||
repo := "tuf-metadata"
|
repo := tufMetadataRepo
|
||||||
data := []byte("test")
|
data := []byte("test")
|
||||||
testLayer := static.NewLayer(data, tufTargetMediaType)
|
testLayer := static.NewLayer(data, tufTargetMediaType)
|
||||||
hash, err := testLayer.Digest()
|
hash, err := testLayer.Digest()
|
||||||
@@ -303,7 +305,7 @@ func TestGetManifest(t *testing.T) {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
// make test manifest
|
// make test manifest
|
||||||
repo := "tuf-metadata"
|
repo := tufMetadataRepo
|
||||||
img := empty.Image
|
img := empty.Image
|
||||||
img = mutate.MediaType(img, types.OCIManifestSchema1)
|
img = mutate.MediaType(img, types.OCIManifestSchema1)
|
||||||
img = mutate.ConfigMediaType(img, types.OCIConfigJSON)
|
img = mutate.ConfigMediaType(img, types.OCIConfigJSON)
|
||||||
@@ -339,7 +341,7 @@ func TestGetManifest(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// RunTestRegistry starts a registry testcontainer for TUF on OCI testdata
|
// RunTestRegistry starts a registry testcontainer for TUF on OCI testdata.
|
||||||
func RunTestRegistry(t *testing.T) (*registry.RegistryContainer, *url.URL) {
|
func RunTestRegistry(t *testing.T) (*registry.RegistryContainer, *url.URL) {
|
||||||
registryContainer, err := registry.Run(context.Background(), "registry:2.8.3")
|
registryContainer, err := registry.Run(context.Background(), "registry:2.8.3")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -359,22 +361,21 @@ func RunTestRegistry(t *testing.T) (*registry.RegistryContainer, *url.URL) {
|
|||||||
return registryContainer, addr
|
return registryContainer, addr
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadRegistryTestData pushes TUF metadata and targets to an OCI registry
|
// LoadRegistryTestData pushes TUF metadata and targets to an OCI registry.
|
||||||
func LoadRegistryTestData(t *testing.T, registry *url.URL, path string) {
|
func LoadRegistryTestData(t *testing.T, registry *url.URL, path string) {
|
||||||
// push tuf metadata and targets to local registry
|
// push tuf metadata and targets to local registry
|
||||||
METADATA_REPO := "tuf-metadata"
|
MetadataRepo := tufMetadataRepo
|
||||||
METADATA_TAG := "latest"
|
TargetsRepo := "tuf-targets"
|
||||||
TARGETS_REPO := "tuf-targets"
|
DelegatedRole := testRole
|
||||||
DELEGATED_ROLE := "test-role"
|
|
||||||
|
|
||||||
// push top-level metadata -> metadata:latest
|
// push top-level metadata -> metadata:latest
|
||||||
err := LoadMetadata(filepath.Join(path, "metadata"), registry.Host, METADATA_REPO, METADATA_TAG)
|
err := LoadMetadata(filepath.Join(path, "metadata"), registry.Host, MetadataRepo, LatestTag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// push delegated metadata -> metadata:<DELEGATED_ROLE>
|
// push delegated metadata -> metadata:<DELEGATED_ROLE>
|
||||||
err = LoadMetadata(filepath.Join(path, "metadata", DELEGATED_ROLE), registry.Host, METADATA_REPO, DELEGATED_ROLE)
|
err = LoadMetadata(filepath.Join(path, "metadata", DelegatedRole), registry.Host, MetadataRepo, DelegatedRole)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -392,7 +393,7 @@ func LoadRegistryTestData(t *testing.T, registry *url.URL, path string) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
ref, err := name.ParseReference(fmt.Sprintf("%s/%s:%s", registry.Host, TARGETS_REPO, dir.Name()))
|
ref, err := name.ParseReference(fmt.Sprintf("%s/%s:%s", registry.Host, TargetsRepo, dir.Name()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -400,7 +401,8 @@ func LoadRegistryTestData(t *testing.T, registry *url.URL, path string) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if len(mf.Manifests) == 1 {
|
switch len(mf.Manifests) {
|
||||||
|
case 1:
|
||||||
// top-level target
|
// top-level target
|
||||||
img, err := tIdx.Image(mf.Manifests[0].Digest)
|
img, err := tIdx.Image(mf.Manifests[0].Digest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -410,19 +412,19 @@ func LoadRegistryTestData(t *testing.T, registry *url.URL, path string) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
} else if len(mf.Manifests) > 1 {
|
case 2:
|
||||||
// delegated target
|
// delegated target
|
||||||
err = remote.WriteIndex(ref, tIdx, oci.MultiKeychainOption())
|
err = remote.WriteIndex(ref, tIdx, oci.MultiKeychainOption())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
} else {
|
default:
|
||||||
t.Fatal("no manifests found")
|
t.Fatal("no manifests found")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadMetadata loads TUF metadata from a local path and pushes to a registry
|
// LoadMetadata loads TUF metadata from a local path and pushes to a registry.
|
||||||
func LoadMetadata(path, host, repo, tag string) error {
|
func LoadMetadata(path, host, repo, tag string) error {
|
||||||
mIdx, err := layout.ImageIndexFromPath(path)
|
mIdx, err := layout.ImageIndexFromPath(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -20,56 +20,56 @@ import (
|
|||||||
"github.com/theupdateframework/go-tuf/v2/metadata/updater"
|
"github.com/theupdateframework/go-tuf/v2/metadata/updater"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TufSource string
|
type Source string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
HttpSource TufSource = "http"
|
HTTPSource Source = "http"
|
||||||
OciSource TufSource = "oci"
|
OCISource Source = "oci"
|
||||||
|
LatestTag string = "latest"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
DockerTufRootProd = embed.RootProd
|
DockerTUFRootProd = embed.RootProd
|
||||||
DockerTufRootStaging = embed.RootStaging
|
DockerTUFRootStaging = embed.RootStaging
|
||||||
DockerTufRootDev = embed.RootDev
|
DockerTUFRootDev = embed.RootDev
|
||||||
DockerTufRootDefault = embed.RootDefault
|
DockerTUFRootDefault = embed.RootDefault
|
||||||
)
|
)
|
||||||
|
|
||||||
type TUFClient interface {
|
type Downloader interface {
|
||||||
DownloadTarget(target, filePath string) (actualFilePath string, data []byte, err error)
|
DownloadTarget(target, filePath string) (actualFilePath string, data []byte, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type TufClient struct {
|
type Client struct {
|
||||||
updater *updater.Updater
|
updater *updater.Updater
|
||||||
cfg *config.UpdaterConfig
|
cfg *config.UpdaterConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTufClient creates a new TUF client
|
// NewClient creates a new TUF client.
|
||||||
func NewTufClient(initialRoot []byte, tufPath, metadataSource, targetsSource string, versionChecker VersionChecker) (*TufClient, error) {
|
func NewClient(initialRoot []byte, tufPath, metadataSource, targetsSource string, versionChecker VersionChecker) (*Client, error) {
|
||||||
var tufSource TufSource
|
var tufSource Source
|
||||||
if strings.HasPrefix(metadataSource, "https://") || strings.HasPrefix(metadataSource, "http://") {
|
if strings.HasPrefix(metadataSource, "https://") || strings.HasPrefix(metadataSource, "http://") {
|
||||||
tufSource = HttpSource
|
tufSource = HTTPSource
|
||||||
} else {
|
} else {
|
||||||
tufSource = OciSource
|
tufSource = OCISource
|
||||||
}
|
}
|
||||||
|
|
||||||
tufRootDigest := util.SHA256Hex(initialRoot)
|
tufRootDigest := util.SHA256Hex(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(tufPath, tufRootDigest)
|
||||||
err := os.MkdirAll(metadataPath, 0755)
|
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)
|
||||||
}
|
}
|
||||||
rootFile := filepath.Join(metadataPath, "root.json")
|
rootFile := filepath.Join(metadataPath, "root.json")
|
||||||
var rootBytes []byte
|
var rootBytes []byte
|
||||||
rootBytes, err = os.ReadFile(rootFile)
|
rootBytes, err = os.ReadFile(rootFile)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !errors.Is(err, fs.ErrNotExist) {
|
if !errors.Is(err, fs.ErrNotExist) {
|
||||||
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, 0644)
|
err = os.WriteFile(rootFile, 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)
|
||||||
}
|
}
|
||||||
@@ -85,11 +85,11 @@ func NewTufClient(initialRoot []byte, tufPath, metadataSource, targetsSource str
|
|||||||
cfg.LocalTargetsDir = filepath.Join(metadataPath, "download")
|
cfg.LocalTargetsDir = filepath.Join(metadataPath, "download")
|
||||||
cfg.RemoteTargetsURL = targetsSource
|
cfg.RemoteTargetsURL = targetsSource
|
||||||
|
|
||||||
if tufSource == OciSource {
|
if tufSource == OCISource {
|
||||||
metadataRepo, metadataTag, found := strings.Cut(metadataSource, ":")
|
metadataRepo, metadataTag, found := strings.Cut(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 = "latest"
|
metadataTag = LatestTag
|
||||||
}
|
}
|
||||||
cfg.Fetcher = NewRegistryFetcher(metadataRepo, metadataTag, targetsSource)
|
cfg.Fetcher = NewRegistryFetcher(metadataRepo, metadataTag, targetsSource)
|
||||||
}
|
}
|
||||||
@@ -106,7 +106,7 @@ func NewTufClient(initialRoot []byte, tufPath, metadataSource, targetsSource str
|
|||||||
return nil, fmt.Errorf("failed to refresh trusted metadata: %w", err)
|
return nil, fmt.Errorf("failed to refresh trusted metadata: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
client := &TufClient{
|
client := &Client{
|
||||||
updater: up,
|
updater: up,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
}
|
}
|
||||||
@@ -122,7 +122,7 @@ func NewTufClient(initialRoot []byte, tufPath, metadataSource, targetsSource str
|
|||||||
// DownloadTarget downloads the target file using Updater. The Updater gets the target
|
// DownloadTarget downloads the target file using Updater. The Updater gets the target
|
||||||
// information, verifies if the target is already cached, and if it is not cached,
|
// information, verifies if the target is already cached, and if it is not cached,
|
||||||
// downloads the target file.
|
// downloads the target file.
|
||||||
func (t *TufClient) DownloadTarget(target string, filePath string) (actualFilePath string, data []byte, err error) {
|
func (t *Client) DownloadTarget(target string, filePath string) (actualFilePath string, data []byte, err error) {
|
||||||
// search if the desired target is available
|
// search if the desired target is available
|
||||||
targetInfo, err := t.updater.GetTargetInfo(target)
|
targetInfo, err := t.updater.GetTargetInfo(target)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -131,7 +131,7 @@ func (t *TufClient) DownloadTarget(target string, filePath string) (actualFilePa
|
|||||||
|
|
||||||
// check if filePath exists and create the directory if it doesn't
|
// check if filePath exists and create the directory if it doesn't
|
||||||
if _, err := os.Stat(filepath.Dir(filePath)); os.IsNotExist(err) {
|
if _, err := os.Stat(filepath.Dir(filePath)); os.IsNotExist(err) {
|
||||||
err = os.MkdirAll(filepath.Dir(filePath), 0755)
|
err = os.MkdirAll(filepath.Dir(filePath), os.ModePerm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, fmt.Errorf("failed to create target download directory '%s': %w", filepath.Dir(filePath), err)
|
return "", nil, fmt.Errorf("failed to create target download directory '%s': %w", filepath.Dir(filePath), err)
|
||||||
}
|
}
|
||||||
@@ -155,15 +155,15 @@ func (t *TufClient) DownloadTarget(target string, filePath string) (actualFilePa
|
|||||||
return actualFilePath, data, err
|
return actualFilePath, data, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TufClient) GetMetadata() trustedmetadata.TrustedMetadata {
|
func (t *Client) GetMetadata() trustedmetadata.TrustedMetadata {
|
||||||
return t.updater.GetTrustedMetadataSet()
|
return t.updater.GetTrustedMetadataSet()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TufClient) MaxRootLength() int64 {
|
func (t *Client) MaxRootLength() int64 {
|
||||||
return t.cfg.RootMaxLength
|
return t.cfg.RootMaxLength
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TufClient) GetPriorRoots(metadataURL string) (map[string][]byte, error) {
|
func (t *Client) GetPriorRoots(metadataURL string) (map[string][]byte, error) {
|
||||||
rootMetadata := map[string][]byte{}
|
rootMetadata := map[string][]byte{}
|
||||||
trustedMetadata := t.GetMetadata()
|
trustedMetadata := t.GetMetadata()
|
||||||
client := fetcher.DefaultFetcher{}
|
client := fetcher.DefaultFetcher{}
|
||||||
@@ -177,12 +177,12 @@ func (t *TufClient) GetPriorRoots(metadataURL string) (map[string][]byte, error)
|
|||||||
return rootMetadata, nil
|
return rootMetadata, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TufClient) SetRemoteTargetsURL(url string) {
|
func (t *Client) SetRemoteTargetsURL(url string) {
|
||||||
t.cfg.RemoteTargetsURL = url
|
t.cfg.RemoteTargetsURL = url
|
||||||
}
|
}
|
||||||
|
|
||||||
// Derived from updater.loadTargets() in theupdateframework/go-tuf
|
// Derived from updater.loadTargets() in theupdateframework/go-tuf.
|
||||||
func (t *TufClient) LoadDelegatedTargets(roleName, parentName string) (*metadata.Metadata[metadata.TargetsType], error) {
|
func (t *Client) LoadDelegatedTargets(roleName, parentName string) (*metadata.Metadata[metadata.TargetsType], error) {
|
||||||
// extract the targets meta from the trusted snapshot metadata
|
// extract the targets meta from the trusted snapshot metadata
|
||||||
meta := t.updater.GetTrustedMetadataSet()
|
meta := t.updater.GetTrustedMetadataSet()
|
||||||
metaInfo := meta.Snapshot.Signed.Meta[fmt.Sprintf("%s.json", roleName)]
|
metaInfo := meta.Snapshot.Signed.Meta[fmt.Sprintf("%s.json", roleName)]
|
||||||
@@ -209,8 +209,8 @@ func (t *TufClient) LoadDelegatedTargets(roleName, parentName string) (*metadata
|
|||||||
return delegatedTargets, nil
|
return delegatedTargets, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// downloadMetadata download a metadata file and return it as bytes
|
// downloadMetadata download a metadata file and return it as bytes.
|
||||||
func (t *TufClient) downloadMetadata(roleName string, length int64, version string) ([]byte, error) {
|
func (t *Client) downloadMetadata(roleName string, length int64, version string) ([]byte, error) {
|
||||||
urlPath := ensureTrailingSlash(t.cfg.RemoteMetadataURL)
|
urlPath := ensureTrailingSlash(t.cfg.RemoteMetadataURL)
|
||||||
// build urlPath
|
// build urlPath
|
||||||
if version == "" {
|
if version == "" {
|
||||||
@@ -221,7 +221,7 @@ func (t *TufClient) downloadMetadata(roleName string, length int64, version stri
|
|||||||
return t.cfg.Fetcher.DownloadFile(urlPath, length, time.Second*15)
|
return t.cfg.Fetcher.DownloadFile(urlPath, length, time.Second*15)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ensureTrailingSlash ensures url ends with a slash
|
// ensureTrailingSlash ensures url ends with a slash.
|
||||||
func ensureTrailingSlash(url string) string {
|
func ensureTrailingSlash(url string) string {
|
||||||
if updater.IsWindowsPath(url) {
|
if updater.IsWindowsPath(url) {
|
||||||
slash := string(filepath.Separator)
|
slash := string(filepath.Separator)
|
||||||
@@ -236,7 +236,7 @@ func ensureTrailingSlash(url string) string {
|
|||||||
return url + "/"
|
return url + "/"
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetEmbeddedTufRoot returns the embedded TUF root based on the given root name
|
// GetEmbeddedRoot returns the embedded TUF root based on the given root name.
|
||||||
func GetEmbeddedTufRoot(root string) (*embed.EmbeddedRoot, error) {
|
func GetEmbeddedRoot(root string) (*embed.EmbeddedRoot, error) {
|
||||||
return embed.GetRootFromName(root)
|
return embed.GetRootFromName(root)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
HttpTufTestDataPath = filepath.Join("..", "..", "test", "testdata", "tuf", "test-repo")
|
HTTPTUFTestDataPath = filepath.Join("..", "..", "test", "testdata", "tuf", "test-repo")
|
||||||
OciTufTestDataPath = filepath.Join("..", "..", "test", "testdata", "tuf", "test-repo-oci")
|
OCITUFTestDataPath = filepath.Join("..", "..", "test", "testdata", "tuf", "test-repo-oci")
|
||||||
)
|
)
|
||||||
|
|
||||||
func CreateTempDir(t *testing.T, dir, pattern string) string {
|
func CreateTempDir(t *testing.T, dir, pattern string) string {
|
||||||
@@ -35,12 +35,12 @@ func CreateTempDir(t *testing.T, dir, pattern string) string {
|
|||||||
return tempDir
|
return tempDir
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTufClient creates a new TUF client
|
// NewTufClient creates a new TUF client.
|
||||||
func TestRootInit(t *testing.T) {
|
func TestRootInit(t *testing.T) {
|
||||||
tufPath := CreateTempDir(t, "", "tuf_temp")
|
tufPath := CreateTempDir(t, "", "tuf_temp")
|
||||||
|
|
||||||
// Start a test HTTP server to serve data from /test/testdata/tuf/test-repo/ paths
|
// Start a test HTTP server to serve data from /test/testdata/tuf/test-repo/ paths
|
||||||
server := httptest.NewServer(http.FileServer(http.Dir(HttpTufTestDataPath)))
|
server := httptest.NewServer(http.FileServer(http.Dir(HTTPTUFTestDataPath)))
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
|
||||||
// run local registry
|
// run local registry
|
||||||
@@ -50,10 +50,10 @@ func TestRootInit(t *testing.T) {
|
|||||||
t.Fatalf("failed to terminate container: %s", err) // nolint:gocritic
|
t.Fatalf("failed to terminate container: %s", err) // nolint:gocritic
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
LoadRegistryTestData(t, regAddr, OciTufTestDataPath)
|
LoadRegistryTestData(t, regAddr, OCITUFTestDataPath)
|
||||||
|
|
||||||
alwaysGoodVersionChecker := &mockVersionChecker{err: nil}
|
alwaysGoodVersionChecker := &MockVersionChecker{err: nil}
|
||||||
alwaysBadVersionChecker := &mockVersionChecker{err: assert.AnError}
|
alwaysBadVersionChecker := &MockVersionChecker{err: assert.AnError}
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
@@ -65,17 +65,17 @@ func TestRootInit(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
_, err := NewTufClient(embed.RootDev.Data, tufPath, tc.metadataSource, tc.targetsSource, alwaysGoodVersionChecker)
|
_, err := NewClient(embed.RootDev.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 = NewTufClient(embed.RootDev.Data, tufPath, tc.metadataSource, tc.targetsSource, alwaysGoodVersionChecker)
|
_, err = NewClient(embed.RootDev.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 = NewTufClient([]byte("broken"), tufPath, tc.metadataSource, tc.targetsSource, alwaysGoodVersionChecker)
|
_, err = NewClient([]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 = NewTufClient(embed.RootDev.Data, tufPath, tc.metadataSource, tc.targetsSource, alwaysBadVersionChecker)
|
_, err = NewClient(embed.RootDev.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 creating TUF client with bad attest version: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -83,11 +83,11 @@ func TestRootInit(t *testing.T) {
|
|||||||
func TestDownloadTarget(t *testing.T) {
|
func TestDownloadTarget(t *testing.T) {
|
||||||
tufPath := CreateTempDir(t, "", "tuf_temp")
|
tufPath := CreateTempDir(t, "", "tuf_temp")
|
||||||
targetFile := "test.txt"
|
targetFile := "test.txt"
|
||||||
delegatedRole := "test-role"
|
delegatedRole := testRole
|
||||||
delegatedTargetFile := fmt.Sprintf("%s/%s", delegatedRole, targetFile)
|
delegatedTargetFile := fmt.Sprintf("%s/%s", delegatedRole, targetFile)
|
||||||
|
|
||||||
// Start a test HTTP server to serve data from /test/testdata/tuf/test-repo/ paths
|
// Start a test HTTP server to serve data from /test/testdata/tuf/test-repo/ paths
|
||||||
server := httptest.NewServer(http.FileServer(http.Dir(HttpTufTestDataPath)))
|
server := httptest.NewServer(http.FileServer(http.Dir(HTTPTUFTestDataPath)))
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
|
||||||
// run local registry
|
// run local registry
|
||||||
@@ -97,9 +97,9 @@ func TestDownloadTarget(t *testing.T) {
|
|||||||
t.Fatalf("failed to terminate container: %s", err) // nolint:gocritic
|
t.Fatalf("failed to terminate container: %s", err) // nolint:gocritic
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
LoadRegistryTestData(t, regAddr, OciTufTestDataPath)
|
LoadRegistryTestData(t, regAddr, OCITUFTestDataPath)
|
||||||
|
|
||||||
alwaysGoodVersionChecker := &mockVersionChecker{err: nil}
|
alwaysGoodVersionChecker := &MockVersionChecker{err: nil}
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
@@ -111,7 +111,7 @@ func TestDownloadTarget(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
tufClient, err := NewTufClient(embed.RootDev.Data, tufPath, tc.metadataSource, tc.targetsSource, alwaysGoodVersionChecker)
|
tufClient, err := NewClient(embed.RootDev.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)
|
||||||
|
|
||||||
// get trusted tuf metadata
|
// get trusted tuf metadata
|
||||||
@@ -135,22 +135,22 @@ func TestDownloadTarget(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestGetEmbeddedTufRootBytes(t *testing.T) {
|
func TestGetEmbeddedTufRootBytes(t *testing.T) {
|
||||||
dev, err := GetEmbeddedTufRoot("dev")
|
dev, err := GetEmbeddedRoot("dev")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
staging, err := GetEmbeddedTufRoot("staging")
|
staging, err := GetEmbeddedRoot("staging")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotEqual(t, dev.Data, staging.Data)
|
assert.NotEqual(t, dev.Data, staging.Data)
|
||||||
|
|
||||||
prod, err := GetEmbeddedTufRoot("prod")
|
prod, err := GetEmbeddedRoot("prod")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotEqual(t, dev.Data, prod.Data)
|
assert.NotEqual(t, dev.Data, prod.Data)
|
||||||
assert.NotEqual(t, staging.Data, prod.Data)
|
assert.NotEqual(t, staging.Data, prod.Data)
|
||||||
|
|
||||||
def, err := GetEmbeddedTufRoot("")
|
def, err := GetEmbeddedRoot("")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, def.Data, prod.Data)
|
assert.Equal(t, def.Data, prod.Data)
|
||||||
|
|
||||||
_, err = GetEmbeddedTufRoot("invalid")
|
_, err = GetEmbeddedRoot("invalid")
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ const ThisModulePath = "github.com/docker/attest"
|
|||||||
|
|
||||||
type VersionChecker interface {
|
type VersionChecker interface {
|
||||||
// CheckVersion checks if the current version of this library meets the constraints from the TUF repo
|
// CheckVersion checks if the current version of this library meets the constraints from the TUF repo
|
||||||
CheckVersion(tufClient TUFClient) error
|
CheckVersion(tufClient Downloader) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type InvalidVersionError struct {
|
type InvalidVersionError struct {
|
||||||
@@ -32,13 +32,13 @@ func (e *InvalidVersionError) Error() string {
|
|||||||
return fmt.Sprintf("%s version %s does not satisfy constraints %s: %s", ThisModulePath, e.AttestVersion, e.VersionConstraint, errsStr.String())
|
return fmt.Sprintf("%s version %s does not satisfy constraints %s: %s", ThisModulePath, e.AttestVersion, e.VersionConstraint, errsStr.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewVersionChecker() *versionChecker {
|
func NewDefaultVersionChecker() *DefaultVersionChecker {
|
||||||
return &versionChecker{}
|
return &DefaultVersionChecker{}
|
||||||
}
|
}
|
||||||
|
|
||||||
type versionChecker struct{}
|
type DefaultVersionChecker struct{}
|
||||||
|
|
||||||
func (vc *versionChecker) CheckVersion(client TUFClient) error {
|
func (vc *DefaultVersionChecker) CheckVersion(client Downloader) error {
|
||||||
var attestMod *debug.Module
|
var attestMod *debug.Module
|
||||||
bi, ok := debug.ReadBuildInfo()
|
bi, ok := debug.ReadBuildInfo()
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|||||||
Reference in New Issue
Block a user