Compare commits
40 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6397dcede8 | ||
|
|
1a7897a052 | ||
|
|
745eea09e8 | ||
|
|
84d7903c46 | ||
|
|
7234e29829 | ||
|
|
b46f544f0c | ||
|
|
85d7b34e18 | ||
|
|
c416c11e10 | ||
|
|
0020ece3b4 | ||
|
|
ec1c994f04 | ||
|
|
6ebf042966 | ||
|
|
a86c8c1209 | ||
|
|
dd621e2a13 | ||
|
|
b05523e7ea | ||
|
|
eddb277d7e | ||
|
|
a103e0e9d7 | ||
|
|
249cf5bcf3 | ||
|
|
33a1996b2b | ||
|
|
1b24098027 | ||
|
|
64f3c9b149 | ||
|
|
3ee718ee67 | ||
|
|
06947cf992 | ||
|
|
4648680a75 | ||
|
|
17902c4eb8 | ||
|
|
bd6d130e17 | ||
|
|
bd849d9b43 | ||
|
|
8d45522fe8 | ||
|
|
da22f71207 | ||
|
|
c69a9586c5 | ||
|
|
e3d02ab2e1 | ||
|
|
d5b059043f | ||
|
|
54996b3c0b | ||
|
|
4566ea56b3 | ||
|
|
20dd9da7c0 | ||
|
|
3aa738b246 | ||
|
|
c99f90cbbf | ||
|
|
3701942bf1 | ||
|
|
0cadeefe6f | ||
|
|
bc7139deaa | ||
|
|
b461c7f8d8 |
128
README.md
128
README.md
@@ -2,129 +2,13 @@
|
||||
library to create, verify, and evaluate policy for attestations on container images
|
||||
|
||||
# usage
|
||||
## verifying attestations
|
||||
1. create a TUF client
|
||||
* using OCI registry for TUF
|
||||
```go
|
||||
tufOutputPath = "/.docker/tuf"
|
||||
metadataURI = "docker/tuf-metadata:latest"
|
||||
targetsURI = "docker/tuf-targets"
|
||||
tufClient, err := tuf.NewTufClient(embed.DefaultRoot, tufOutputPath, metadataURI, targetsURI)
|
||||
```
|
||||
* using HTTPS for TUF
|
||||
```go
|
||||
tufOutputPath = "/.docker/tuf"
|
||||
metadataURI = "https://docker.github.io/tuf/metadata"
|
||||
targetsURI = "https://docker.github.io/tuf/targets"
|
||||
tufClient, err := tuf.NewTufClient(embed.DefaultRoot, tufOutputPath, metadataURI, targetsURI)
|
||||
```
|
||||
## signing and verifying attestations
|
||||
See [example_sign_test.go](./pkg/attest/example_sign_test.go)
|
||||
|
||||
1. configure an attestation resolver
|
||||
* using OCI registry
|
||||
```go
|
||||
var resolver oci.AttestationResolver
|
||||
resolver = &oci.RegistryResolver{
|
||||
Image: image, // path to image index in OCI registry containing image attestations (e.g. docker/nginx:latest)
|
||||
Platform: platform, // platform of subject image (image that attestations are being verified against)
|
||||
}
|
||||
```
|
||||
* using local OCI layout
|
||||
```go
|
||||
var resolver oci.AttestationResolver
|
||||
resolver = &oci.OCILayoutResolver{
|
||||
Path: path, // file path to OCI layout containing image attestations (e.g. /myimage)
|
||||
Platform: platform, // platform of subject image (image that attestations are being verified against)
|
||||
}
|
||||
```
|
||||
See [example_verify_test.go](./pkg/attest/example_verify_test.go)
|
||||
|
||||
1. configure policy options
|
||||
```go
|
||||
opts := &policy.PolicyOptions{
|
||||
TufClient: tufClient,
|
||||
LocalTargetsDir: "/.docker/policy", // location to store policy files downloaded from TUF
|
||||
LocalPolicyDir: "", // overrides TUF policy for local policy files
|
||||
}
|
||||
```
|
||||
## mirroring TUF repositories to OCI
|
||||
See [example_mirror_test.go](./pkg/mirror/example_mirror_test.go)
|
||||
|
||||
1. verify attestations
|
||||
```go
|
||||
policy, err := attest.Verify(ctx, opts, resolver)
|
||||
if err != nil {
|
||||
return false // failed policy or attestation signature verification
|
||||
}
|
||||
if policy {
|
||||
return true // passed policy
|
||||
}
|
||||
return true // no policy for image
|
||||
```
|
||||
|
||||
## signing attestations
|
||||
1. generate an image with intoto Statements (optional)
|
||||
```sh
|
||||
docker buildx build <PATH TO DOCKERFILE> --sbom true --provenance true --output type=oci,tar=false,name=<REPO>:<TAG>,dest=<OUTPUT DIR>
|
||||
```
|
||||
|
||||
1. confgiure a `dsse.SignerVerifier`
|
||||
```go
|
||||
var signer dsse.SignerVerifier
|
||||
signer, err = signerverifier.GetAWSSigner(cmd.Context(), aws_arn, aws_region)
|
||||
```
|
||||
|
||||
1. configure signing options
|
||||
```go
|
||||
opts := &attest.SigningOptions{
|
||||
Replace: true, // replace unsigned intoto statements with signed intoto attestations, otherwise leave in place
|
||||
}
|
||||
```
|
||||
* add [Verification Summary Attestation (VSA)](https://slsa.dev/spec/v1.0/verification_summary) for all intoto attestations (optional)
|
||||
```go
|
||||
opts.VSAOptions = &attestation.VSAOptions{
|
||||
BuildLevel: "SLSA_BUILD_LEVEL_" + slsaBuildLevel,
|
||||
PolicyURI: slsaPolicyUri,
|
||||
VerifierID: slsaVerifierId,
|
||||
}
|
||||
```
|
||||
1. load attestations
|
||||
* oci registry
|
||||
```go
|
||||
ref := "docker/attest:latest"
|
||||
att, err := oci.AttestationIndexFromRemote(ref)
|
||||
```
|
||||
* local filepath
|
||||
```go
|
||||
path := "/test-image"
|
||||
att, err := oci.AttestationIndexFromPath(path)
|
||||
```
|
||||
|
||||
1. sign attestations
|
||||
```go
|
||||
signedImageIndex, err := attest.Sign(ctx, att, signer, opts)
|
||||
```
|
||||
`attest.Sign()` iterates over attestation manifests in the image index and signs all intoto statements (optionally generates a VSA), returning a mutated ImageIndex with all intoto statements signed as attestations.
|
||||
|
||||
1. save output (optional)
|
||||
* push to oci registry
|
||||
```go
|
||||
err = mirror.PushToRegistry(signedImageIndex, ref)
|
||||
```
|
||||
* save to local filesystem
|
||||
```go
|
||||
idx := v1.ImageIndex(empty.Index)
|
||||
idx = mutate.AppendManifests(idx, mutate.IndexAddendum{
|
||||
Add: signedImageIndex,
|
||||
Descriptor: v1.Descriptor{
|
||||
Annotations: map[string]string{
|
||||
oci.OciReferenceTarget: att.Name,
|
||||
},
|
||||
},
|
||||
})
|
||||
err = mirror.SaveAsOCILayout(idx, path)
|
||||
```
|
||||
|
||||
## mirroring TUF repositories
|
||||
TODO: write content for this outline
|
||||
### mirroring TUF metadata to OCI
|
||||
#### delegated metadata
|
||||
### mirroring TUF targets to OCI
|
||||
#### delegated targets
|
||||
### using `go-tuf` OCI registry client
|
||||
See [example_registry_test.go](./pkg/tuf/example_registry_test.go)
|
||||
|
||||
117
go.mod
117
go.mod
@@ -3,8 +3,9 @@ module github.com/docker/attest
|
||||
go 1.22.1
|
||||
|
||||
require (
|
||||
github.com/aws/aws-sdk-go-v2/config v1.27.11
|
||||
github.com/containerd/containerd v1.7.16
|
||||
github.com/Masterminds/semver/v3 v3.2.1
|
||||
github.com/aws/aws-sdk-go-v2/config v1.27.15
|
||||
github.com/containerd/containerd v1.7.17
|
||||
github.com/distribution/reference v0.6.0
|
||||
github.com/go-openapi/runtime v0.28.0
|
||||
github.com/go-openapi/strfmt v0.23.0
|
||||
@@ -13,58 +14,58 @@ require (
|
||||
github.com/in-toto/in-toto-golang v0.9.0
|
||||
github.com/open-policy-agent/opa v0.64.1
|
||||
github.com/opencontainers/image-spec v1.1.0
|
||||
github.com/package-url/packageurl-go v0.1.2
|
||||
github.com/package-url/packageurl-go v0.1.3
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/secure-systems-lab/go-securesystemslib v0.8.0
|
||||
github.com/sigstore/cosign/v2 v2.2.4
|
||||
github.com/sigstore/rekor v1.3.6
|
||||
github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.3
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/testcontainers/testcontainers-go v0.30.0
|
||||
github.com/testcontainers/testcontainers-go/modules/registry v0.30.0
|
||||
github.com/theupdateframework/go-tuf/v2 v2.0.0-20240402164131-b2e024ad4752
|
||||
github.com/testcontainers/testcontainers-go v0.31.0
|
||||
github.com/testcontainers/testcontainers-go/modules/registry v0.31.0
|
||||
github.com/theupdateframework/go-tuf/v2 v2.0.0-20240504210453-5a634eb214ae // for https://github.com/theupdateframework/go-tuf/pull/632
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
sigs.k8s.io/yaml v1.4.0
|
||||
)
|
||||
|
||||
replace github.com/theupdateframework/go-tuf/v2 => github.com/mrjoelkamp/go-tuf/v2 v2.0.1 // for https://github.com/theupdateframework/go-tuf/pull/632
|
||||
|
||||
require (
|
||||
dario.cat/mergo v1.0.0 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.1 // indirect
|
||||
github.com/Microsoft/hcsshim v0.11.4 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/Microsoft/hcsshim v0.12.3 // indirect
|
||||
github.com/OneOfOne/xxhash v1.2.8 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.0.0 // indirect
|
||||
github.com/agnivade/levenshtein v1.1.1 // indirect
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.26.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.11 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.27.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.15 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/kms v1.30.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.20.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.28.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.9 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/kms v1.31.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.20.8 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.28.9 // indirect
|
||||
github.com/aws/smithy-go v1.20.2 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/blang/semver v3.5.1+incompatible // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/cloudflare/circl v1.3.8 // indirect
|
||||
github.com/containerd/log v0.1.0 // indirect
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.15.1 // indirect
|
||||
github.com/cpuguy83/dockercfg v0.3.1 // indirect
|
||||
github.com/cyberphone/json-canonicalization v0.0.0-20231011164504-785e29786b46 // indirect
|
||||
github.com/cyberphone/json-canonicalization v0.0.0-20231217050601-ba74d44ecf5f // 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/timestamp v0.0.0-20231217203849-220c5c2851b7 // indirect
|
||||
github.com/docker/cli v24.0.7+incompatible // indirect
|
||||
github.com/docker/cli v26.1.3+incompatible // indirect
|
||||
github.com/docker/distribution v2.8.3+incompatible // indirect
|
||||
github.com/docker/docker v25.0.5+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.8.0 // indirect
|
||||
github.com/docker/docker v26.1.3+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.8.1 // indirect
|
||||
github.com/docker/go-connections v0.5.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
@@ -72,9 +73,10 @@ require (
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/go-chi/chi v4.1.2+incompatible // indirect
|
||||
github.com/go-ini/ini v1.67.0 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.0.1 // indirect
|
||||
github.com/go-logr/logr v1.4.1 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||
github.com/go-openapi/analysis v0.23.0 // indirect
|
||||
github.com/go-openapi/errors v0.22.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
||||
@@ -89,19 +91,20 @@ require (
|
||||
github.com/google/certificate-transparency-go v1.1.8 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/gorilla/mux v1.8.1 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.5 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.6 // indirect
|
||||
github.com/hashicorp/hcl v1.0.1-vault-5 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267 // indirect
|
||||
github.com/jellydator/ttlcache/v3 v3.2.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/klauspost/compress v1.17.4 // indirect
|
||||
github.com/letsencrypt/boulder v0.0.0-20231026200631-000cd05d5491 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||
github.com/klauspost/compress v1.17.8 // indirect
|
||||
github.com/letsencrypt/boulder v0.0.0-20240515153123-6ae6aa8e9055 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||
github.com/moby/patternmatcher v0.6.0 // indirect
|
||||
github.com/moby/sys/sequential v0.5.0 // indirect
|
||||
github.com/moby/sys/user v0.1.0 // indirect
|
||||
@@ -111,19 +114,19 @@ require (
|
||||
github.com/oklog/ulid v1.3.1 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opentracing/opentracing-go v1.2.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/prometheus/client_golang v1.19.0 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
||||
github.com/prometheus/client_golang v1.19.1 // indirect
|
||||
github.com/prometheus/client_model v0.6.1 // indirect
|
||||
github.com/prometheus/common v0.51.1 // indirect
|
||||
github.com/prometheus/procfs v0.12.0 // indirect
|
||||
github.com/prometheus/common v0.53.0 // indirect
|
||||
github.com/prometheus/procfs v0.15.0 // indirect
|
||||
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect
|
||||
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||
github.com/sassoftware/relic v7.2.1+incompatible // indirect
|
||||
github.com/shibumi/go-pathspec v1.3.0 // indirect
|
||||
github.com/shirou/gopsutil/v3 v3.23.12 // indirect
|
||||
github.com/shirou/gopsutil/v3 v3.24.4 // indirect
|
||||
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||
github.com/sigstore/sigstore v1.8.3 // indirect
|
||||
github.com/sigstore/timestamp-authority v1.2.2 // indirect
|
||||
@@ -139,34 +142,32 @@ require (
|
||||
github.com/tchap/go-patricia/v2 v2.3.1 // indirect
|
||||
github.com/theupdateframework/go-tuf v0.7.0 // indirect
|
||||
github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
||||
github.com/tklauser/numcpus v0.6.1 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.14 // indirect
|
||||
github.com/tklauser/numcpus v0.8.0 // indirect
|
||||
github.com/transparency-dev/merkle v0.0.2 // indirect
|
||||
github.com/vbatts/tar-split v0.11.5 // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
github.com/yashtewari/glob-intersection v0.2.0 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.3 // indirect
|
||||
go.mongodb.org/mongo-driver v1.14.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
|
||||
go.opentelemetry.io/otel v1.24.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.24.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.24.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.24.0 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
go.mongodb.org/mongo-driver v1.15.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 // indirect
|
||||
go.opentelemetry.io/otel v1.26.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.26.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.26.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.26.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
golang.org/x/crypto v0.22.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20231108232855-2478ac86f678 // indirect
|
||||
golang.org/x/mod v0.16.0 // indirect
|
||||
golang.org/x/crypto v0.23.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect
|
||||
golang.org/x/mod v0.17.0 // indirect
|
||||
golang.org/x/sync v0.7.0 // indirect
|
||||
golang.org/x/sys v0.19.0 // indirect
|
||||
golang.org/x/term v0.19.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/tools v0.19.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect
|
||||
google.golang.org/grpc v1.63.2 // indirect
|
||||
google.golang.org/protobuf v1.33.0 // indirect
|
||||
gopkg.in/go-jose/go-jose.v2 v2.6.3 // indirect
|
||||
golang.org/x/sys v0.20.0 // indirect
|
||||
golang.org/x/term v0.20.0 // indirect
|
||||
golang.org/x/text v0.15.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 // indirect
|
||||
google.golang.org/grpc v1.64.0 // indirect
|
||||
google.golang.org/protobuf v1.34.1 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
k8s.io/klog/v2 v2.120.1 // indirect
|
||||
|
||||
296
go.sum
296
go.sum
@@ -1,6 +1,6 @@
|
||||
cloud.google.com/go v0.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM=
|
||||
cloud.google.com/go/compute v1.25.0 h1:H1/4SqSUhjPFE7L5ddzHOfY2bCAvjwNRZPNl6Ni5oYU=
|
||||
cloud.google.com/go/compute v1.25.0/go.mod h1:GR7F0ZPZH8EhChlMo9FkLd7eUTwEymjqQagxzilIxIE=
|
||||
cloud.google.com/go/compute v1.25.1 h1:ZRpHJedLtTpKgr3RV1Fx23NuaAEN1Zfx9hw1u4aJdjU=
|
||||
cloud.google.com/go/compute v1.25.1/go.mod h1:oopOIR53ly6viBYxaDhBfJwzUAxf1zE//uf3IB011ls=
|
||||
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
|
||||
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
|
||||
cloud.google.com/go/iam v1.1.6 h1:bEa06k05IO4f4uJonbB5iAgKTPpABy1ayxaIZV/GHVc=
|
||||
@@ -53,14 +53,16 @@ github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUM
|
||||
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
|
||||
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
|
||||
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
|
||||
github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8=
|
||||
github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w=
|
||||
github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0=
|
||||
github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/Microsoft/hcsshim v0.12.3 h1:LS9NXqXhMoqNCplK1ApmVSfB4UnVLRDWRapB6EIlxE0=
|
||||
github.com/Microsoft/hcsshim v0.12.3/go.mod h1:Iyl1WVpZzr+UkzjekHZbV8o5Z9ZkxNGx6CtY2Qg/JVQ=
|
||||
github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8=
|
||||
github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c h1:kMFnB0vCcX7IL/m9Y5LO+KQYv+t1CQOiFe6+SV2J7bE=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
|
||||
github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78=
|
||||
github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
|
||||
github.com/ThalesIgnite/crypto11 v1.2.5 h1:1IiIIEqYmBvUYFeMnHqRft4bwf/O36jryEUpY+9ef8E=
|
||||
github.com/ThalesIgnite/crypto11 v1.2.5/go.mod h1:ILDKtnCKiQ7zRoNxcp36Y1ZR8LBPmR2E23+wTQe/MlE=
|
||||
github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8=
|
||||
@@ -95,18 +97,18 @@ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3d
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
|
||||
github.com/aws/aws-sdk-go v1.51.6 h1:Ld36dn9r7P9IjU8WZSaswQ8Y/XUCRpewim5980DwYiU=
|
||||
github.com/aws/aws-sdk-go v1.51.6/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk=
|
||||
github.com/aws/aws-sdk-go-v2 v1.26.1 h1:5554eUqIYVWpU0YmeeYZ0wU64H2VLBs8TlhRB2L+EkA=
|
||||
github.com/aws/aws-sdk-go-v2 v1.26.1/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.27.11 h1:f47rANd2LQEYHda2ddSCKYId18/8BhSRM4BULGmfgNA=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.27.11/go.mod h1:SMsV78RIOYdve1vf36z8LmnszlRWkwMQtomCAI0/mIE=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.11 h1:YuIB1dJNf1Re822rriUOTxopaHHvIq0l/pX3fwO+Tzs=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.11/go.mod h1:AQtFPsDH9bI2O+71anW6EKL+NcD7LG3dpKGMV4SShgo=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1 h1:FVJ0r5XTHSmIHJV6KuDmdYhEpvlHpiSd38RQWhut5J4=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1/go.mod h1:zusuAeqezXzAB24LGuzuekqMAEgWkVYukBec3kr3jUg=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 h1:aw39xVGeRWlWx9EzGVnhOR4yOjQDHPQ6o6NmBlscyQg=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5/go.mod h1:FSaRudD0dXiMPK2UjknVwwTYyZMRsHv3TtkabsZih5I=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 h1:PG1F3OD1szkuQPzDw3CIQsRIrtTlUC3lP84taWzHlq0=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5/go.mod h1:jU1li6RFryMz+so64PpKtudI+QzbKoIEivqdf6LNpOc=
|
||||
github.com/aws/aws-sdk-go-v2 v1.27.0 h1:7bZWKoXhzI+mMR/HjdMx8ZCC5+6fY0lS5tr0bbgiLlo=
|
||||
github.com/aws/aws-sdk-go-v2 v1.27.0/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.27.15 h1:uNnGLZ+DutuNEkuPh6fwqK7LpEiPmzb7MIMA1mNWEUc=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.27.15/go.mod h1:7j7Kxx9/7kTmL7z4LlhwQe63MYEE5vkVV6nWg4ZAI8M=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.15 h1:YDexlvDRCA8ems2T5IP1xkMtOZ1uLJOCJdTr0igs5zo=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.15/go.mod h1:vxHggqW6hFNaeNC0WyXS3VdyjcV0a4KMUY4dKJ96buU=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.3 h1:dQLK4TjtnlRGb0czOht2CevZ5l6RSyRWAnKeGd7VAFE=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.3/go.mod h1:TL79f2P6+8Q7dTsILpiVST+AL9lkF6PPGI167Ny0Cjw=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.7 h1:lf/8VTF2cM+N4SLzaYJERKEWAXq8MOMpZfU6wEPWsPk=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.7/go.mod h1:4SjkU7QiqK2M9oozyMzfZ/23LmUY+h3oFqhdeP5OMiI=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.7 h1:4OYVp0705xu8yjdyoWix0r9wPIRXnIzzOoUpQVHIJ/g=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.7/go.mod h1:vd7ESTEvI76T2Na050gODNmNU7+OyKrIKroYTu4ABiI=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY=
|
||||
github.com/aws/aws-sdk-go-v2/service/ecr v1.20.2 h1:y6LX9GUoEA3mO0qpFl1ZQHj1rFyPWVphlzebiSt2tKE=
|
||||
@@ -115,16 +117,16 @@ github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.18.2 h1:PpbXaecV3sLAS6rjQiaKw4
|
||||
github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.18.2/go.mod h1:fUHpGXr4DrXkEDpGAjClPsviWf+Bszeb0daKE0blxv8=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 h1:Ji0DY1xUsUr3I8cHps0G+XM3WWU16lP6yG8qu1GAZAs=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2/go.mod h1:5CsjAbs3NlGQyZNFACh+zztPDI7fU6eW9QsxjfnuBKg=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.7 h1:ogRAwT1/gxJBcSWDMZlgyFUM962F51A5CRhDLbxLdmo=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.7/go.mod h1:YCsIZhXfRPLFFCl5xxY+1T9RKzOKjCut+28JSX2DnAk=
|
||||
github.com/aws/aws-sdk-go-v2/service/kms v1.30.0 h1:yS0JkEdV6h9JOo8sy2JSpjX+i7vsKifU8SIeHrqiDhU=
|
||||
github.com/aws/aws-sdk-go-v2/service/kms v1.30.0/go.mod h1:+I8VUUSVD4p5ISQtzpgSva4I8cJ4SQ4b1dcBcof7O+g=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.20.5 h1:vN8hEbpRnL7+Hopy9dzmRle1xmDc7o8tmY0klsr175w=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.20.5/go.mod h1:qGzynb/msuZIE8I75DVRCUXw3o3ZyBmUvMwQ2t/BrGM=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.4 h1:Jux+gDDyi1Lruk+KHF91tK2KCuY61kzoCpvtvJJBtOE=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.4/go.mod h1:mUYPBhaF2lGiukDEjJX2BLRRKTmoUSitGDUgM4tRxak=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.28.6 h1:cwIxeBttqPN3qkaAjcEcsh8NYr8n2HZPkcKgPAi1phU=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.28.6/go.mod h1:FZf1/nKNEkHdGGJP/cI2MoIMquumuRK6ol3QQJNDxmw=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.9 h1:Wx0rlZoEJR7JwlSZcHnEa7CNjrSIyVxMFWGAaXy4fJY=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.9/go.mod h1:aVMHdE0aHO3v+f/iw01fmXV/5DbfQ3Bi9nN7nd9bE9Y=
|
||||
github.com/aws/aws-sdk-go-v2/service/kms v1.31.2 h1:z4NOTY1sm0Vb/+Kovnbf8TLPcH8P36bILR5hgXE1sOY=
|
||||
github.com/aws/aws-sdk-go-v2/service/kms v1.31.2/go.mod h1:6HNwTCo40yDvnmgT/NgRgWsx0/0bN2TV6RO5FfG8G60=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.20.8 h1:Kv1hwNG6jHC/sxMTe5saMjH6t6ZLkgfvVxyEjfWL1ks=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.20.8/go.mod h1:c1qtZUWtygI6ZdvKppzCSXsDOq5I4luJPZ0Ud3juFCA=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.2 h1:nWBZ1xHCF+A7vv9sDzJOq4NWIdzFYm0kH7Pr4OjHYsQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.2/go.mod h1:9lmoVDVLz/yUZwLaQ676TK02fhCu4+PgRSmMaKR1ozk=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.28.9 h1:Qp6Boy0cGDloOE3zI6XhNLNZgjNS8YmiFQFHe71SaW0=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.28.9/go.mod h1:0Aqn1MnEuitqfsCNyKsdKLhDUOr4txD/g19EfiUqgws=
|
||||
github.com/aws/smithy-go v1.20.2 h1:tbp628ireGtzcHDDmLT/6ADHidqnwgF57XOXZe6tp4Q=
|
||||
github.com/aws/smithy-go v1.20.2/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E=
|
||||
github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20231024185945-8841054dbdb8 h1:SoFYaT9UyGkR0+nogNyD/Lj+bsixB+SNuAS4ABlEs6M=
|
||||
@@ -139,16 +141,17 @@ github.com/buildkite/go-pipeline v0.3.2 h1:SW4EaXNwfjow7xDRPGgX0Rcx+dPj5C1kV9LKC
|
||||
github.com/buildkite/go-pipeline v0.3.2/go.mod h1:iY5jzs3Afc8yHg6KDUcu3EJVkfaUkd9x/v/OH98qyUA=
|
||||
github.com/buildkite/interpolate v0.0.0-20200526001904-07f35b4ae251 h1:k6UDF1uPYOs0iy1HPeotNa155qXRWrzKnqAaGXHLZCE=
|
||||
github.com/buildkite/interpolate v0.0.0-20200526001904-07f35b4ae251/go.mod h1:gbPR1gPu9dB96mucYIR7T3B7p/78hRVSOuzIWLHK2Y4=
|
||||
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
|
||||
github.com/bytecodealliance/wasmtime-go/v3 v3.0.2 h1:3uZCA/BLTIu+DqCfguByNMJa2HVHpXvjfy0Dy7g6fuA=
|
||||
github.com/bytecodealliance/wasmtime-go/v3 v3.0.2/go.mod h1:RnUjnIXxEJcL6BgCvNyzCCRzZcxCgsZCi+RNlvYor5Q=
|
||||
github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M=
|
||||
github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs=
|
||||
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
|
||||
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chrismellard/docker-credential-acr-env v0.0.0-20230304212654-82a0ddb27589 h1:krfRl01rzPzxSxyLyrChD+U+MzsBXbm0OwYYB67uF+4=
|
||||
github.com/chrismellard/docker-credential-acr-env v0.0.0-20230304212654-82a0ddb27589/go.mod h1:OuDyvmLnMCwa2ep4Jkm6nyA0ocJuZlGyk2gGseVzERM=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
@@ -156,20 +159,21 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME=
|
||||
github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
|
||||
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
|
||||
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
|
||||
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
|
||||
github.com/cloudflare/circl v1.3.8 h1:j+V8jJt09PoeMFIu2uh5JUyEaIHTXVOHslFoLNAKqwI=
|
||||
github.com/cloudflare/circl v1.3.8/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZFnBQS5QU=
|
||||
github.com/cockroachdb/apd/v3 v3.2.1 h1:U+8j7t0axsIgvQUqthuNm82HIrYXodOV2iWLWtEaIwg=
|
||||
github.com/cockroachdb/apd/v3 v3.2.1/go.mod h1:klXJcjp+FffLTHlhIG69tezTDvdP065naDsHzKhYSqc=
|
||||
github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE=
|
||||
github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4=
|
||||
github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be h1:J5BL2kskAlV9ckgEsNQXscjIaLiOYiZ75d4e94E6dcQ=
|
||||
github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be/go.mod h1:mk5IQ+Y0ZeO87b858TlA645sVcEcbiX6YqP98kt+7+w=
|
||||
github.com/containerd/containerd v1.7.16 h1:7Zsfe8Fkj4Wi2My6DXGQ87hiqIrmOXolm72ZEkFU5Mg=
|
||||
github.com/containerd/containerd v1.7.16/go.mod h1:NL49g7A/Fui7ccmxV6zkBWwqMgmMxFWzujYCc+JLt7k=
|
||||
github.com/containerd/containerd v1.7.17 h1:KjNnn0+tAVQHAoaWRjmdak9WlvnFR/8rU1CHHy8Rm2A=
|
||||
github.com/containerd/containerd v1.7.17/go.mod h1:vK+hhT4TIv2uejlcDlbVIc8+h/BqtKLIyNrtCZol8lI=
|
||||
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
||||
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.14.3 h1:OqlDCK3ZVUO6C3B/5FSkDwbkEETK84kQgEeFwDC+62k=
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.14.3/go.mod h1:KY//uOCIkSuNAHhJogcZtrNHdKrA99/FCCRjE3HD36o=
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.15.1 h1:eXJjw9RbkLFgioVaTG+G/ZW/0kEe2oEKCdS/ZxIyoCU=
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.15.1/go.mod h1:gr2RNwukQ/S9Nv33Lt6UC7xEx58C+LHRdoqbEKjz1Kk=
|
||||
github.com/coreos/go-oidc/v3 v3.10.0 h1:tDnXHnLyiTVyT/2zLDGj09pFPkhND8Gl8lnTRhoEaJU=
|
||||
github.com/coreos/go-oidc/v3 v3.10.0/go.mod h1:5j11xcw0D3+SGxn6Z/WFADsgcWVMyNAlSQupk0KK3ac=
|
||||
github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E=
|
||||
@@ -177,10 +181,10 @@ github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHf
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
|
||||
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||
github.com/cyberphone/json-canonicalization v0.0.0-20231011164504-785e29786b46 h1:2Dx4IHfC1yHWI12AxQDJM1QbRCDfk6M+blLzlZCXdrc=
|
||||
github.com/cyberphone/json-canonicalization v0.0.0-20231011164504-785e29786b46/go.mod h1:uzvlm1mxhHkdfqitSA92i7Se+S9ksOn3a3qmv/kyOCw=
|
||||
github.com/danieljoos/wincred v1.2.0 h1:ozqKHaLK0W/ii4KVbbvluM91W2H3Sh0BncbUNPS7jLE=
|
||||
github.com/danieljoos/wincred v1.2.0/go.mod h1:FzQLLMKBFdvu+osBrnFODiv32YGwCfx0SkRa/eYHgec=
|
||||
github.com/cyberphone/json-canonicalization v0.0.0-20231217050601-ba74d44ecf5f h1:eHnXnuK47UlSTOQexbzxAZfekVz6i+LKRdj1CU5DPaM=
|
||||
github.com/cyberphone/json-canonicalization v0.0.0-20231217050601-ba74d44ecf5f/go.mod h1:uzvlm1mxhHkdfqitSA92i7Se+S9ksOn3a3qmv/kyOCw=
|
||||
github.com/danieljoos/wincred v1.2.1 h1:dl9cBrupW8+r5250DYkYxocLeZ1Y4vB1kxgtjxw8GQs=
|
||||
github.com/danieljoos/wincred v1.2.1/go.mod h1:uGaFL9fDn3OLTvzCGulzE+SzjEe5NGlh5FdCcyfPwps=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
@@ -200,14 +204,14 @@ 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/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/docker/cli v24.0.7+incompatible h1:wa/nIwYFW7BVTGa7SWPVyyXU9lgORqUb1xfI36MSkFg=
|
||||
github.com/docker/cli v24.0.7+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/cli v26.1.3+incompatible h1:bUpXT/N0kDE3VUHI2r5VMsYQgi38kYuoC0oL9yt3lqc=
|
||||
github.com/docker/cli v26.1.3+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/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/docker v25.0.5+incompatible h1:UmQydMduGkrD5nQde1mecF/YnSbTOaPeFIeP5C4W+DE=
|
||||
github.com/docker/docker v25.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker-credential-helpers v0.8.0 h1:YQFtbBQb4VrpoPxhFuzEBPQ9E16qz5SpHLS+uswaCp8=
|
||||
github.com/docker/docker-credential-helpers v0.8.0/go.mod h1:UGFXcuoQ5TxPiB54nHOZ32AWRqQdECoh/Mg0AlEYb40=
|
||||
github.com/docker/docker v26.1.3+incompatible h1:lLCzRbrVZrljpVNobJu1J2FHk8V0s4BawoZippkc+xo=
|
||||
github.com/docker/docker v26.1.3+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/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M=
|
||||
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-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
@@ -218,8 +222,8 @@ github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxER
|
||||
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
github.com/emicklei/proto v1.12.1 h1:6n/Z2pZAnBwuhU66Gs8160B8rrrYKo7h2F2sCOnNceE=
|
||||
github.com/emicklei/proto v1.12.1/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A=
|
||||
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
|
||||
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
|
||||
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
|
||||
@@ -246,8 +250,9 @@ github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
||||
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||
github.com/go-openapi/analysis v0.23.0 h1:aGday7OWupfMs+LbmLZG4k0MYXIANxcuBTYUC03zFCU=
|
||||
github.com/go-openapi/analysis v0.23.0/go.mod h1:9mz9ZWaSlV8TvjQHLl2mUW2PbZtemkE8yA5v22ohupo=
|
||||
github.com/go-openapi/errors v0.22.0 h1:c4xY/OLxUBSTiepAg3j/MHuAv5mJhnf53LLMWFB+u/w=
|
||||
@@ -344,13 +349,12 @@ github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY
|
||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
||||
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
|
||||
github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c=
|
||||
github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
|
||||
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
|
||||
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
|
||||
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.5 h1:bJj+Pj19UZMIweq/iie+1u5YCdGrnxCT9yvm0e+Nd5M=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.5/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.6 h1:TwRYfx2z2C4cLbXmT8I5PgP/xmuqASDyiVuGYfs9GZM=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.6/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk=
|
||||
github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc=
|
||||
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
|
||||
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7 h1:UpiO20jno/eV1eVZcxqWnUohyKRe1g8FPV/xH1s/2qs=
|
||||
@@ -387,28 +391,29 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
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/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
|
||||
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
|
||||
github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=
|
||||
github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/letsencrypt/boulder v0.0.0-20231026200631-000cd05d5491 h1:WGrKdjHtWC67RX96eTkYD2f53NDHhrq/7robWTAfk4s=
|
||||
github.com/letsencrypt/boulder v0.0.0-20231026200631-000cd05d5491/go.mod h1:o158RFmdEbYyIZmXAbrvmJWesbyxlLKee6X64VPVuOc=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
||||
github.com/letsencrypt/boulder v0.0.0-20240515153123-6ae6aa8e9055 h1:sl8s8GXv/oHUSid9gd4B+Rovu9DOW4PxnKT2rNRfmzM=
|
||||
github.com/letsencrypt/boulder v0.0.0-20240515153123-6ae6aa8e9055/go.mod h1:wGJPvcZTEexA3UpMx+4cZ19nk6gRrzrdW4jFEPsEqf0=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||
github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae h1:dIZY4ULFcto4tAFlj1FYZl8ztUZ13bdq+PLY+NOfbyI=
|
||||
github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k=
|
||||
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
|
||||
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM=
|
||||
github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4=
|
||||
github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY=
|
||||
github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU=
|
||||
github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
@@ -417,6 +422,8 @@ github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQ
|
||||
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||
github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
|
||||
github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
|
||||
github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc=
|
||||
@@ -433,15 +440,14 @@ github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
github.com/mozillazg/docker-credential-acr-helper v0.3.0 h1:DVWFZ3/O8BP6Ue3iS/Olw+G07u1hCq1EOVCDZZjCIBI=
|
||||
github.com/mozillazg/docker-credential-acr-helper v0.3.0/go.mod h1:cZlu3tof523ujmLuiNUb6JsjtHcNA70u1jitrrdnuyA=
|
||||
github.com/mrjoelkamp/go-tuf/v2 v2.0.1 h1:nDJGPlrU05sirPlA16M1XJiGDqM0zMwguA4cVgCJ9YY=
|
||||
github.com/mrjoelkamp/go-tuf/v2 v2.0.1/go.mod h1:LJo5jrV0LYV0jVSbCjPem6+0zrkPz8FnimzIECzsFDY=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/nozzle/throttler v0.0.0-20180817012639-2ea982251481 h1:Up6+btDp321ZG5/zdSLo48H9Iaq0UQGthrhWC6pCxzE=
|
||||
github.com/nozzle/throttler v0.0.0-20180817012639-2ea982251481/go.mod h1:yKZQO8QE2bHlgozqWDiRVqTFlLQSj30K/6SAK8EeYFw=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY=
|
||||
github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc=
|
||||
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
|
||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
github.com/oleiade/reflections v1.0.1 h1:D1XO3LVEYroYskEsoSiGItp9RUxG6jWnCVvrqH0HHQM=
|
||||
@@ -465,12 +471,12 @@ github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQ
|
||||
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
|
||||
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
|
||||
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
|
||||
github.com/package-url/packageurl-go v0.1.2 h1:0H2DQt6DHd/NeRlVwW4EZ4oEI6Bn40XlNPRqegcxuo4=
|
||||
github.com/package-url/packageurl-go v0.1.2/go.mod h1:uQd4a7Rh3ZsVg5j0lNyAfyxIeGde9yrlhjF78GzeW0c=
|
||||
github.com/package-url/packageurl-go v0.1.3 h1:4juMED3hHiz0set3Vq3KeQ75KD1avthoXLtmE3I0PLs=
|
||||
github.com/package-url/packageurl-go v0.1.3/go.mod h1:nKAWB8E6uk1MHqiS/lQb9pYBGH2+mdJ2PJc2s50dQY0=
|
||||
github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw=
|
||||
github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
|
||||
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
|
||||
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
@@ -478,16 +484,17 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU=
|
||||
github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k=
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
|
||||
github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
|
||||
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
||||
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
||||
github.com/prometheus/common v0.51.1 h1:eIjN50Bwglz6a/c3hAgSMcofL3nD+nFQkV6Dd4DsQCw=
|
||||
github.com/prometheus/common v0.51.1/go.mod h1:lrWtQx+iDfn2mbH5GUzlH9TSHyfZpHkSiG1W7y3sF2Q=
|
||||
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
|
||||
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
|
||||
github.com/prometheus/common v0.53.0 h1:U2pL9w9nmJwJDa4qqLQ3ZaePJ6ZTwt7cMD3AG3+aLCE=
|
||||
github.com/prometheus/common v0.53.0/go.mod h1:BrxBKv3FWBIGXw89Mg1AeBq7FSyRzXWI3l3e7W3RN5U=
|
||||
github.com/prometheus/procfs v0.15.0 h1:A82kmvXJq2jTu5YUhSGNlYoxh85zLnKgPz4bMZgI5Ek=
|
||||
github.com/prometheus/procfs v0.15.0/go.mod h1:Y0RJ/Y5g5wJpkTisOtqwDSo4HwhGmLB4VQSw2sQJLHk=
|
||||
github.com/protocolbuffers/txtpbfmt v0.0.0-20231025115547-084445ff1adf h1:014O62zIzQwvoD7Ekj3ePDF5bv9Xxy0w6AZk0qYbjUk=
|
||||
github.com/protocolbuffers/txtpbfmt v0.0.0-20231025115547-084445ff1adf/go.mod h1:jgxiZysxFPM+iWKwQwPR+y+Jvo54ARd4EisXxKYpB5c=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM=
|
||||
@@ -511,8 +518,8 @@ github.com/segmentio/ksuid v1.0.4 h1:sBo2BdShXjmcugAMwjugoGUdUV0pcxY5mW4xKRn3v4c
|
||||
github.com/segmentio/ksuid v1.0.4/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE=
|
||||
github.com/shibumi/go-pathspec v1.3.0 h1:QUyMZhFo0Md5B8zV8x2tesohbb5kfbpTi9rBnKh5dkI=
|
||||
github.com/shibumi/go-pathspec v1.3.0/go.mod h1:Xutfslp817l2I1cZvgcfeMQJG5QnU2lh5tVaaMCl3jE=
|
||||
github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4=
|
||||
github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM=
|
||||
github.com/shirou/gopsutil/v3 v3.24.4 h1:dEHgzZXt4LMNm+oYELpzl9YCqV65Yr/6SfrvgRBtXeU=
|
||||
github.com/shirou/gopsutil/v3 v3.24.4/go.mod h1:lTd2mdiOspcqLgAnr9/nGi71NkeMpWKdmhuxm9GusH8=
|
||||
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
||||
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
||||
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
|
||||
@@ -556,7 +563,7 @@ github.com/spiffe/go-spiffe/v2 v2.2.0/go.mod h1:Urzb779b3+IwDJD2ZbN8fVl3Aa8G4N/P
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
@@ -572,22 +579,26 @@ github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d h1:vfofYNRScrDd
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48=
|
||||
github.com/tchap/go-patricia/v2 v2.3.1 h1:6rQp39lgIYZ+MHmdEq4xzuk1t7OdC35z/xm0BGhTkes=
|
||||
github.com/tchap/go-patricia/v2 v2.3.1/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k=
|
||||
github.com/testcontainers/testcontainers-go v0.30.0 h1:jmn/XS22q4YRrcMwWg0pAwlClzs/abopbsBzrepyc4E=
|
||||
github.com/testcontainers/testcontainers-go v0.30.0/go.mod h1:K+kHNGiM5zjklKjgTtcrEetF3uhWbMUyqAQoyoh8Pf0=
|
||||
github.com/testcontainers/testcontainers-go/modules/registry v0.30.0 h1:/GYaNnQ09Gvwv3GvhWYbzL2gQiqwzlqDyQZ175uVPC4=
|
||||
github.com/testcontainers/testcontainers-go/modules/registry v0.30.0/go.mod h1:bu2AS7kGxJQgZ16qbb5SHKSuEVrriENPIpKugl0aCHA=
|
||||
github.com/testcontainers/testcontainers-go v0.31.0 h1:W0VwIhcEVhRflwL9as3dhY6jXjVCA27AkmbnZ+UTh3U=
|
||||
github.com/testcontainers/testcontainers-go v0.31.0/go.mod h1:D2lAoA0zUFiSY+eAflqK5mcUx/A5hrrORaEQrd0SefI=
|
||||
github.com/testcontainers/testcontainers-go/modules/registry v0.31.0 h1:QiQb8omImfD5ZWSh0YR0WNrFeRU+j2Cqfd8+dYdLgaE=
|
||||
github.com/testcontainers/testcontainers-go/modules/registry v0.31.0/go.mod h1:rrkCrh2acVVbQw9JfN4DOBm/ODVCIHbveEq+k+HSyfU=
|
||||
github.com/thales-e-security/pool v0.0.2 h1:RAPs4q2EbWsTit6tpzuvTFlgFRJ3S8Evf5gtvVDbmPg=
|
||||
github.com/thales-e-security/pool v0.0.2/go.mod h1:qtpMm2+thHtqhLzTwgDBj/OuNnMpupY8mv0Phz0gjhU=
|
||||
github.com/theupdateframework/go-tuf v0.7.0 h1:CqbQFrWo1ae3/I0UCblSbczevCCbS31Qvs5LdxRWqRI=
|
||||
github.com/theupdateframework/go-tuf v0.7.0/go.mod h1:uEB7WSY+7ZIugK6R1hiBMBjQftaFzn7ZCDJcp1tCUug=
|
||||
github.com/theupdateframework/go-tuf/v2 v2.0.0-20240504210453-5a634eb214ae h1:Cb5/8rY0k9oB+SigleRtEP5BeQ3PZQGX051cFIyBNaM=
|
||||
github.com/theupdateframework/go-tuf/v2 v2.0.0-20240504210453-5a634eb214ae/go.mod h1:LJo5jrV0LYV0jVSbCjPem6+0zrkPz8FnimzIECzsFDY=
|
||||
github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 h1:e/5i7d4oYZ+C1wj2THlRK+oAhjeS/TRQwMfkIuet3w0=
|
||||
github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399/go.mod h1:LdwHTNJT99C5fTAzDz0ud328OgXz+gierycbcIx2fRs=
|
||||
github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
|
||||
github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
|
||||
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
||||
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
||||
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
||||
github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU=
|
||||
github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY=
|
||||
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
|
||||
github.com/tklauser/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYgY=
|
||||
github.com/tklauser/numcpus v0.8.0/go.mod h1:ZJZlAY+dmR4eut8epnzf0u/VwodKmryxR8txiloSqBE=
|
||||
github.com/transparency-dev/merkle v0.0.2 h1:Q9nBoQcZcgPamMkGn7ghV8XiTZ/kRxn1yCG81+twTK4=
|
||||
github.com/transparency-dev/merkle v0.0.2/go.mod h1:pqSy+OXefQ1EDUVmAJ8MUhHB9TXGuzVAT58PqBoHz1A=
|
||||
github.com/vbatts/tar-split v0.11.5 h1:3bHCTIheBm1qFTcgh9oPu+nNBtX+XJIupG/vacinCts=
|
||||
@@ -602,34 +613,35 @@ github.com/yashtewari/glob-intersection v0.2.0 h1:8iuHdN88yYuCzCdjt0gDe+6bAhUwBe
|
||||
github.com/yashtewari/glob-intersection v0.2.0/go.mod h1:LK7pIC3piUjovexikBbJ26Yml7g8xa5bsjfx2v1fwok=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
|
||||
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
github.com/zalando/go-keyring v0.2.3 h1:v9CUu9phlABObO4LPWycf+zwMG7nlbb3t/B5wa97yms=
|
||||
github.com/zalando/go-keyring v0.2.3/go.mod h1:HL4k+OXQfJUWaMnqyuSOc0drfGPX2b51Du6K+MRgZMk=
|
||||
github.com/zeebo/errs v1.3.0 h1:hmiaKqgYZzcVgRL1Vkc1Mn2914BbzB0IBxs+ebeutGs=
|
||||
github.com/zeebo/errs v1.3.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4=
|
||||
go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd80=
|
||||
go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c=
|
||||
go.mongodb.org/mongo-driver v1.15.0 h1:rJCKC8eEliewXjZGf0ddURtl7tTVy1TK3bfl0gkUSLc=
|
||||
go.mongodb.org/mongo-driver v1.15.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c=
|
||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
|
||||
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
|
||||
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 h1:Xs2Ncz0gNihqu9iosIZ5SkBbWo5T8JhhLJFMQL1qmLI=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0/go.mod h1:vy+2G/6NvVMpwGX/NyLqcC41fxepnuKHk16E6IZUcJc=
|
||||
go.opentelemetry.io/otel v1.26.0 h1:LQwgL5s/1W7YiiRwxf03QGnWLb2HW4pLiAhaA5cZXBs=
|
||||
go.opentelemetry.io/otel v1.26.0/go.mod h1:UmLkJHUAidDval2EICqBMbnAd0/m2vmpf/dAM+fvFs4=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 h1:cl5P5/GIfFh4t6xyruOgJP5QiA1pw4fYYdv6nc6CBWw=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0/go.mod h1:zgBdWWAu7oEEMC06MMKc5NLbA/1YDXV1sMpSqEeLQLg=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0 h1:tIqheXEFWAZ7O8A7m+J0aPTmpJN3YQ7qetUAdkkkKpk=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0/go.mod h1:nUeKExfxAQVbiVFn32YXpXZZHZ61Cc3s3Rn1pDBGAb0=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU=
|
||||
go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI=
|
||||
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
|
||||
go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw=
|
||||
go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg=
|
||||
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
|
||||
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
|
||||
go.opentelemetry.io/otel/metric v1.26.0 h1:7S39CLuY5Jgg9CrnA9HHiEjGMF/X2VHvoXGgSllRz30=
|
||||
go.opentelemetry.io/otel/metric v1.26.0/go.mod h1:SY+rHOI4cEawI9a7N1A4nIg/nTQXe1ccCNWYOJUrpX4=
|
||||
go.opentelemetry.io/otel/sdk v1.26.0 h1:Y7bumHf5tAiDlRYFmGqetNcLaVUZmh4iYfmGxtmz7F8=
|
||||
go.opentelemetry.io/otel/sdk v1.26.0/go.mod h1:0p8MXpqLeJ0pzcszQQN4F0S5FVjBLgypeGSngLsmirs=
|
||||
go.opentelemetry.io/otel/trace v1.26.0 h1:1ieeAUb4y0TE26jUFrCIXKpTuVK7uJGN9/Z/2LP5sQA=
|
||||
go.opentelemetry.io/otel/trace v1.26.0/go.mod h1:4iDxvGDQuUkHve82hJJ8UqrwswHYsZuWCBllGV2U2y0=
|
||||
go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
|
||||
go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
|
||||
go.step.sm/crypto v0.44.2 h1:t3p3uQ7raP2jp2ha9P6xkQF85TJZh+87xmjSLaib+jk=
|
||||
@@ -643,31 +655,43 @@ go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
|
||||
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
|
||||
golang.org/x/exp v0.0.0-20231108232855-2478ac86f678 h1:mchzmB1XO2pMaKFRqk/+MV3mgGG96aqaPXaMifQU47w=
|
||||
golang.org/x/exp v0.0.0-20231108232855-2478ac86f678/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
|
||||
golang.org/x/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.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
|
||||
golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
|
||||
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/oauth2 v0.19.0 h1:9+E/EZBCbTLNrbN35fHv/a/d/mOBatymz1zbtQrXpIg=
|
||||
golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
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.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -690,21 +714,33 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
|
||||
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
|
||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@@ -712,8 +748,10 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw=
|
||||
golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw=
|
||||
golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -723,12 +761,12 @@ google.golang.org/api v0.172.0 h1:/1OcMZGPmW1rX2LCu2CmGUD1KXK1+pfzxotxyRUCCdk=
|
||||
google.golang.org/api v0.172.0/go.mod h1:+fJZq6QXWfa9pXhnIzsjx4yI22d4aI9ZpLb58gvXjis=
|
||||
google.golang.org/genproto v0.0.0-20240311173647-c811ad7063a7 h1:ImUcDPHjTrAqNhlOkSocDLfG9rrNHH7w7uoKWPaWZ8s=
|
||||
google.golang.org/genproto v0.0.0-20240311173647-c811ad7063a7/go.mod h1:/3XmxOjePkvmKrHuBy4zNFw7IzxJXtAgdpXi8Ll990U=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240311173647-c811ad7063a7 h1:oqta3O3AnlWbmIE3bFnWbu4bRxZjfbWCp0cKSuZh01E=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240311173647-c811ad7063a7/go.mod h1:VQW3tUculP/D4B+xVCo+VgSq8As6wA9ZjHl//pmk+6s=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
|
||||
google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM=
|
||||
google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 h1:AgADTJarZTBqgjiUzRgfaBchgYB3/WFTC80GPwsMcRI=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0=
|
||||
google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=
|
||||
google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
@@ -737,14 +775,12 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
||||
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/go-jose/go-jose.v2 v2.6.3 h1:nt80fvSDlhKWQgSWyHyy5CfmlQr+asih51R8PTWNKKs=
|
||||
gopkg.in/go-jose/go-jose.v2 v2.6.3/go.mod h1:zzZDPkNNw/c9IE7Z9jr11mBZQhKQTMzoEEIoEdZlFBI=
|
||||
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
|
||||
@@ -6,11 +6,11 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/attest/pkg/attestation"
|
||||
"github.com/docker/attest/pkg/oci"
|
||||
"github.com/docker/attest/pkg/policy"
|
||||
"github.com/docker/attest/pkg/signerverifier"
|
||||
"github.com/docker/attest/pkg/tlog"
|
||||
@@ -58,7 +58,7 @@ func Setup(t *testing.T) (context.Context, dsse.SignerVerifier) {
|
||||
|
||||
var policyEvaluator policy.PolicyEvaluator
|
||||
if USE_MOCK_POLICY {
|
||||
policyEvaluator = GetMockPolicy()
|
||||
policyEvaluator = policy.GetMockPolicy()
|
||||
} else {
|
||||
policyEvaluator = policy.NewRegoEvaluator(true)
|
||||
}
|
||||
@@ -83,26 +83,11 @@ func Setup(t *testing.T) (context.Context, dsse.SignerVerifier) {
|
||||
}
|
||||
|
||||
func GetMockSigner(ctx context.Context) (dsse.SignerVerifier, error) {
|
||||
return signerverifier.GenKeyPair()
|
||||
}
|
||||
|
||||
type MockPolicyEvaluator struct {
|
||||
EvaluateFunc func(ctx context.Context, resolver oci.AttestationResolver, policy []*policy.PolicyFile, input *policy.PolicyInput) error
|
||||
}
|
||||
|
||||
func (pe *MockPolicyEvaluator) Evaluate(ctx context.Context, resolver oci.AttestationResolver, policy []*policy.PolicyFile, input *policy.PolicyInput) error {
|
||||
if pe.EvaluateFunc != nil {
|
||||
return pe.EvaluateFunc(ctx, resolver, policy, input)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetMockPolicy() policy.PolicyEvaluator {
|
||||
return &MockPolicyEvaluator{
|
||||
EvaluateFunc: func(ctx context.Context, resolver oci.AttestationResolver, policy []*policy.PolicyFile, input *policy.PolicyInput) error {
|
||||
return nil
|
||||
},
|
||||
priv, err := os.ReadFile(filepath.Join("..", "..", "test", "testdata", "test-signing-key.pem"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return signerverifier.LoadKeyPair(priv)
|
||||
}
|
||||
|
||||
type AnnotatedStatement struct {
|
||||
@@ -135,7 +120,7 @@ func ExtractAnnotatedStatements(path string, mediaType string) ([]*AnnotatedStat
|
||||
var statements []*AnnotatedStatement
|
||||
|
||||
for _, mf := range mfs2.Manifests {
|
||||
if mf.Annotations["vnd.docker.reference.type"] != "attestation-manifest" {
|
||||
if mf.Annotations[attestation.DockerReferenceType] != "attestation-manifest" {
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
@@ -5,14 +5,11 @@ import (
|
||||
"encoding/hex"
|
||||
)
|
||||
|
||||
func HexHashBytes(input []byte) string {
|
||||
s256 := sha256.New()
|
||||
s256.Write(input)
|
||||
hashSum := s256.Sum(nil)
|
||||
return hex.EncodeToString(hashSum)
|
||||
func SHA256Hex(input []byte) string {
|
||||
return hex.EncodeToString(SHA256(input))
|
||||
}
|
||||
|
||||
func S256(data []byte) []byte {
|
||||
func SHA256(data []byte) []byte {
|
||||
h := sha256.Sum256(data)
|
||||
return h[:]
|
||||
}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
package util
|
||||
|
||||
func StringInSlice(str string, list []string) bool {
|
||||
for _, v := range list {
|
||||
if v == str {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
68
pkg/attest/example_sign_test.go
Normal file
68
pkg/attest/example_sign_test.go
Normal file
@@ -0,0 +1,68 @@
|
||||
package attest_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/docker/attest/pkg/attest"
|
||||
"github.com/docker/attest/pkg/mirror"
|
||||
"github.com/docker/attest/pkg/oci"
|
||||
"github.com/docker/attest/pkg/signerverifier"
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/google/go-containerregistry/pkg/v1/empty"
|
||||
"github.com/google/go-containerregistry/pkg/v1/mutate"
|
||||
)
|
||||
|
||||
func ExampleSign_remote() {
|
||||
// configure signerverifier
|
||||
// local signer (unsafe for production)
|
||||
signer, err := signerverifier.GenKeyPair()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// example using AWS KMS signer
|
||||
// aws_arn := "arn:aws:kms:us-west-2:123456789012:key/12345678-1234-1234-1234-123456789012"
|
||||
// aws_region := "us-west-2"
|
||||
// signer, err := signerverifier.GetAWSSigner(cmd.Context(), aws_arn, aws_region)
|
||||
|
||||
// configure signing options
|
||||
opts := &attest.SigningOptions{
|
||||
Replace: true, // replace unsigned intoto statements with signed intoto attestations, otherwise leave in place
|
||||
}
|
||||
|
||||
// load image index with unsigned attestation-manifests
|
||||
ref := "docker/image-signer-verifier:latest"
|
||||
att, err := oci.AttestationIndexFromRemote(ref)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// example for local image index
|
||||
// path := "/myimage"
|
||||
// att, err := oci.AttestationIndexFromLocal(path)
|
||||
|
||||
// sign attestations
|
||||
signedImageIndex, err := attest.Sign(context.Background(), att.Index, signer, opts)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// push image index with signed attestation-manifests
|
||||
err = mirror.PushToRegistry(signedImageIndex, ref)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// output image index to filesystem (optional)
|
||||
path := "/myimage"
|
||||
idx := v1.ImageIndex(empty.Index)
|
||||
idx = mutate.AppendManifests(idx, mutate.IndexAddendum{
|
||||
Add: signedImageIndex,
|
||||
Descriptor: v1.Descriptor{
|
||||
Annotations: map[string]string{
|
||||
oci.OciReferenceTarget: att.Name,
|
||||
},
|
||||
},
|
||||
})
|
||||
err = mirror.SaveAsOCILayout(idx, path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
74
pkg/attest/example_verify_test.go
Normal file
74
pkg/attest/example_verify_test.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package attest_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/docker/attest/internal/embed"
|
||||
"github.com/docker/attest/pkg/attest"
|
||||
"github.com/docker/attest/pkg/oci"
|
||||
"github.com/docker/attest/pkg/policy"
|
||||
"github.com/docker/attest/pkg/tuf"
|
||||
)
|
||||
|
||||
func createTufClient(outputPath string) (*tuf.TufClient, error) {
|
||||
// using oci tuf metadata and targets
|
||||
metadataURI := "registry-1.docker.io/docker/tuf-metadata:latest"
|
||||
targetsURI := "registry-1.docker.io/docker/tuf-targets"
|
||||
// example using http tuf metadata and targets
|
||||
// metadataURI := "https://docker.github.io/tuf-staging/metadata"
|
||||
// targetsURI := "https://docker.github.io/tuf-staging/targets"
|
||||
|
||||
return tuf.NewTufClient(embed.StagingRoot, outputPath, metadataURI, targetsURI, tuf.NewMockVersionChecker())
|
||||
}
|
||||
|
||||
func ExampleVerify_remote() {
|
||||
// create a tuf client
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
tufOutputPath := filepath.Join(home, ".docker", "tuf")
|
||||
tufClient, err := createTufClient(tufOutputPath)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// create a resolver for remote attestations
|
||||
image := "registry-1.docker.io/library/notary:server"
|
||||
platform := "linux/amd64"
|
||||
resolver := &oci.RegistryResolver{
|
||||
Image: image, // path to image index in OCI registry containing image attestations
|
||||
Platform: platform, // platform of subject image (image that attestations are being verified against)
|
||||
}
|
||||
// example using a local resolver
|
||||
// path := "/myimage"
|
||||
// platform := "linux/amd64"
|
||||
// resolver := &oci.OCILayoutResolver{
|
||||
// Path: path, // file path to OCI layout containing image attestations
|
||||
// Platform: platform, // platform of subject image (image that attestations are being verified against)
|
||||
// }
|
||||
|
||||
// configure policy options
|
||||
opts := &policy.PolicyOptions{
|
||||
TufClient: tufClient,
|
||||
LocalTargetsDir: filepath.Join(home, ".docker", "policy"), // location to store policy files downloaded from TUF
|
||||
LocalPolicyDir: "", // overrides TUF policy for local policy files if set
|
||||
}
|
||||
|
||||
// verify attestations
|
||||
result, err := attest.Verify(context.Background(), opts, resolver)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
switch result.Outcome {
|
||||
case attest.OutcomeSuccess:
|
||||
fmt.Println("policy passed")
|
||||
case attest.OutcomeNoPolicy:
|
||||
fmt.Println("no policy for image")
|
||||
case attest.OutcomeFailure:
|
||||
fmt.Println("policy failed")
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/docker/attest/pkg/attestation"
|
||||
"github.com/docker/attest/pkg/oci"
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/google/go-containerregistry/pkg/v1/empty"
|
||||
"github.com/google/go-containerregistry/pkg/v1/match"
|
||||
@@ -26,45 +27,98 @@ func Sign(ctx context.Context, idx v1.ImageIndex, signer dsse.SignerVerifier, op
|
||||
|
||||
// sign every attestation layer in each manifest
|
||||
for _, manifest := range attestationManifests {
|
||||
attestationLayers, err := attestation.GetAttestationsFromImage(manifest.Attestation.Image)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get attestations from image: %w", err)
|
||||
}
|
||||
signedLayers, err := signLayers(ctx, attestationLayers, signer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to sign attestations: %w", err)
|
||||
}
|
||||
if opts.VSAOptions != nil {
|
||||
newLayer, err := generateVSA(ctx, manifest, signer, opts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate VSA: %w", err)
|
||||
}
|
||||
signedLayers = append(signedLayers, *newLayer)
|
||||
}
|
||||
newImg, err := addSignedLayers(signedLayers, manifest, opts)
|
||||
idx, err = signLayersAndAddToIndex(ctx, idx, manifest.Attestation.Layers, manifest, signer, opts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to add signed layers: %w", err)
|
||||
}
|
||||
newDesc, err := partial.Descriptor(newImg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get descriptor: %w", err)
|
||||
}
|
||||
cf, err := manifest.Attestation.Image.ConfigFile()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get config file: %w", err)
|
||||
}
|
||||
newDesc.Platform = cf.Platform()
|
||||
newDesc.MediaType = manifest.MediaType
|
||||
newDesc.Annotations = manifest.Annotations
|
||||
idx = mutate.RemoveManifests(idx, match.Digests(manifest.Digest))
|
||||
idx = mutate.AppendManifests(idx, mutate.IndexAddendum{
|
||||
Add: newImg,
|
||||
Descriptor: *newDesc,
|
||||
})
|
||||
}
|
||||
return idx, nil
|
||||
}
|
||||
|
||||
func AddAttestation(ctx context.Context, idx v1.ImageIndex, statement *intoto.Statement, signer dsse.SignerVerifier) (v1.ImageIndex, error) {
|
||||
if len(statement.Subject) == 0 {
|
||||
return nil, fmt.Errorf("statement has no subjects")
|
||||
}
|
||||
|
||||
subjectDigests := make(map[string]bool)
|
||||
for _, subject := range statement.Subject {
|
||||
subjectDigest := fmt.Sprintf("sha256:%s", subject.Digest["sha256"])
|
||||
subjectDigests[subjectDigest] = true
|
||||
}
|
||||
|
||||
attestationManifests, err := attestation.GetAttestationManifestsFromIndex(idx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get attestation manifests: %w", err)
|
||||
}
|
||||
updatedIndex := false
|
||||
for _, manifest := range attestationManifests {
|
||||
if subjectDigests[manifest.Annotations[oci.DockerReferenceDigest]] {
|
||||
attestationLayers := []attestation.AttestationLayer{
|
||||
{
|
||||
Statement: statement,
|
||||
MediaType: types.MediaType(intoto.PayloadType),
|
||||
Annotations: map[string]string{
|
||||
oci.InTotoPredicateType: statement.PredicateType,
|
||||
},
|
||||
},
|
||||
}
|
||||
// hard-coding replace to false here, because if it's true we will remove any unsigned statements, even unrelated ones
|
||||
idx, err = signLayersAndAddToIndex(ctx, idx, attestationLayers, manifest, signer, &SigningOptions{Replace: false})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to add signed layers: %w", err)
|
||||
}
|
||||
updatedIndex = true
|
||||
}
|
||||
}
|
||||
if !updatedIndex {
|
||||
return nil, fmt.Errorf("no attestation manifest found for statement")
|
||||
}
|
||||
return idx, nil
|
||||
|
||||
}
|
||||
|
||||
func signLayersAndAddToIndex(
|
||||
ctx context.Context,
|
||||
idx v1.ImageIndex,
|
||||
attestationLayers []attestation.AttestationLayer,
|
||||
manifest attestation.AttestationManifest,
|
||||
signer dsse.SignerVerifier,
|
||||
opts *SigningOptions) (v1.ImageIndex, error) {
|
||||
|
||||
signedLayers, err := signLayers(ctx, attestationLayers, signer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to sign attestations: %w", err)
|
||||
}
|
||||
|
||||
newImg, err := addSignedLayers(signedLayers, manifest, opts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to add signed layers: %w", err)
|
||||
}
|
||||
newDesc, err := partial.Descriptor(newImg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get descriptor: %w", err)
|
||||
}
|
||||
cf, err := manifest.Attestation.Image.ConfigFile()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get config file: %w", err)
|
||||
}
|
||||
newDesc.Platform = cf.Platform()
|
||||
if newDesc.Platform == nil {
|
||||
newDesc.Platform = &v1.Platform{
|
||||
Architecture: "unknown",
|
||||
OS: "unknown",
|
||||
}
|
||||
}
|
||||
newDesc.MediaType = manifest.MediaType
|
||||
newDesc.Annotations = manifest.Annotations
|
||||
idx = mutate.RemoveManifests(idx, match.Digests(manifest.Digest))
|
||||
idx = mutate.AppendManifests(idx, mutate.IndexAddendum{
|
||||
Add: newImg,
|
||||
Descriptor: *newDesc,
|
||||
})
|
||||
return idx, nil
|
||||
}
|
||||
|
||||
// signLayers signs each intoto attestation layer with the given signer
|
||||
func signLayers(ctx context.Context, layers []attestation.AttestationLayer, signer dsse.SignerVerifier) ([]mutate.Addendum, error) {
|
||||
var signedLayers []mutate.Addendum
|
||||
@@ -77,11 +131,7 @@ func signLayers(ctx context.Context, layers []attestation.AttestationLayer, sign
|
||||
layer.Annotations[InTotoReferenceLifecycleStage] = LifecycleStageExperimental
|
||||
|
||||
// sign the statement
|
||||
payload, err := json.Marshal(layer.Statement)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal statement: %w", err)
|
||||
}
|
||||
env, err := attestation.SignDSSE(ctx, payload, intoto.PayloadType, signer)
|
||||
env, err := signInTotoStatement(ctx, layer.Statement, signer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to sign statement: %w", err)
|
||||
}
|
||||
@@ -103,6 +153,18 @@ func signLayers(ctx context.Context, layers []attestation.AttestationLayer, sign
|
||||
return signedLayers, nil
|
||||
}
|
||||
|
||||
func signInTotoStatement(ctx context.Context, statement *intoto.Statement, signer dsse.SignerVerifier) (*attestation.Envelope, error) {
|
||||
payload, err := json.Marshal(statement)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal statement: %w", err)
|
||||
}
|
||||
env, err := attestation.SignDSSE(ctx, payload, intoto.PayloadType, signer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to sign statement: %w", err)
|
||||
}
|
||||
return env, nil
|
||||
}
|
||||
|
||||
// addSignedLayers adds signed layers to a new or existing attestation image
|
||||
func addSignedLayers(signedLayers []mutate.Addendum, manifest attestation.AttestationManifest, opts *SigningOptions) (v1.Image, error) {
|
||||
var err error
|
||||
|
||||
@@ -2,6 +2,7 @@ package attest
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
@@ -18,12 +19,14 @@ import (
|
||||
intoto "github.com/in-toto/in-toto-golang/in_toto"
|
||||
v02 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var (
|
||||
UnsignedTestImage = filepath.Join("..", "..", "test", "testdata", "unsigned-test-image")
|
||||
NoProvenanceImage = filepath.Join("..", "..", "test", "testdata", "no-provenance-image")
|
||||
LocalPolicyDir = filepath.Join("..", "..", "test", "testdata", "local-policy")
|
||||
PassPolicyDir = filepath.Join("..", "..", "test", "testdata", "local-policy-pass")
|
||||
FailPolicyDir = filepath.Join("..", "..", "test", "testdata", "local-policy-fail")
|
||||
TestTempDir = "attest-sign-test"
|
||||
)
|
||||
|
||||
@@ -38,31 +41,25 @@ func TestSignVerifyOCILayout(t *testing.T) {
|
||||
replace bool
|
||||
}{
|
||||
|
||||
{"signed replaced (does nothing)", UnsignedTestImage, 0, 6, true},
|
||||
{"without replace", UnsignedTestImage, 4, 6, false},
|
||||
{"signed replaced (does nothing)", UnsignedTestImage, 0, 4, true},
|
||||
{"without replace", UnsignedTestImage, 4, 4, false},
|
||||
// image without provenance doesn't fail
|
||||
{"no provenance (replace)", NoProvenanceImage, 0, 4, true},
|
||||
{"no provenance (no replace)", NoProvenanceImage, 2, 4, false},
|
||||
{"no provenance (replace)", NoProvenanceImage, 0, 2, true},
|
||||
{"no provenance (no replace)", NoProvenanceImage, 2, 2, false},
|
||||
}
|
||||
policyResolver := &policy.PolicyOptions{
|
||||
LocalPolicyDir: LocalPolicyDir,
|
||||
LocalPolicyDir: PassPolicyDir,
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
tempDir := test.CreateTempDir(t, "", TestTempDir)
|
||||
outputLayout := tempDir
|
||||
outputLayout := test.CreateTempDir(t, "", TestTempDir)
|
||||
opts := &SigningOptions{
|
||||
Replace: tc.replace,
|
||||
VSAOptions: &attestation.VSAOptions{
|
||||
BuildLevel: "SLSA_BUILD_LEVEL_3",
|
||||
PolicyURI: "https://docker.com/attest/policy",
|
||||
VerifierID: "https://docker.com",
|
||||
},
|
||||
}
|
||||
attIdx, err := oci.AttestationIndexFromPath(tc.TestImage)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
signedIndex, err := Sign(ctx, attIdx.Index, signer, opts)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
|
||||
// output signed attestations
|
||||
idx := v1.ImageIndex(empty.Index)
|
||||
@@ -75,25 +72,21 @@ func TestSignVerifyOCILayout(t *testing.T) {
|
||||
},
|
||||
})
|
||||
_, err = layout.Write(outputLayout, idx)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
|
||||
resolver := &oci.OCILayoutResolver{
|
||||
Path: outputLayout,
|
||||
Platform: "",
|
||||
}
|
||||
policy, err := Verify(ctx, policyResolver, resolver)
|
||||
assert.NoError(t, err)
|
||||
assert.Truef(t, policy, "Policy should have been found")
|
||||
require.NoError(t, err)
|
||||
assert.Equalf(t, OutcomeSuccess, policy.Outcome, "Policy should have been found")
|
||||
|
||||
mt, _ := attestation.DSSEMediaType(attestation.VSAPredicateType)
|
||||
vsas, err := test.ExtractAnnotatedStatements(tempDir, mt)
|
||||
assert.NoError(t, err)
|
||||
assert.Equalf(t, len(vsas), 2, "expected %d vsa statement, got %d", 2, len(vsas))
|
||||
var allEnvelopes []*test.AnnotatedStatement
|
||||
for _, predicate := range []string{intoto.PredicateSPDX, v02.PredicateSLSAProvenance, attestation.VSAPredicateType} {
|
||||
mt, _ := attestation.DSSEMediaType(predicate)
|
||||
statements, err := test.ExtractAnnotatedStatements(tempDir, mt)
|
||||
assert.NoError(t, err)
|
||||
statements, err := test.ExtractAnnotatedStatements(outputLayout, mt)
|
||||
require.NoError(t, err)
|
||||
allEnvelopes = append(allEnvelopes, statements...)
|
||||
|
||||
for _, stmt := range statements {
|
||||
@@ -102,13 +95,77 @@ func TestSignVerifyOCILayout(t *testing.T) {
|
||||
}
|
||||
}
|
||||
assert.Equalf(t, tc.expectedAttestations, len(allEnvelopes), "expected %d attestations, got %d", tc.expectedAttestations, len(allEnvelopes))
|
||||
statements, err := test.ExtractAnnotatedStatements(tempDir, intoto.PayloadType)
|
||||
assert.NoError(t, err)
|
||||
statements, err := test.ExtractAnnotatedStatements(outputLayout, intoto.PayloadType)
|
||||
require.NoError(t, err)
|
||||
assert.Equalf(t, tc.expectedStatements, len(statements), "expected %d statement, got %d", tc.expectedStatements, len(statements))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddAttestation(t *testing.T) {
|
||||
ctx, signer := test.Setup(t)
|
||||
|
||||
expectedAttestations := 2
|
||||
expectedStatements := 4
|
||||
|
||||
outputLayout := test.CreateTempDir(t, "", TestTempDir)
|
||||
attIdx, err := oci.AttestationIndexFromPath(UnsignedTestImage)
|
||||
require.NoError(t, err)
|
||||
|
||||
statementToAdd := &intoto.Statement{
|
||||
StatementHeader: intoto.StatementHeader{
|
||||
PredicateType: attestation.VSAPredicateType,
|
||||
Type: intoto.StatementInTotoV01,
|
||||
Subject: []intoto.Subject{
|
||||
{
|
||||
Name: attIdx.Name,
|
||||
Digest: map[string]string{
|
||||
"sha256": "da8b190665956ea07890a0273e2a9c96bfe291662f08e2860e868eef69c34620",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: attIdx.Name,
|
||||
Digest: map[string]string{
|
||||
"sha256": "7a76cec943853f9f7105b1976afa1bf7cd5bb6afc4e9d5852dd8da7cf81ae86e",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
signedIndex, err := AddAttestation(ctx, attIdx.Index, statementToAdd, signer)
|
||||
require.NoError(t, err)
|
||||
|
||||
// output signed attestations
|
||||
idx := v1.ImageIndex(empty.Index)
|
||||
idx = mutate.AppendManifests(idx, mutate.IndexAddendum{
|
||||
Add: signedIndex,
|
||||
Descriptor: v1.Descriptor{
|
||||
Annotations: map[string]string{
|
||||
oci.OciReferenceTarget: attIdx.Name,
|
||||
},
|
||||
},
|
||||
})
|
||||
_, err = layout.Write(outputLayout, idx)
|
||||
require.NoError(t, err)
|
||||
|
||||
var allEnvelopes []*test.AnnotatedStatement
|
||||
mt, _ := attestation.DSSEMediaType(attestation.VSAPredicateType)
|
||||
statements, err := test.ExtractAnnotatedStatements(outputLayout, mt)
|
||||
require.NoError(t, err)
|
||||
allEnvelopes = append(allEnvelopes, statements...)
|
||||
|
||||
for _, stmt := range statements {
|
||||
assert.Equalf(t, attestation.VSAPredicateType, stmt.Annotations[oci.InTotoPredicateType], "expected predicate-type annotation to be set to %s, got %s", attestation.VSAPredicateType, stmt.Annotations[oci.InTotoPredicateType])
|
||||
assert.Equalf(t, LifecycleStageExperimental, stmt.Annotations[InTotoReferenceLifecycleStage], "expected reference lifecycle stage annotation to be set to %s, got %s", LifecycleStageExperimental, stmt.Annotations[InTotoReferenceLifecycleStage])
|
||||
}
|
||||
assert.Equalf(t, expectedAttestations, len(allEnvelopes), "expected %d attestations, got %d", expectedAttestations, len(allEnvelopes))
|
||||
statements, err = test.ExtractAnnotatedStatements(outputLayout, intoto.PayloadType)
|
||||
fmt.Printf("statements: %+v\n", statements)
|
||||
require.NoError(t, err)
|
||||
assert.Equalf(t, expectedStatements, len(statements), "expected %d statement, got %d", expectedStatements, len(statements))
|
||||
}
|
||||
|
||||
func TestAddSignedLayerAnnotations(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
@@ -147,7 +204,7 @@ func TestAddSignedLayerAnnotations(t *testing.T) {
|
||||
},
|
||||
}
|
||||
newImg, err := addSignedLayers(signedLayers, manifest, opts)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
mf, _ := newImg.RawManifest()
|
||||
type Annotations struct {
|
||||
Annotations map[string]string `json:"annotations"`
|
||||
@@ -157,7 +214,7 @@ func TestAddSignedLayerAnnotations(t *testing.T) {
|
||||
}
|
||||
l := &Layers{}
|
||||
err = json.Unmarshal(mf, l)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
_, ok := l.Layers[0].Annotations["test"]
|
||||
assert.Truef(t, ok, "missing annotations")
|
||||
})
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package attest
|
||||
|
||||
import (
|
||||
"github.com/docker/attest/pkg/attestation"
|
||||
"fmt"
|
||||
|
||||
"github.com/docker/attest/pkg/policy"
|
||||
intoto "github.com/in-toto/in-toto-golang/in_toto"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -10,6 +13,32 @@ const (
|
||||
)
|
||||
|
||||
type SigningOptions struct {
|
||||
Replace bool
|
||||
VSAOptions *attestation.VSAOptions
|
||||
Replace bool
|
||||
}
|
||||
|
||||
type Outcome string
|
||||
|
||||
const (
|
||||
OutcomeSuccess Outcome = "success"
|
||||
OutcomeFailure Outcome = "failure"
|
||||
OutcomeNoPolicy Outcome = "no_policy"
|
||||
)
|
||||
|
||||
func (o Outcome) StringForVSA() (string, error) {
|
||||
switch o {
|
||||
case OutcomeSuccess:
|
||||
return "PASSED", nil
|
||||
case OutcomeFailure:
|
||||
return "FAILED", nil
|
||||
default:
|
||||
return "", fmt.Errorf("unknown outcome: %s", o)
|
||||
}
|
||||
}
|
||||
|
||||
type VerificationResult struct {
|
||||
Outcome Outcome
|
||||
Policy *policy.Policy
|
||||
Input *policy.PolicyInput
|
||||
VSA *intoto.Statement
|
||||
Violations []policy.Violation
|
||||
}
|
||||
|
||||
@@ -3,23 +3,95 @@ package attest
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/docker/attest/pkg/attestation"
|
||||
"github.com/docker/attest/pkg/oci"
|
||||
"github.com/docker/attest/pkg/policy"
|
||||
intoto "github.com/in-toto/in-toto-golang/in_toto"
|
||||
)
|
||||
|
||||
func VerifyAttestations(ctx context.Context, resolver oci.AttestationResolver, files []*policy.PolicyFile) error {
|
||||
func Verify(ctx context.Context, opts *policy.PolicyOptions, resolver oci.AttestationResolver) (result *VerificationResult, err error) {
|
||||
pctx, err := policy.ResolvePolicy(ctx, resolver, opts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to resolve policy: %w", err)
|
||||
}
|
||||
|
||||
if pctx == nil {
|
||||
return &VerificationResult{
|
||||
Outcome: OutcomeNoPolicy,
|
||||
}, nil
|
||||
}
|
||||
|
||||
result, err = VerifyAttestations(ctx, resolver, pctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to evaluate policy: %w", err)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func ToPolicyResult(p *policy.Policy, input *policy.PolicyInput, result *policy.Result) (*VerificationResult, error) {
|
||||
dgst, err := oci.SplitDigest(input.Digest)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to split digest: %w", err)
|
||||
}
|
||||
subject := intoto.Subject{
|
||||
Name: input.Purl,
|
||||
Digest: *dgst,
|
||||
}
|
||||
resourceUri, err := attestation.ToVSAResourceURI(subject)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create resource uri: %w", err)
|
||||
}
|
||||
|
||||
var outcome Outcome
|
||||
if result.Success {
|
||||
outcome = OutcomeSuccess
|
||||
} else {
|
||||
outcome = OutcomeFailure
|
||||
}
|
||||
|
||||
outcomeStr, err := outcome.StringForVSA()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &VerificationResult{
|
||||
Policy: p,
|
||||
Outcome: outcome,
|
||||
Violations: result.Violations,
|
||||
VSA: &intoto.Statement{
|
||||
StatementHeader: intoto.StatementHeader{
|
||||
PredicateType: attestation.VSAPredicateType,
|
||||
Type: intoto.StatementInTotoV01,
|
||||
Subject: result.Summary.Subjects,
|
||||
},
|
||||
Predicate: attestation.VSAPredicate{
|
||||
Verifier: attestation.VSAVerifier{
|
||||
ID: result.Summary.Verifier,
|
||||
},
|
||||
TimeVerified: time.Now().UTC().Format(time.RFC3339),
|
||||
ResourceUri: resourceUri,
|
||||
Policy: attestation.VSAPolicy{URI: result.Summary.PolicyURI},
|
||||
VerificationResult: outcomeStr,
|
||||
VerifiedLevels: result.Summary.SLSALevels,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func VerifyAttestations(ctx context.Context, resolver oci.AttestationResolver, pctx *policy.Policy) (*VerificationResult, error) {
|
||||
digest, err := resolver.ImageDigest(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get image digest: %w", err)
|
||||
return nil, fmt.Errorf("failed to get image digest: %w", err)
|
||||
}
|
||||
name, err := resolver.ImageName(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get image name: %w", err)
|
||||
return nil, fmt.Errorf("failed to get image name: %w", err)
|
||||
}
|
||||
purl, canonical, err := oci.RefToPURL(name, resolver.ImagePlatformStr())
|
||||
if err != nil {
|
||||
return 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{
|
||||
Digest: digest,
|
||||
@@ -29,27 +101,11 @@ func VerifyAttestations(ctx context.Context, resolver oci.AttestationResolver, f
|
||||
|
||||
evaluator, err := policy.GetPolicyEvaluator(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
err = evaluator.Evaluate(ctx, resolver, files, input)
|
||||
result, err := evaluator.Evaluate(ctx, resolver, pctx, input)
|
||||
if err != nil {
|
||||
return fmt.Errorf("policy evaluation failed: %w", err)
|
||||
return nil, fmt.Errorf("policy evaluation failed: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func Verify(ctx context.Context, opts *policy.PolicyOptions, resolver oci.AttestationResolver) (policyFound bool, err error) {
|
||||
policyFiles, err := policy.ResolvePolicy(ctx, resolver, opts)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to resolve policy: %w", err)
|
||||
}
|
||||
|
||||
// no policy for image -> success
|
||||
if policyFiles == nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// policy found -> verify
|
||||
return true, VerifyAttestations(ctx, resolver, policyFiles)
|
||||
return ToPolicyResult(pctx, input, result)
|
||||
}
|
||||
|
||||
@@ -12,7 +12,13 @@ import (
|
||||
"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/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"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -42,20 +48,136 @@ func TestVerifyAttestations(t *testing.T) {
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
|
||||
mockPE := test.MockPolicyEvaluator{
|
||||
EvaluateFunc: func(ctx context.Context, resolver oci.AttestationResolver, pfs []*policy.PolicyFile, input *policy.PolicyInput) error {
|
||||
return tc.policyEvaluationError
|
||||
mockPE := policy.MockPolicyEvaluator{
|
||||
EvaluateFunc: func(ctx context.Context, resolver oci.AttestationResolver, pctx *policy.Policy, input *policy.PolicyInput) (*policy.Result, error) {
|
||||
return policy.AllowedResult(), tc.policyEvaluationError
|
||||
},
|
||||
}
|
||||
|
||||
ctx := policy.WithPolicyEvaluator(context.Background(), &mockPE)
|
||||
err = VerifyAttestations(ctx, resolver, nil)
|
||||
_, err := VerifyAttestations(ctx, resolver, nil)
|
||||
if tc.expectedError != nil {
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, tc.expectedError.Error(), err.Error())
|
||||
if assert.Error(t, err) {
|
||||
assert.Equal(t, tc.expectedError.Error(), err.Error())
|
||||
}
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestVSA(t *testing.T) {
|
||||
ctx, signer := test.Setup(t)
|
||||
ctx = policy.WithPolicyEvaluator(ctx, policy.NewRegoEvaluator(true))
|
||||
// setup an image with signed attestations
|
||||
outputLayout := test.CreateTempDir(t, "", TestTempDir)
|
||||
|
||||
opts := &SigningOptions{
|
||||
Replace: true,
|
||||
}
|
||||
attIdx, err := oci.AttestationIndexFromPath(UnsignedTestImage)
|
||||
assert.NoError(t, err)
|
||||
signedIndex, err := Sign(ctx, attIdx.Index, signer, opts)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// output signed attestations
|
||||
idx := v1.ImageIndex(empty.Index)
|
||||
idx = mutate.AppendManifests(idx, mutate.IndexAddendum{
|
||||
Add: signedIndex,
|
||||
Descriptor: v1.Descriptor{
|
||||
Annotations: map[string]string{
|
||||
oci.OciReferenceTarget: attIdx.Name,
|
||||
},
|
||||
},
|
||||
})
|
||||
_, err = layout.Write(outputLayout, idx)
|
||||
assert.NoError(t, err)
|
||||
|
||||
//verify (without vsa should fail)
|
||||
resolver := &oci.OCILayoutResolver{
|
||||
Path: outputLayout,
|
||||
Platform: "linux/amd64",
|
||||
}
|
||||
|
||||
// mocked vsa query should pass
|
||||
policyOpts := &policy.PolicyOptions{
|
||||
LocalPolicyDir: PassPolicyDir,
|
||||
}
|
||||
results, err := Verify(ctx, policyOpts, resolver)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, OutcomeSuccess, results.Outcome)
|
||||
assert.Empty(t, results.Violations)
|
||||
|
||||
assert.Equal(t, intoto.StatementInTotoV01, results.VSA.Type)
|
||||
assert.Equal(t, attestation.VSAPredicateType, results.VSA.PredicateType)
|
||||
assert.Len(t, results.VSA.Subject, 1)
|
||||
|
||||
require.IsType(t, attestation.VSAPredicate{}, results.VSA.Predicate)
|
||||
attestationPredicate := results.VSA.Predicate.(attestation.VSAPredicate)
|
||||
|
||||
assert.Equal(t, "PASSED", attestationPredicate.VerificationResult)
|
||||
assert.Equal(t, "docker-official-images", attestationPredicate.Verifier.ID)
|
||||
assert.Equal(t, []string{"SLSA_BUILD_LEVEL_3"}, attestationPredicate.VerifiedLevels)
|
||||
assert.Equal(t, "https://docker.com/official/policy/v0.1", attestationPredicate.Policy.URI)
|
||||
}
|
||||
|
||||
func TestVerificationFailure(t *testing.T) {
|
||||
ctx, signer := test.Setup(t)
|
||||
ctx = policy.WithPolicyEvaluator(ctx, policy.NewRegoEvaluator(true))
|
||||
// setup an image with signed attestations
|
||||
outputLayout := test.CreateTempDir(t, "", TestTempDir)
|
||||
|
||||
opts := &SigningOptions{
|
||||
Replace: true,
|
||||
}
|
||||
attIdx, err := oci.AttestationIndexFromPath(UnsignedTestImage)
|
||||
assert.NoError(t, err)
|
||||
signedIndex, err := Sign(ctx, attIdx.Index, signer, opts)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// output signed attestations
|
||||
idx := v1.ImageIndex(empty.Index)
|
||||
idx = mutate.AppendManifests(idx, mutate.IndexAddendum{
|
||||
Add: signedIndex,
|
||||
Descriptor: v1.Descriptor{
|
||||
Annotations: map[string]string{
|
||||
oci.OciReferenceTarget: attIdx.Name,
|
||||
},
|
||||
},
|
||||
})
|
||||
_, err = layout.Write(outputLayout, idx)
|
||||
assert.NoError(t, err)
|
||||
|
||||
//verify (without vsa should fail)
|
||||
resolver := &oci.OCILayoutResolver{
|
||||
Path: outputLayout,
|
||||
Platform: "linux/amd64",
|
||||
}
|
||||
|
||||
// mocked vsa query should pass
|
||||
policyOpts := &policy.PolicyOptions{
|
||||
LocalPolicyDir: FailPolicyDir,
|
||||
}
|
||||
results, err := Verify(ctx, policyOpts, resolver)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, OutcomeFailure, results.Outcome)
|
||||
assert.Len(t, results.Violations, 1)
|
||||
|
||||
violation := results.Violations[0]
|
||||
assert.Equal(t, "missing_attestation", violation.Type)
|
||||
assert.Equal(t, "Attestation missing for subject", violation.Description)
|
||||
assert.Nil(t, violation.Attestation)
|
||||
|
||||
assert.Equal(t, intoto.StatementInTotoV01, results.VSA.Type)
|
||||
assert.Equal(t, attestation.VSAPredicateType, results.VSA.PredicateType)
|
||||
assert.Len(t, results.VSA.Subject, 1)
|
||||
|
||||
require.IsType(t, attestation.VSAPredicate{}, results.VSA.Predicate)
|
||||
attestationPredicate := results.VSA.Predicate.(attestation.VSAPredicate)
|
||||
|
||||
assert.Equal(t, "FAILED", attestationPredicate.VerificationResult)
|
||||
assert.Equal(t, "docker-official-images", attestationPredicate.Verifier.ID)
|
||||
assert.Equal(t, []string{"SLSA_BUILD_LEVEL_3"}, attestationPredicate.VerifiedLevels)
|
||||
assert.Equal(t, "https://docker.com/official/policy/v0.1", attestationPredicate.Policy.URI)
|
||||
}
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
package attest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/attest/pkg/attestation"
|
||||
"github.com/docker/attest/pkg/oci"
|
||||
"github.com/google/go-containerregistry/pkg/v1/mutate"
|
||||
"github.com/google/go-containerregistry/pkg/v1/static"
|
||||
"github.com/google/go-containerregistry/pkg/v1/types"
|
||||
intoto "github.com/in-toto/in-toto-golang/in_toto"
|
||||
"github.com/secure-systems-lab/go-securesystemslib/dsse"
|
||||
)
|
||||
|
||||
// generateVSA generates a VSA from the attestation manifest
|
||||
// TODO: remove signing logic and move generateVSA to attestation/vsa.go
|
||||
func generateVSA(ctx context.Context, manifest attestation.AttestationManifest, signer dsse.SignerVerifier, opts *SigningOptions) (*mutate.Addendum, error) {
|
||||
if len(manifest.Attestation.Layers) == 0 {
|
||||
return nil, fmt.Errorf("no attestations found to generate VSA from")
|
||||
}
|
||||
sub := manifest.Attestation.Layers[0].Statement.Subject[0]
|
||||
stype := manifest.Attestation.Layers[0].Statement.Type
|
||||
|
||||
uri, err := attestation.ToVSAResourceURI(sub)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate VSA resource URI: %w", err)
|
||||
}
|
||||
|
||||
inputs := make([]attestation.VSAInputAttestation, 0, len(manifest.Attestation.Layers))
|
||||
for _, att := range manifest.Attestation.Layers {
|
||||
mt, err := att.Layer.MediaType()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get layer media type: %w", err)
|
||||
}
|
||||
if !strings.HasSuffix(string(mt), "+dsse") {
|
||||
continue
|
||||
}
|
||||
dgst, err := att.Layer.Digest()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get layer digest: %w", err)
|
||||
}
|
||||
inputs = append(inputs, attestation.VSAInputAttestation{
|
||||
Digest: map[string]string{"sha256": dgst.Hex},
|
||||
MediaType: string(mt),
|
||||
})
|
||||
}
|
||||
vsaStatement := &intoto.Statement{
|
||||
StatementHeader: intoto.StatementHeader{
|
||||
PredicateType: attestation.VSAPredicateType,
|
||||
Type: stype,
|
||||
Subject: manifest.Attestation.Layers[0].Statement.Subject,
|
||||
},
|
||||
Predicate: attestation.VSAPredicate{
|
||||
Verifier: attestation.VSAVerifier{
|
||||
ID: opts.VSAOptions.VerifierID,
|
||||
},
|
||||
TimeVerified: time.Now().UTC().Format(time.RFC3339),
|
||||
ResourceUri: uri,
|
||||
Policy: attestation.VSAPolicy{URI: opts.VSAOptions.PolicyURI},
|
||||
VerificationResult: "PASSED",
|
||||
VerifiedLevels: []string{opts.VSAOptions.BuildLevel},
|
||||
InputAttestations: inputs,
|
||||
},
|
||||
}
|
||||
payload, err := json.Marshal(vsaStatement)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal statement: %w", err)
|
||||
}
|
||||
env, err := attestation.SignDSSE(ctx, payload, intoto.PayloadType, signer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to sign statement: %w", err)
|
||||
}
|
||||
mediaType, err := attestation.DSSEMediaType(vsaStatement.PredicateType)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get DSSE media type: %w", err)
|
||||
}
|
||||
|
||||
data, err := json.Marshal(env)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal envelope: %w", err)
|
||||
}
|
||||
mt := types.MediaType(mediaType)
|
||||
newLayer := static.NewLayer(data, mt)
|
||||
ann := make(map[string]string)
|
||||
ann[InTotoReferenceLifecycleStage] = LifecycleStageExperimental
|
||||
ann[oci.InTotoPredicateType] = attestation.VSAPredicateType
|
||||
withAnnotations := mutate.Addendum{
|
||||
Layer: newLayer,
|
||||
Annotations: ann,
|
||||
}
|
||||
return &withAnnotations, nil
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package attestation
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"maps"
|
||||
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/google/go-containerregistry/pkg/v1/partial"
|
||||
@@ -64,10 +65,7 @@ func GetAttestationsFromImage(image v1.Image) ([]AttestationLayer, error) {
|
||||
return nil, fmt.Errorf("failed to get descriptor for layer: %w", err)
|
||||
}
|
||||
// copy original annotations
|
||||
ann := make(map[string]string)
|
||||
for k, v := range layerDesc.Annotations {
|
||||
ann[k] = v
|
||||
}
|
||||
ann := maps.Clone(layerDesc.Annotations)
|
||||
// only decode intoto statements
|
||||
var stmt = new(intoto.Statement)
|
||||
if mt == types.MediaType(intoto.PayloadType) {
|
||||
|
||||
@@ -19,7 +19,7 @@ func SignDSSE(ctx context.Context, payload []byte, payloadType string, signer ds
|
||||
encPayload := dsse.PAE(payloadType, payload)
|
||||
|
||||
// statement message digest
|
||||
hash := util.S256(encPayload)
|
||||
hash := util.SHA256(encPayload)
|
||||
|
||||
// sign message digest
|
||||
sig, err := signer.Sign(ctx, hash)
|
||||
|
||||
@@ -121,7 +121,7 @@ func verifySignature(ctx context.Context, sig Signature, payload []byte, keys Ke
|
||||
return fmt.Errorf("error failed to decode signature: %w", err)
|
||||
}
|
||||
// verify payload ecdsa signature
|
||||
ok = ecdsa.VerifyASN1(publicKey, util.S256(payload), signature)
|
||||
ok = ecdsa.VerifyASN1(publicKey, util.SHA256(payload), signature)
|
||||
if !ok {
|
||||
return fmt.Errorf("payload signature is not valid")
|
||||
}
|
||||
|
||||
@@ -34,12 +34,6 @@ type VSAInputAttestation struct {
|
||||
MediaType string `json:"mediaType"`
|
||||
}
|
||||
|
||||
type VSAOptions struct {
|
||||
BuildLevel string
|
||||
PolicyURI string
|
||||
VerifierID string
|
||||
}
|
||||
|
||||
func ToVSAResourceURI(sub intoto.Subject) (string, error) {
|
||||
//parse purl
|
||||
purl, err := packageurl.FromString(sub.Name)
|
||||
|
||||
152
pkg/mirror/example_mirror_test.go
Normal file
152
pkg/mirror/example_mirror_test.go
Normal file
@@ -0,0 +1,152 @@
|
||||
package mirror_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/attest/internal/embed"
|
||||
"github.com/docker/attest/pkg/mirror"
|
||||
"github.com/docker/attest/pkg/tuf"
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
)
|
||||
|
||||
type TufMirrorOutput struct {
|
||||
metadata *v1.Image
|
||||
delegatedMetadata []*mirror.MirrorImage
|
||||
targets []*mirror.MirrorImage
|
||||
delegatedTargets []*mirror.MirrorIndex
|
||||
}
|
||||
|
||||
func ExampleNewTufMirror() {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
tufOutputPath := filepath.Join(home, ".docker", "tuf")
|
||||
|
||||
// configure TUF mirror
|
||||
metadataURI := "https://docker.github.io/tuf-staging/metadata"
|
||||
targetsURI := "https://docker.github.io/tuf-staging/targets"
|
||||
m, err := mirror.NewTufMirror(embed.StagingRoot, tufOutputPath, metadataURI, targetsURI, tuf.NewMockVersionChecker())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// create metadata manifest
|
||||
metadataManifest, err := m.GetMetadataManifest(metadataURI)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// create delegated targets metadata manifests
|
||||
delegatedMetadata, err := m.GetDelegatedMetadataMirrors()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// create targets manifest
|
||||
targets, err := m.GetTufTargetMirrors()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// create delegated targets manifests
|
||||
delegatedTargets, err := m.GetDelegatedTargetMirrors()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
mirrorOutput := &TufMirrorOutput{
|
||||
metadata: metadataManifest,
|
||||
delegatedMetadata: delegatedMetadata,
|
||||
targets: targets,
|
||||
delegatedTargets: delegatedTargets,
|
||||
}
|
||||
|
||||
// push metadata and targets to registry (optional)
|
||||
err = mirrorToRegistry(mirrorOutput)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// save metadata and targets to local directory (optional)
|
||||
mirrorOutputPath := filepath.Join(home, ".docker", "tuf", "mirror")
|
||||
err = mirrorToLocal(mirrorOutput, mirrorOutputPath)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func mirrorToRegistry(o *TufMirrorOutput) error {
|
||||
// push metadata to registry
|
||||
metadataRepo := "registry-1.docker.io/docker/tuf-metadata:latest"
|
||||
err := mirror.PushToRegistry(o.metadata, metadataRepo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// push delegated metadata to registry
|
||||
for _, metadata := range o.delegatedMetadata {
|
||||
repo, _, ok := strings.Cut(metadataRepo, ":")
|
||||
if !ok {
|
||||
return fmt.Errorf("failed to get repo without tag: %s", metadataRepo)
|
||||
}
|
||||
imageName := fmt.Sprintf("%s:%s", repo, metadata.Tag)
|
||||
err = mirror.PushToRegistry(metadata.Image, imageName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// push top-level targets to registry
|
||||
targetsRepo := "registry-1.docker.io/docker/tuf-targets"
|
||||
for _, target := range o.targets {
|
||||
imageName := fmt.Sprintf("%s:%s", targetsRepo, target.Tag)
|
||||
err = mirror.PushToRegistry(target.Image, imageName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// push delegated targets to registry
|
||||
for _, target := range o.delegatedTargets {
|
||||
imageName := fmt.Sprintf("%s:%s", targetsRepo, target.Tag)
|
||||
err = mirror.PushToRegistry(target.Index, imageName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func mirrorToLocal(o *TufMirrorOutput, outputPath string) error {
|
||||
// output metadata to local directory
|
||||
err := mirror.SaveAsOCILayout(o.metadata, outputPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// output delegated metadata to local directory
|
||||
for _, metadata := range o.delegatedMetadata {
|
||||
path := filepath.Join(outputPath, metadata.Tag)
|
||||
err = mirror.SaveAsOCILayout(metadata.Image, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// output top-level targets to local directory
|
||||
for _, target := range o.targets {
|
||||
path := filepath.Join(outputPath, target.Tag)
|
||||
err = mirror.SaveAsOCILayout(target.Image, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// output delegated targets to local directory
|
||||
for _, target := range o.delegatedTargets {
|
||||
path := filepath.Join(outputPath, target.Tag)
|
||||
err = mirror.SaveAsOCILayout(target.Index, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
|
||||
"github.com/docker/attest/internal/embed"
|
||||
"github.com/docker/attest/internal/test"
|
||||
"github.com/docker/attest/pkg/tuf"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/theupdateframework/go-tuf/v2/metadata"
|
||||
)
|
||||
@@ -20,11 +21,11 @@ func TestGetTufMetadataMirror(t *testing.T) {
|
||||
defer server.Close()
|
||||
|
||||
path := test.CreateTempDir(t, "", "tuf_temp")
|
||||
m, err := NewTufMirror(embed.DevRoot, path, server.URL+"/metadata", server.URL+"/targets")
|
||||
assert.Nil(t, err)
|
||||
m, err := NewTufMirror(embed.DevRoot, path, server.URL+"/metadata", server.URL+"/targets", tuf.NewMockVersionChecker())
|
||||
assert.NoError(t, err)
|
||||
|
||||
tufMetadata, err := m.getTufMetadataMirror(server.URL + "/metadata")
|
||||
assert.Nil(t, err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// check that all roles are not empty
|
||||
assert.Greater(t, len(tufMetadata.Root), 0)
|
||||
@@ -38,16 +39,16 @@ func TestGetMetadataManifest(t *testing.T) {
|
||||
defer server.Close()
|
||||
|
||||
path := test.CreateTempDir(t, "", "tuf_temp")
|
||||
m, err := NewTufMirror(embed.DevRoot, path, server.URL+"/metadata", server.URL+"/targets")
|
||||
assert.Nil(t, err)
|
||||
m, err := NewTufMirror(embed.DevRoot, path, server.URL+"/metadata", server.URL+"/targets", tuf.NewMockVersionChecker())
|
||||
assert.NoError(t, err)
|
||||
|
||||
img, err := m.GetMetadataManifest(server.URL + "/metadata")
|
||||
assert.Nil(t, err)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, img)
|
||||
|
||||
image := *img
|
||||
mf, err := image.RawManifest()
|
||||
assert.Nil(t, err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
type Annotations struct {
|
||||
Annotations map[string]string `json:"annotations"`
|
||||
@@ -57,7 +58,7 @@ func TestGetMetadataManifest(t *testing.T) {
|
||||
}
|
||||
l := &Layers{}
|
||||
err = json.Unmarshal(mf, l)
|
||||
assert.Nil(t, err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// check that layers are annotated and use consistent snapshot naming
|
||||
for _, layer := range l.Layers {
|
||||
@@ -69,7 +70,7 @@ func TestGetMetadataManifest(t *testing.T) {
|
||||
continue
|
||||
}
|
||||
_, err := strconv.Atoi(parts[0])
|
||||
assert.Nil(t, err)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,11 +79,11 @@ func TestGetDelegatedMetadataMirrors(t *testing.T) {
|
||||
defer server.Close()
|
||||
|
||||
path := test.CreateTempDir(t, "", "tuf_temp")
|
||||
m, err := NewTufMirror(embed.DevRoot, path, server.URL+"/metadata", server.URL+"/targets")
|
||||
assert.Nil(t, err)
|
||||
m, err := NewTufMirror(embed.DevRoot, path, server.URL+"/metadata", server.URL+"/targets", tuf.NewMockVersionChecker())
|
||||
assert.NoError(t, err)
|
||||
|
||||
delegations, err := m.GetDelegatedMetadataMirrors()
|
||||
assert.Nil(t, err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.NotNil(t, delegations)
|
||||
assert.Greater(t, len(delegations), 0)
|
||||
|
||||
@@ -15,11 +15,11 @@ import (
|
||||
"github.com/google/go-containerregistry/pkg/v1/remote"
|
||||
)
|
||||
|
||||
func NewTufMirror(root []byte, tufPath, metadataURL, targetsURL string) (*TufMirror, error) {
|
||||
func NewTufMirror(root []byte, tufPath, metadataURL, targetsURL string, versionChecker tuf.VersionChecker) (*TufMirror, error) {
|
||||
if root == nil {
|
||||
root = embed.DefaultRoot
|
||||
}
|
||||
tufClient, err := tuf.NewTufClient(root, tufPath, metadataURL, targetsURL)
|
||||
tufClient, err := tuf.NewTufClient(root, tufPath, metadataURL, targetsURL, versionChecker)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create TUF client: %w", err)
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ func (m *TufMirror) GetTufTargetMirrors() ([]*MirrorImage, error) {
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("missing sha256 hash for target %s", t.Path)
|
||||
}
|
||||
name := strings.Join([]string{hash.String(), t.Path}, ".")
|
||||
name := hash.String() + "." + t.Path
|
||||
ann := map[string]string{tufFileAnnotation: name}
|
||||
layer := mutate.Addendum{Layer: static.NewLayer(data, tufTargetMediaType), Annotations: ann}
|
||||
img, err = mutate.Append(img, layer)
|
||||
@@ -86,7 +86,7 @@ func (m *TufMirror) GetDelegatedTargetMirrors() ([]*MirrorIndex, error) {
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("failed to find target subdirectory [%s] in path: %s", subdir, target.Path)
|
||||
}
|
||||
name := strings.Join([]string{hash.String(), filename}, ".")
|
||||
name := hash.String() + "." + filename
|
||||
ann := map[string]string{tufFileAnnotation: name}
|
||||
layer := mutate.Addendum{Layer: static.NewLayer(data, tufTargetMediaType), Annotations: ann}
|
||||
img, err = mutate.Append(img, layer)
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
|
||||
"github.com/docker/attest/internal/embed"
|
||||
"github.com/docker/attest/internal/test"
|
||||
"github.com/docker/attest/pkg/tuf"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -26,23 +27,23 @@ func TestGetTufTargetsMirror(t *testing.T) {
|
||||
defer server.Close()
|
||||
|
||||
path := test.CreateTempDir(t, "", "tuf_temp")
|
||||
m, err := NewTufMirror(embed.DevRoot, path, server.URL+"/metadata", server.URL+"/targets")
|
||||
assert.Nil(t, err)
|
||||
m, err := NewTufMirror(embed.DevRoot, path, server.URL+"/metadata", server.URL+"/targets", tuf.NewMockVersionChecker())
|
||||
assert.NoError(t, err)
|
||||
|
||||
targets, err := m.GetTufTargetMirrors()
|
||||
assert.Nil(t, err)
|
||||
assert.NoError(t, err)
|
||||
assert.Greater(t, len(targets), 0)
|
||||
|
||||
// check for image layer annotations
|
||||
for _, target := range targets {
|
||||
img := *target.Image
|
||||
mf, err := img.RawManifest()
|
||||
assert.Nil(t, err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// unmarshal manifest with annotations
|
||||
l := &Layers{}
|
||||
err = json.Unmarshal(mf, l)
|
||||
assert.Nil(t, err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// check that layers are annotated
|
||||
for _, layer := range l.Layers {
|
||||
@@ -60,11 +61,11 @@ func TestTargetDelegationMetadata(t *testing.T) {
|
||||
defer server.Close()
|
||||
|
||||
path := test.CreateTempDir(t, "", "tuf_temp")
|
||||
tm, err := NewTufMirror(embed.DevRoot, path, server.URL+"/metadata", server.URL+"/targets")
|
||||
assert.Nil(t, err)
|
||||
tm, err := NewTufMirror(embed.DevRoot, path, server.URL+"/metadata", server.URL+"/targets", tuf.NewMockVersionChecker())
|
||||
assert.NoError(t, err)
|
||||
|
||||
targets, err := tm.TufClient.LoadDelegatedTargets("test-role", "targets")
|
||||
assert.Nil(t, err)
|
||||
assert.NoError(t, err)
|
||||
assert.Greater(t, len(targets.Signed.Targets), 0)
|
||||
}
|
||||
|
||||
@@ -73,23 +74,23 @@ func TestGetDelegatedTargetMirrors(t *testing.T) {
|
||||
defer server.Close()
|
||||
|
||||
path := test.CreateTempDir(t, "", "tuf_temp")
|
||||
m, err := NewTufMirror(embed.DevRoot, path, server.URL+"/metadata", server.URL+"/targets")
|
||||
assert.Nil(t, err)
|
||||
m, err := NewTufMirror(embed.DevRoot, path, server.URL+"/metadata", server.URL+"/targets", tuf.NewMockVersionChecker())
|
||||
assert.NoError(t, err)
|
||||
|
||||
mirrors, err := m.GetDelegatedTargetMirrors()
|
||||
assert.Nil(t, err)
|
||||
assert.NoError(t, err)
|
||||
assert.Greater(t, len(mirrors), 0)
|
||||
|
||||
// check for index image annotations
|
||||
for _, mirror := range mirrors {
|
||||
idx := *mirror.Index
|
||||
mf, err := idx.RawManifest()
|
||||
assert.Nil(t, err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// unmarshal manifest with annotations
|
||||
l := &Layers{}
|
||||
err = json.Unmarshal(mf, l)
|
||||
assert.Nil(t, err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// check that layers are annotated
|
||||
for _, layer := range l.Layers {
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/google/go-containerregistry/pkg/v1/layout"
|
||||
"github.com/google/go-containerregistry/pkg/v1/remote"
|
||||
"github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/common"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/package-url/packageurl-go"
|
||||
"github.com/pkg/errors"
|
||||
@@ -71,7 +72,7 @@ func attestationManifestFromOCILayout(path string, platformStr string) (*Attesta
|
||||
}
|
||||
}
|
||||
for _, mf := range mfs2.Manifests {
|
||||
if mf.Annotations[DockerReferenceType] != AttestationManifestType {
|
||||
if mf.Annotations[att.DockerReferenceType] != AttestationManifestType {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -329,7 +330,7 @@ func ExtractEnvelopes(ia *AttestationManifest, predicateType string) ([]*att.Env
|
||||
|
||||
func imageDigestForPlatform(ix *v1.IndexManifest, platform *v1.Platform) (string, error) {
|
||||
for _, m := range ix.Manifests {
|
||||
if m.MediaType == ocispec.MediaTypeImageManifest || m.MediaType == "application/vnd.docker.distribution.manifest.v2+json" && m.Platform.Equals(*platform) {
|
||||
if (m.MediaType == ocispec.MediaTypeImageManifest || m.MediaType == "application/vnd.docker.distribution.manifest.v2+json") && m.Platform.Equals(*platform) {
|
||||
return m.Digest.String(), nil
|
||||
}
|
||||
}
|
||||
@@ -338,8 +339,8 @@ func imageDigestForPlatform(ix *v1.IndexManifest, platform *v1.Platform) (string
|
||||
|
||||
func attestationDigestForDigest(ix *v1.IndexManifest, imageDigest string, attestType string) (string, error) {
|
||||
for _, m := range ix.Manifests {
|
||||
if v, ok := m.Annotations["vnd.docker.reference.type"]; ok && v == attestType {
|
||||
if d, ok := m.Annotations["vnd.docker.reference.digest"]; ok && d == imageDigest {
|
||||
if v, ok := m.Annotations[att.DockerReferenceType]; ok && v == attestType {
|
||||
if d, ok := m.Annotations[DockerReferenceDigest]; ok && d == imageDigest {
|
||||
return m.Digest.String(), nil
|
||||
}
|
||||
}
|
||||
@@ -393,3 +394,13 @@ func RefToPURL(ref string, platform string) (string, bool, error) {
|
||||
p := packageurl.NewPackageURL("docker", ns, name, version, qualifiers, "")
|
||||
return p.ToString(), isCanonical, nil
|
||||
}
|
||||
|
||||
func SplitDigest(digest string) (*common.DigestSet, error) {
|
||||
parts := strings.SplitN(digest, ":", 2)
|
||||
if len(parts) != 2 {
|
||||
return nil, fmt.Errorf("invalid digest %q", digest)
|
||||
}
|
||||
return &common.DigestSet{
|
||||
parts[0]: parts[1],
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package oci
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-containerregistry/pkg/v1/layout"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -47,3 +49,36 @@ func TestRefToPurl(t *testing.T) {
|
||||
assert.Equal(t, "pkg:docker/localhost%3A5001/alpine?digest=sha256%3Ac5b1261d6d3e43071626931fc004f70149baeba2c8ec672bd4f27761f8e1ad6b&platform=arm64%2Flinux", purl)
|
||||
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
|
||||
func TestImageDigestForPlatform(t *testing.T) {
|
||||
idx, err := layout.ImageIndexFromPath(UnsignedTestImage)
|
||||
assert.NoError(t, err)
|
||||
|
||||
idxm, err := idx.IndexManifest()
|
||||
assert.NoError(t, err)
|
||||
|
||||
idxDescriptor := idxm.Manifests[0]
|
||||
idxDigest := idxDescriptor.Digest
|
||||
|
||||
mfs, err := idx.ImageIndex(idxDigest)
|
||||
assert.NoError(t, err)
|
||||
mfs2, err := mfs.IndexManifest()
|
||||
assert.NoError(t, err)
|
||||
|
||||
p, err := parsePlatform("linux/amd64")
|
||||
assert.NoError(t, err)
|
||||
digest, err := imageDigestForPlatform(mfs2, p)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "sha256:da8b190665956ea07890a0273e2a9c96bfe291662f08e2860e868eef69c34620", digest)
|
||||
|
||||
p, err = parsePlatform("linux/arm64")
|
||||
assert.NoError(t, err)
|
||||
digest, err = imageDigestForPlatform(mfs2, p)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "sha256:7a76cec943853f9f7105b1976afa1bf7cd5bb6afc4e9d5852dd8da7cf81ae86e", digest)
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
DockerReferenceType = "vnd.docker.reference.type"
|
||||
DockerReferenceDigest = "vnd.docker.reference.digest"
|
||||
AttestationManifestType = "attestation-manifest"
|
||||
InTotoPredicateType = "in-toto.io/predicate-type"
|
||||
|
||||
@@ -26,5 +26,5 @@ func GetPolicyEvaluator(ctx context.Context) (PolicyEvaluator, error) {
|
||||
}
|
||||
|
||||
type PolicyEvaluator interface {
|
||||
Evaluate(ctx context.Context, resolver oci.AttestationResolver, policy []*PolicyFile, input *PolicyInput) error
|
||||
Evaluate(ctx context.Context, resolver oci.AttestationResolver, pctx *Policy, input *PolicyInput) (*Result, error)
|
||||
}
|
||||
|
||||
32
pkg/policy/mock.go
Normal file
32
pkg/policy/mock.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package policy
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/docker/attest/pkg/oci"
|
||||
)
|
||||
|
||||
type MockPolicyEvaluator struct {
|
||||
EvaluateFunc func(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 *PolicyInput) (*Result, error) {
|
||||
if pe.EvaluateFunc != nil {
|
||||
return pe.EvaluateFunc(ctx, resolver, pctx, input)
|
||||
}
|
||||
return AllowedResult(), nil
|
||||
}
|
||||
|
||||
func GetMockPolicy() PolicyEvaluator {
|
||||
return &MockPolicyEvaluator{
|
||||
EvaluateFunc: func(ctx context.Context, resolver oci.AttestationResolver, pctx *Policy, input *PolicyInput) (*Result, error) {
|
||||
return AllowedResult(), nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func AllowedResult() *Result {
|
||||
return &Result{
|
||||
Success: true,
|
||||
}
|
||||
}
|
||||
@@ -6,12 +6,13 @@ import (
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/attest/internal/util"
|
||||
"github.com/docker/attest/pkg/oci"
|
||||
"github.com/docker/attest/pkg/tuf"
|
||||
intoto "github.com/in-toto/in-toto-golang/in_toto"
|
||||
|
||||
goyaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
@@ -20,9 +21,25 @@ const (
|
||||
PolicyMappingFileName = "mapping.yaml"
|
||||
)
|
||||
|
||||
var (
|
||||
PolicyFileNames = []string{"data.yaml", "policy.rego"}
|
||||
)
|
||||
type Summary struct {
|
||||
Subjects []intoto.Subject `json:"subjects"`
|
||||
SLSALevels []string `json:"slsa_levels"`
|
||||
Verifier string `json:"verifier"`
|
||||
PolicyURI string `json:"policy_uri"`
|
||||
}
|
||||
|
||||
type Violation struct {
|
||||
Type string `json:"type"`
|
||||
Description string `json:"description"`
|
||||
Attestation *intoto.Statement `json:"attestation"`
|
||||
Details map[string]any `json:"details"`
|
||||
}
|
||||
|
||||
type Result struct {
|
||||
Success bool `json:"success"`
|
||||
Violations []Violation `json:"violations"`
|
||||
Summary Summary `json:"summary"`
|
||||
}
|
||||
|
||||
type PolicyMappings struct {
|
||||
Version string `json:"version"`
|
||||
@@ -32,15 +49,19 @@ type PolicyMappings struct {
|
||||
}
|
||||
|
||||
type PolicyMapping struct {
|
||||
Name string `json:"namespace"`
|
||||
Location string `json:"location"`
|
||||
Description string `json:"description"`
|
||||
Origin PolicyOrigin `json:"origin"`
|
||||
Id string `json:"id"`
|
||||
Description string `json:"description"`
|
||||
Origin PolicyOrigin `json:"origin"`
|
||||
Files []PolicyMappingFile `json:"files"`
|
||||
}
|
||||
|
||||
type PolicyMappingFile struct {
|
||||
Path string `json:"path"`
|
||||
}
|
||||
|
||||
type PolicyMirror struct {
|
||||
Name string `json:"name"`
|
||||
Mirror MirrorSpec `json:"mirror"`
|
||||
PolicyId string `yaml:"policy-id"`
|
||||
Mirror MirrorSpec `json:"mirror"`
|
||||
}
|
||||
|
||||
type MirrorSpec struct {
|
||||
@@ -60,6 +81,11 @@ type PolicyOptions struct {
|
||||
LocalPolicyDir string
|
||||
}
|
||||
|
||||
type Policy struct {
|
||||
InputFiles []*PolicyFile
|
||||
Query string
|
||||
}
|
||||
|
||||
type PolicyInput struct {
|
||||
Digest string `json:"digest"`
|
||||
Purl string `json:"purl"`
|
||||
@@ -71,13 +97,14 @@ type PolicyFile struct {
|
||||
Content []byte
|
||||
}
|
||||
|
||||
func resolveLocalPolicy(opts *PolicyOptions, mapping *PolicyMapping) ([]*PolicyFile, error) {
|
||||
func resolveLocalPolicy(opts *PolicyOptions, mapping *PolicyMapping) (*Policy, error) {
|
||||
if opts.LocalPolicyDir == "" {
|
||||
return nil, fmt.Errorf("local policy dir not set")
|
||||
}
|
||||
files := make([]*PolicyFile, 0, len(PolicyFileNames))
|
||||
for _, filename := range PolicyFileNames {
|
||||
filePath := path.Join(opts.LocalPolicyDir, mapping.Location, filename)
|
||||
files := make([]*PolicyFile, 0, len(mapping.Files))
|
||||
for _, f := range mapping.Files {
|
||||
filename := f.Path
|
||||
filePath := path.Join(opts.LocalPolicyDir, filename)
|
||||
fileContents, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read policy file %s: %w", filename, err)
|
||||
@@ -87,10 +114,13 @@ func resolveLocalPolicy(opts *PolicyOptions, mapping *PolicyMapping) ([]*PolicyF
|
||||
Content: fileContents,
|
||||
})
|
||||
}
|
||||
return files, nil
|
||||
policy := &Policy{
|
||||
InputFiles: files,
|
||||
}
|
||||
return policy, nil
|
||||
}
|
||||
|
||||
func loadLocalMappings(opts *PolicyOptions) (*PolicyMappings, error) {
|
||||
func LoadLocalMappings(opts *PolicyOptions) (*PolicyMappings, error) {
|
||||
if opts.LocalPolicyDir == "" {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -107,11 +137,11 @@ func loadLocalMappings(opts *PolicyOptions) (*PolicyMappings, error) {
|
||||
return mappings, nil
|
||||
}
|
||||
|
||||
func resolveTufPolicy(opts *PolicyOptions, mapping *PolicyMapping) ([]*PolicyFile, error) {
|
||||
files := make([]*PolicyFile, 0, len(PolicyFileNames))
|
||||
for _, filename := range PolicyFileNames {
|
||||
filePath := path.Join(mapping.Location, filename)
|
||||
_, fileContents, err := opts.TufClient.DownloadTarget(filePath, filepath.Join(opts.LocalTargetsDir, filePath))
|
||||
func resolveTufPolicy(opts *PolicyOptions, mapping *PolicyMapping) (*Policy, error) {
|
||||
files := make([]*PolicyFile, 0, len(mapping.Files))
|
||||
for _, f := range mapping.Files {
|
||||
filename := f.Path
|
||||
_, fileContents, err := opts.TufClient.DownloadTarget(filename, filepath.Join(opts.LocalTargetsDir, filename))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to download policy file %s: %w", filename, err)
|
||||
}
|
||||
@@ -120,7 +150,10 @@ func resolveTufPolicy(opts *PolicyOptions, mapping *PolicyMapping) ([]*PolicyFil
|
||||
Content: fileContents,
|
||||
})
|
||||
}
|
||||
return files, nil
|
||||
policy := &Policy{
|
||||
InputFiles: files,
|
||||
}
|
||||
return policy, nil
|
||||
}
|
||||
|
||||
func loadTufMappings(tufClient tuf.TUFClient, localTargetsDir string) (*PolicyMappings, error) {
|
||||
@@ -148,10 +181,10 @@ func findPolicyMatch(named reference.Named, mappings *PolicyMappings) (*PolicyMa
|
||||
}
|
||||
// now search mirrors
|
||||
for _, mirror := range mappings.Mirrors {
|
||||
if util.StringInSlice(reference.Domain(named), mirror.Mirror.Domains) &&
|
||||
if slices.Contains(mirror.Mirror.Domains, reference.Domain(named)) &&
|
||||
strings.HasPrefix(reference.Path(named), mirror.Mirror.Prefix) {
|
||||
for _, mapping := range mappings.Policies {
|
||||
if mapping.Name == mirror.Name {
|
||||
if mapping.Id == mirror.PolicyId {
|
||||
return &mapping, nil
|
||||
}
|
||||
}
|
||||
@@ -162,7 +195,7 @@ func findPolicyMatch(named reference.Named, mappings *PolicyMappings) (*PolicyMa
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func ResolvePolicy(ctx context.Context, resolver oci.AttestationResolver, opts *PolicyOptions) ([]*PolicyFile, error) {
|
||||
func ResolvePolicy(ctx context.Context, resolver oci.AttestationResolver, opts *PolicyOptions) (*Policy, error) {
|
||||
imageName, err := resolver.ImageName(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get image name: %w", err)
|
||||
@@ -171,7 +204,7 @@ func ResolvePolicy(ctx context.Context, resolver oci.AttestationResolver, opts *
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse image name: %w", err)
|
||||
}
|
||||
localMappings, err := loadLocalMappings(opts)
|
||||
localMappings, err := LoadLocalMappings(opts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load local policy mappings: %w", err)
|
||||
}
|
||||
@@ -188,7 +221,7 @@ func ResolvePolicy(ctx context.Context, resolver oci.AttestationResolver, opts *
|
||||
// it's a mirror of a tuf policy
|
||||
if mirror != nil {
|
||||
for _, mapping := range tufMappings.Policies {
|
||||
if mapping.Name == mirror.Name {
|
||||
if mapping.Id == mirror.PolicyId {
|
||||
return resolveTufPolicy(opts, &mapping)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/docker/attest/pkg/policy"
|
||||
"github.com/docker/attest/pkg/tuf"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func loadAttestation(t *testing.T, path string) *attestation.Envelope {
|
||||
@@ -32,61 +33,37 @@ func TestRegoEvaluator_Evaluate(t *testing.T) {
|
||||
ctx, _ := test.Setup(t)
|
||||
|
||||
TestDataPath := filepath.Join("..", "..", "test", "testdata")
|
||||
MockTufRepo := filepath.Join(TestDataPath, "local-policy")
|
||||
ExampleAttestation := filepath.Join(TestDataPath, "example_attestation.json")
|
||||
VSA := filepath.Join(TestDataPath, "vsa.json")
|
||||
|
||||
re := policy.NewRegoEvaluator(true)
|
||||
|
||||
defaultInput := &policy.PolicyInput{
|
||||
Digest: "sha256:test-digest",
|
||||
Purl: "test-purl",
|
||||
IsCanonical: true,
|
||||
}
|
||||
|
||||
defaultResolver := oci.MockResolver{
|
||||
Envs: []*attestation.Envelope{loadAttestation(t, ExampleAttestation)},
|
||||
}
|
||||
|
||||
vsaResolver := oci.MockResolver{
|
||||
Envs: []*attestation.Envelope{loadAttestation(t, ExampleAttestation), loadAttestation(t, VSA)},
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
repo string
|
||||
expectSuccess bool
|
||||
input *policy.PolicyInput
|
||||
isCanonical bool
|
||||
resolver oci.AttestationResolver
|
||||
policy *policy.PolicyOptions
|
||||
}{
|
||||
{repo: "testdata/mock-tuf-allow", expectSuccess: true, input: defaultInput, resolver: defaultResolver},
|
||||
{repo: "testdata/mock-tuf-deny", expectSuccess: false, input: defaultInput, resolver: defaultResolver},
|
||||
{repo: "testdata/mock-tuf-verify-sig", expectSuccess: true, input: defaultInput, resolver: defaultResolver},
|
||||
{repo: "testdata/mock-tuf-wrong-key", expectSuccess: false, input: defaultInput, resolver: defaultResolver},
|
||||
{repo: MockTufRepo, expectSuccess: true, input: &policy.PolicyInput{
|
||||
Digest: "sha256:da8b190665956ea07890a0273e2a9c96bfe291662f08e2860e868eef69c34620",
|
||||
Purl: "pkg:docker/test-image@test?platform=linux%2Famd64",
|
||||
IsCanonical: true,
|
||||
}, resolver: vsaResolver},
|
||||
{repo: MockTufRepo, expectSuccess: true, input: &policy.PolicyInput{
|
||||
Digest: "sha256:da8b190665956ea07890a0273e2a9c96bfe291662f08e2860e868eef69c34620",
|
||||
Purl: "pkg:docker/test-image@test?platform=linux%2Famd64",
|
||||
IsCanonical: false,
|
||||
}, resolver: vsaResolver},
|
||||
// not a doi
|
||||
{repo: MockTufRepo, expectSuccess: false, input: defaultInput, resolver: vsaResolver, policy: &policy.PolicyOptions{
|
||||
LocalPolicyDir: "testdata/mock-tuf-deny",
|
||||
}},
|
||||
// digest mismatch
|
||||
{repo: MockTufRepo, expectSuccess: false, input: &policy.PolicyInput{
|
||||
Digest: "sha256:test-digest-wrong",
|
||||
Purl: "test-purl",
|
||||
IsCanonical: false,
|
||||
}, resolver: vsaResolver},
|
||||
{repo: "testdata/mock-tuf-allow", expectSuccess: true, 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-wrong-key", expectSuccess: false, isCanonical: false, resolver: defaultResolver},
|
||||
{repo: "testdata/mock-tuf-allow-canonical", expectSuccess: true, isCanonical: true, resolver: defaultResolver},
|
||||
{repo: "testdata/mock-tuf-allow-canonical", expectSuccess: false, isCanonical: false, resolver: defaultResolver},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.repo, func(t *testing.T) {
|
||||
input := &policy.PolicyInput{
|
||||
Digest: "sha256:test-digest",
|
||||
Purl: "test-purl",
|
||||
IsCanonical: tc.isCanonical,
|
||||
}
|
||||
|
||||
tufClient := tuf.NewMockTufClient(tc.repo, test.CreateTempDir(t, "", "tuf-dest"))
|
||||
if tc.policy == nil {
|
||||
tc.policy = &policy.PolicyOptions{
|
||||
@@ -95,15 +72,29 @@ func TestRegoEvaluator_Evaluate(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
policyFiles, err := policy.ResolvePolicy(ctx, tc.resolver, tc.policy)
|
||||
policy, err := policy.ResolvePolicy(ctx, tc.resolver, tc.policy)
|
||||
assert.NoErrorf(t, err, "failed to resolve policy")
|
||||
err = re.Evaluate(ctx, tc.resolver, policyFiles, tc.input)
|
||||
result, err := re.Evaluate(ctx, tc.resolver, policy, input)
|
||||
require.NoErrorf(t, err, "Evaluate failed")
|
||||
|
||||
if tc.expectSuccess {
|
||||
assert.NoErrorf(t, err, "Evaluate failed")
|
||||
assert.True(t, result.Success, "Evaluate should have succeeded")
|
||||
} else {
|
||||
assert.Errorf(t, err, "Evaluate should have failed")
|
||||
assert.False(t, result.Success, "Evaluate should have failed")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestLoadingMappings(t *testing.T) {
|
||||
opts := &policy.PolicyOptions{
|
||||
LocalPolicyDir: filepath.Join("testdata", "mock-tuf-allow"),
|
||||
}
|
||||
policyMappings, err := policy.LoadLocalMappings(opts)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, len(policyMappings.Mirrors), 1)
|
||||
for _, mirror := range policyMappings.Mirrors {
|
||||
assert.Equal(t, "docker-official-images", mirror.PolicyId)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,16 +23,20 @@ import (
|
||||
|
||||
type regoEvaluator struct {
|
||||
debug bool
|
||||
query string
|
||||
}
|
||||
|
||||
const (
|
||||
DefaultQuery = "result := data.attest.result"
|
||||
resultBinding = "result"
|
||||
)
|
||||
|
||||
func NewRegoEvaluator(debug bool) PolicyEvaluator {
|
||||
return ®oEvaluator{
|
||||
debug: debug,
|
||||
}
|
||||
}
|
||||
|
||||
func (re *regoEvaluator) Evaluate(ctx context.Context, resolver oci.AttestationResolver, files []*PolicyFile, input *PolicyInput) error {
|
||||
func (re *regoEvaluator) Evaluate(ctx context.Context, resolver oci.AttestationResolver, pctx *Policy, input *PolicyInput) (*Result, error) {
|
||||
var regoOpts []func(*rego.Rego)
|
||||
|
||||
// Create a new in-memory store
|
||||
@@ -41,19 +45,19 @@ func (re *regoEvaluator) Evaluate(ctx context.Context, resolver oci.AttestationR
|
||||
params.Write = true
|
||||
txn, err := store.NewTransaction(ctx, params)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, target := range files {
|
||||
for _, target := range pctx.InputFiles {
|
||||
// load yaml as data (no rego opt for this!?)
|
||||
if filepath.Ext(target.Path) == ".yaml" {
|
||||
yamlData, err := loadYAML(target.Path, target.Content)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
err = store.Write(ctx, txn, storage.AddOp, storage.Path{}, yamlData)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
regoOpts = append(regoOpts, rego.Module(target.Path, string(target.Content)))
|
||||
@@ -63,7 +67,7 @@ func (re *regoEvaluator) Evaluate(ctx context.Context, resolver oci.AttestationR
|
||||
err = store.Commit(ctx, txn)
|
||||
if err != nil {
|
||||
store.Abort(ctx, txn)
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if re.debug {
|
||||
@@ -73,12 +77,15 @@ func (re *regoEvaluator) Evaluate(ctx context.Context, resolver oci.AttestationR
|
||||
rego.Dump(os.Stderr),
|
||||
)
|
||||
}
|
||||
|
||||
query := DefaultQuery
|
||||
if pctx.Query != "" {
|
||||
query = pctx.Query
|
||||
}
|
||||
regoOpts = append(regoOpts,
|
||||
rego.Query("data.docker.allow"),
|
||||
rego.StrictBuiltinErrors(true),
|
||||
rego.Query(query),
|
||||
rego.Input(input),
|
||||
rego.Store(store),
|
||||
rego.GenerateJSON(jsonGenerator[Result]()),
|
||||
)
|
||||
for _, custom := range RegoFunctions(resolver) {
|
||||
regoOpts = append(regoOpts, custom.Func)
|
||||
@@ -87,18 +94,49 @@ func (re *regoEvaluator) Evaluate(ctx context.Context, resolver oci.AttestationR
|
||||
r := rego.New(regoOpts...)
|
||||
rs, err := r.Eval(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error from Eval: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !rs.Allowed() {
|
||||
return fmt.Errorf("policy evaluation failed")
|
||||
if len(rs) == 0 {
|
||||
return nil, fmt.Errorf("no policy evaluation result")
|
||||
}
|
||||
binding, ok := rs[0].Bindings[resultBinding]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("failed to extract verification result")
|
||||
}
|
||||
result, ok := binding.(Result)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("failed to extract verification result")
|
||||
}
|
||||
|
||||
return nil
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func jsonGenerator[T any]() func(t *ast.Term, ec *rego.EvalContext) (any, error) {
|
||||
return func(t *ast.Term, ec *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
|
||||
// We can't use ast.As directly because it fails if the AST contains a set
|
||||
json, err := ast.JSON(t.Value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v, err := ast.InterfaceToValue(json)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var result T
|
||||
err = ast.As(v, &result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
|
||||
var dynamicObj = types.NewObject(nil, types.NewDynamicProperty(types.S, types.A))
|
||||
var arrayObj = types.NewArray(nil, dynamicObj)
|
||||
var setObj = types.NewSet(dynamicObj)
|
||||
|
||||
var verifyDecl = &ast.Builtin{
|
||||
Name: "attestations.verify_envelope",
|
||||
Decl: types.NewFunction(types.Args(dynamicObj, arrayObj), dynamicObj),
|
||||
@@ -106,7 +144,7 @@ var verifyDecl = &ast.Builtin{
|
||||
}
|
||||
var attestDecl = &ast.Builtin{
|
||||
Name: "attestations.attestation",
|
||||
Decl: types.NewFunction(types.Args(types.S), dynamicObj),
|
||||
Decl: types.NewFunction(types.Args(types.S), setObj),
|
||||
Nondeterministic: true,
|
||||
}
|
||||
|
||||
@@ -160,12 +198,13 @@ func fetchIntotoAttestations(resolver oci.AttestationResolver) func(rego.Builtin
|
||||
values[i] = ast.NewTerm(value)
|
||||
}
|
||||
|
||||
// Wrap the values in an ast.Array and convert it to an ast.Term.
|
||||
array := ast.NewTerm(ast.NewArray(values...))
|
||||
// Wrap the values in an ast.Set and convert it to an ast.Term.
|
||||
set := ast.NewTerm(ast.NewSet(values...))
|
||||
|
||||
return array, nil
|
||||
return set, nil
|
||||
}
|
||||
}
|
||||
|
||||
func verifyIntotoEnvelope(rCtx rego.BuiltinContext, envTerm, keysTerm *ast.Term) (*ast.Term, error) {
|
||||
env := new(att.Envelope)
|
||||
var keys att.Keys
|
||||
|
||||
7
pkg/policy/testdata/mock-tuf-allow-canonical/doi/policy.rego
vendored
Normal file
7
pkg/policy/testdata/mock-tuf-allow-canonical/doi/policy.rego
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
package attest
|
||||
|
||||
import rego.v1
|
||||
|
||||
result := {
|
||||
"success": input.isCanonical,
|
||||
}
|
||||
16
pkg/policy/testdata/mock-tuf-allow-canonical/mapping.yaml
vendored
Normal file
16
pkg/policy/testdata/mock-tuf-allow-canonical/mapping.yaml
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
# map repos to policies
|
||||
version: v1
|
||||
kind: policy-mapping
|
||||
policies:
|
||||
- origin:
|
||||
domain: docker.io
|
||||
prefix: library/
|
||||
id: docker-official-images
|
||||
description: Docker Official Images
|
||||
files:
|
||||
- path: doi/policy.rego
|
||||
mirrors:
|
||||
- policy-id: docker-official-images
|
||||
mirror:
|
||||
domains: [localhost:5001, registry.local:5000]
|
||||
prefix: ""
|
||||
@@ -1 +0,0 @@
|
||||
config:
|
||||
@@ -1,5 +1,7 @@
|
||||
package docker
|
||||
package attest
|
||||
|
||||
import rego.v1
|
||||
|
||||
allow := true
|
||||
result := {
|
||||
"success": true,
|
||||
}
|
||||
|
||||
10
pkg/policy/testdata/mock-tuf-allow/mapping.yaml
vendored
10
pkg/policy/testdata/mock-tuf-allow/mapping.yaml
vendored
@@ -5,6 +5,12 @@ policies:
|
||||
- origin:
|
||||
domain: docker.io
|
||||
prefix: library/
|
||||
name: docker-official-images
|
||||
id: docker-official-images
|
||||
description: Docker Official Images
|
||||
location: doi
|
||||
files:
|
||||
- path: doi/policy.rego
|
||||
mirrors:
|
||||
- policy-id: docker-official-images
|
||||
mirror:
|
||||
domains: [localhost:5001, registry.local:5000]
|
||||
prefix: ""
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
config:
|
||||
@@ -1,5 +1,7 @@
|
||||
package docker
|
||||
package attest
|
||||
|
||||
import rego.v1
|
||||
|
||||
allow := false
|
||||
result := {
|
||||
"success": false,
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ policies:
|
||||
- origin:
|
||||
domain: docker.io
|
||||
prefix: library/
|
||||
name: docker-official-images
|
||||
id: docker-official-images
|
||||
description: Docker Official Images
|
||||
location: doi
|
||||
files:
|
||||
- path: doi/policy.rego
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
config:
|
||||
@@ -1,15 +1,19 @@
|
||||
package docker
|
||||
package attest
|
||||
|
||||
import rego.v1
|
||||
|
||||
keys := [{
|
||||
"id": "a0c296026645799b2a297913878e81b0aefff2a0c301e97232f717e14402f3e4",
|
||||
"key": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEgH23D1i2+ZIOtVjmfB7iFvX8AhVN\n9CPJ4ie9axw+WRHozGnRy99U2dRge3zueBBg2MweF0zrToXGig2v3YOrdw==\n-----END PUBLIC KEY-----",
|
||||
"from": "2023-12-15T14:00:00Z",
|
||||
"to": null
|
||||
"id": "a0c296026645799b2a297913878e81b0aefff2a0c301e97232f717e14402f3e4",
|
||||
"key": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEgH23D1i2+ZIOtVjmfB7iFvX8AhVN\n9CPJ4ie9axw+WRHozGnRy99U2dRge3zueBBg2MweF0zrToXGig2v3YOrdw==\n-----END PUBLIC KEY-----",
|
||||
"from": "2023-12-15T14:00:00Z",
|
||||
"to": null
|
||||
}]
|
||||
|
||||
allow if {
|
||||
success if {
|
||||
some env in attestations.attestation("foo")
|
||||
statement := attestations.verify_envelope(env, keys)
|
||||
}
|
||||
|
||||
result := {
|
||||
"success": success
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ policies:
|
||||
- origin:
|
||||
domain: docker.io
|
||||
prefix: library/
|
||||
name: docker-official-images
|
||||
id: docker-official-images
|
||||
description: Docker Official Images
|
||||
location: doi
|
||||
files:
|
||||
- path: doi/policy.rego
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
config:
|
||||
@@ -1,19 +1,21 @@
|
||||
package docker
|
||||
package attest
|
||||
|
||||
import rego.v1
|
||||
|
||||
keys := {
|
||||
"a0c296026645799b2a297913878e81b0aefff2a0c301e97232f717e14402f3e4": {
|
||||
"id": "a0c296026645799b2a297913878e81b0aefff2a0c301e97232f717e14402f3e4",
|
||||
"key": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEHyZpSgzvqFqNv7f3x7865OS38rAb\nQMcff55zM2UH/KR3Pr84a8QsGDNgaNGzJQJWjtMSgfV8WnNoffNK+svFNg==\n-----END PUBLIC KEY-----",
|
||||
"from": "2023-12-15T14:00:00Z",
|
||||
"to": null,
|
||||
}
|
||||
}
|
||||
keys := [{
|
||||
"id": "a0c296026645799b2a297913878e81b0aefff2a0c301e97232f717e14402f3e4",
|
||||
"key": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEHyZpSgzvqFqNv7f3x7865OS38rAb\nQMcff55zM2UH/KR3Pr84a8QsGDNgaNGzJQJWjtMSgfV8WnNoffNK+svFNg==\n-----END PUBLIC KEY-----",
|
||||
"from": "2023-12-15T14:00:00Z",
|
||||
"to": null,
|
||||
}]
|
||||
|
||||
allow if {
|
||||
default success := false
|
||||
|
||||
success if {
|
||||
some env in attestations.attestation("foo")
|
||||
statement := attestations.verify_envelope(env, keys)
|
||||
}
|
||||
|
||||
allow := true
|
||||
result := {
|
||||
"success": success
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ policies:
|
||||
- origin:
|
||||
domain: docker.io
|
||||
prefix: library/
|
||||
name: docker-official-images
|
||||
id: docker-official-images
|
||||
description: Docker Official Images
|
||||
location: doi
|
||||
files:
|
||||
- path: doi/policy.rego
|
||||
|
||||
@@ -6,6 +6,8 @@ import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
|
||||
"github.com/docker/attest/internal/util"
|
||||
@@ -38,13 +40,39 @@ func (s *ECDSA256_SignerVerifier) Verify(ctx context.Context, data []byte, sig [
|
||||
if !ok {
|
||||
return fmt.Errorf("public key is not ecdsa")
|
||||
}
|
||||
ok = ecdsa.VerifyASN1(pub, util.S256(data), sig)
|
||||
ok = ecdsa.VerifyASN1(pub, util.SHA256(data), sig)
|
||||
if !ok {
|
||||
return fmt.Errorf("payload signature is not valid")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func LoadKeyPair(priv []byte) (dsse.SignerVerifier, error) {
|
||||
privateKey, err := parsePriv(priv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ECDSA256_SignerVerifier{
|
||||
Signer: privateKey,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parsePriv(privkeyBytes []byte) (*ecdsa.PrivateKey, error) {
|
||||
p, _ := pem.Decode(privkeyBytes)
|
||||
if p == nil {
|
||||
return nil, fmt.Errorf("privkey file does not contain any PEM data")
|
||||
}
|
||||
if p.Type != "EC PRIVATE KEY" {
|
||||
return nil, fmt.Errorf("privkey file does not contain a priavte key")
|
||||
}
|
||||
privKey, err := x509.ParseECPrivateKey(p.Bytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error failed to parse public key: %w", err)
|
||||
}
|
||||
|
||||
return privKey, nil
|
||||
}
|
||||
|
||||
func GenKeyPair() (dsse.SignerVerifier, error) {
|
||||
signer, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
|
||||
@@ -13,5 +13,5 @@ func KeyID(pubKey crypto.PublicKey) (string, error) {
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error marshalling public key: %w", err)
|
||||
}
|
||||
return util.HexHashBytes(pub), nil
|
||||
return util.SHA256Hex(pub), nil
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"math/big"
|
||||
@@ -214,7 +213,7 @@ func (tl *RekorTL) VerifyEntryPayload(entryBytes, payload, publicKey []byte) err
|
||||
}
|
||||
|
||||
// compare payload hashes
|
||||
payloadHash := hex.EncodeToString(util.S256(payload))
|
||||
payloadHash := util.SHA256Hex(payload)
|
||||
if rekord.Hash != payloadHash {
|
||||
return fmt.Errorf("error payload and tl entry hash mismatch")
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ func TestCreateX509Cert(t *testing.T) {
|
||||
func TestUploadAndVerifyLogEntry(t *testing.T) {
|
||||
// message digest
|
||||
payload := []byte("test")
|
||||
hash := util.S256(payload)
|
||||
hash := util.SHA256(payload)
|
||||
|
||||
// generate ephemeral keys to sign message digest
|
||||
signer, err := signerverifier.GenKeyPair()
|
||||
|
||||
45
pkg/tuf/example_registry_test.go
Normal file
45
pkg/tuf/example_registry_test.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package tuf_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/docker/attest/internal/embed"
|
||||
"github.com/docker/attest/pkg/tuf"
|
||||
"github.com/theupdateframework/go-tuf/v2/metadata"
|
||||
)
|
||||
|
||||
func ExampleNewTufClient_registry() {
|
||||
// create a tuf client
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
tufOutputPath := filepath.Join(home, ".docker", "tuf")
|
||||
|
||||
// using oci tuf metadata and targets
|
||||
metadataURI := "registry-1.docker.io/docker/tuf-metadata:latest"
|
||||
targetsURI := "registry-1.docker.io/docker/tuf-targets"
|
||||
|
||||
registryClient, err := tuf.NewTufClient(embed.StagingRoot, tufOutputPath, metadataURI, targetsURI, tuf.NewMockVersionChecker())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// get trusted tuf metadata
|
||||
trustedMetadata := registryClient.GetMetadata()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// top-level target files
|
||||
targets := trustedMetadata.Targets[metadata.TARGETS].Signed.Targets
|
||||
|
||||
for _, t := range targets {
|
||||
// download target files
|
||||
_, _, err := registryClient.DownloadTarget(t.Path, filepath.Join(tufOutputPath, "download"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -58,3 +58,15 @@ func (dc *mockTufClient) DownloadTarget(target string, filePath string) (actualF
|
||||
|
||||
return dstFilePath, b, nil
|
||||
}
|
||||
|
||||
type mockVersionChecker struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func NewMockVersionChecker() *mockVersionChecker {
|
||||
return &mockVersionChecker{}
|
||||
}
|
||||
|
||||
func (vc *mockVersionChecker) CheckVersion(client TUFClient) error {
|
||||
return vc.err
|
||||
}
|
||||
|
||||
@@ -121,7 +121,7 @@ func TestFindFileInManifest(t *testing.T) {
|
||||
// make test image manifest
|
||||
file := "test.json"
|
||||
data := []byte("test")
|
||||
hash := v1.Hash{Algorithm: "sha256", Hex: util.HexHashBytes(data)}
|
||||
hash := v1.Hash{Algorithm: "sha256", Hex: util.SHA256Hex(data)}
|
||||
img := empty.Image
|
||||
img = mutate.MediaType(img, types.OCIManifestSchema1)
|
||||
img = mutate.ConfigMediaType(img, types.OCIConfigJSON)
|
||||
|
||||
@@ -36,7 +36,7 @@ type TufClient struct {
|
||||
}
|
||||
|
||||
// NewTufClient creates a new TUF client
|
||||
func NewTufClient(initialRoot []byte, tufPath, metadataSource, targetsSource string) (*TufClient, error) {
|
||||
func NewTufClient(initialRoot []byte, tufPath, metadataSource, targetsSource string, versionChecker VersionChecker) (*TufClient, error) {
|
||||
var tufSource TufSource
|
||||
if strings.HasPrefix(metadataSource, "https://") || strings.HasPrefix(metadataSource, "http://") {
|
||||
tufSource = HttpSource
|
||||
@@ -44,7 +44,7 @@ func NewTufClient(initialRoot []byte, tufPath, metadataSource, targetsSource str
|
||||
tufSource = OciSource
|
||||
}
|
||||
|
||||
tufRootDigest := util.HexHashBytes(initialRoot)
|
||||
tufRootDigest := util.SHA256Hex(initialRoot)
|
||||
|
||||
// create a directory for each initial root.json
|
||||
metadataPath := filepath.Join(tufPath, tufRootDigest)
|
||||
@@ -102,8 +102,13 @@ func NewTufClient(initialRoot []byte, tufPath, metadataSource, targetsSource str
|
||||
updater: up,
|
||||
cfg: cfg,
|
||||
}
|
||||
return client, nil
|
||||
|
||||
err = versionChecker.CheckVersion(client)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
// DownloadTarget downloads the target file using Updater. The Updater gets the target
|
||||
@@ -116,6 +121,14 @@ func (t *TufClient) DownloadTarget(target string, filePath string) (actualFilePa
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
// check if filePath exists and create the directory if it doesn't
|
||||
if _, err := os.Stat(filepath.Dir(filePath)); os.IsNotExist(err) {
|
||||
err = os.MkdirAll(filepath.Dir(filePath), 0755)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("failed to create target download directory '%s': %w", filepath.Dir(filePath), err)
|
||||
}
|
||||
}
|
||||
|
||||
// target is available, so let's see if the target is already present locally
|
||||
actualFilePath, data, err = t.updater.FindCachedTarget(targetInfo, filePath)
|
||||
if err != nil {
|
||||
|
||||
@@ -2,6 +2,7 @@ package tuf
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
@@ -10,6 +11,7 @@ import (
|
||||
|
||||
"github.com/docker/attest/internal/embed"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/theupdateframework/go-tuf/v2/metadata"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -50,6 +52,9 @@ func TestRootInit(t *testing.T) {
|
||||
}()
|
||||
LoadRegistryTestData(t, regAddr, OciTufTestDataPath)
|
||||
|
||||
alwaysGoodVersionChecker := &mockVersionChecker{err: nil}
|
||||
alwaysBadVersionChecker := &mockVersionChecker{err: assert.AnError}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
metadataSource string
|
||||
@@ -60,14 +65,71 @@ func TestRootInit(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
_, err := NewTufClient(embed.DevRoot, tufPath, tc.metadataSource, tc.targetsSource)
|
||||
_, err := NewTufClient(embed.DevRoot, tufPath, tc.metadataSource, tc.targetsSource, alwaysGoodVersionChecker)
|
||||
assert.NoErrorf(t, err, "Failed to create TUF client: %v", err)
|
||||
|
||||
// recreation should work with same root
|
||||
_, err = NewTufClient(embed.DevRoot, tufPath, tc.metadataSource, tc.targetsSource)
|
||||
_, err = NewTufClient(embed.DevRoot, tufPath, tc.metadataSource, tc.targetsSource, alwaysGoodVersionChecker)
|
||||
assert.NoErrorf(t, err, "Failed to recreate TUF client: %v", err)
|
||||
|
||||
_, err = NewTufClient([]byte("broken"), tufPath, tc.metadataSource, tc.targetsSource)
|
||||
_, err = NewTufClient([]byte("broken"), tufPath, tc.metadataSource, tc.targetsSource, alwaysGoodVersionChecker)
|
||||
assert.Errorf(t, err, "Expected error recreating TUF client with broken root: %v", err)
|
||||
|
||||
_, err = NewTufClient(embed.DevRoot, tufPath, tc.metadataSource, tc.targetsSource, alwaysBadVersionChecker)
|
||||
assert.Errorf(t, err, "Expected error creating TUF client with bad attest version: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDownloadTarget(t *testing.T) {
|
||||
tufPath := CreateTempDir(t, "", "tuf_temp")
|
||||
targetFile := "test.txt"
|
||||
delegatedRole := "test-role"
|
||||
delegatedTargetFile := fmt.Sprintf("%s/%s", delegatedRole, targetFile)
|
||||
|
||||
// Start a test HTTP server to serve data from /test/testdata/tuf/test-repo/ paths
|
||||
server := httptest.NewServer(http.FileServer(http.Dir(HttpTufTestDataPath)))
|
||||
defer server.Close()
|
||||
|
||||
// run local registry
|
||||
registry, regAddr := RunTestRegistry(t)
|
||||
defer func() {
|
||||
if err := registry.Terminate(context.Background()); err != nil {
|
||||
t.Fatalf("failed to terminate container: %s", err) // nolint:gocritic
|
||||
}
|
||||
}()
|
||||
LoadRegistryTestData(t, regAddr, OciTufTestDataPath)
|
||||
|
||||
alwaysGoodVersionChecker := &mockVersionChecker{err: nil}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
metadataSource string
|
||||
targetsSource string
|
||||
}{
|
||||
{"http", server.URL + "/metadata", server.URL + "/targets"},
|
||||
{"oci", regAddr.Host + "/tuf-metadata:latest", regAddr.Host + "/tuf-targets"},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tufClient, err := NewTufClient(embed.DevRoot, tufPath, tc.metadataSource, tc.targetsSource, alwaysGoodVersionChecker)
|
||||
assert.NoErrorf(t, err, "Failed to create TUF client: %v", err)
|
||||
|
||||
// get trusted tuf metadata
|
||||
trustedMetadata := tufClient.updater.GetTrustedMetadataSet()
|
||||
assert.NotNil(t, trustedMetadata, "Failed to get trusted metadata")
|
||||
|
||||
// download top-level target files
|
||||
targets := trustedMetadata.Targets[metadata.TARGETS].Signed.Targets
|
||||
for _, target := range targets {
|
||||
// download target files
|
||||
_, _, err := tufClient.DownloadTarget(target.Path, filepath.Join(tufPath, "download"))
|
||||
assert.NoErrorf(t, err, "Failed to download target: %v", err)
|
||||
}
|
||||
|
||||
// download delegated target
|
||||
targetInfo, err := tufClient.updater.GetTargetInfo(delegatedTargetFile)
|
||||
assert.NoError(t, err)
|
||||
_, _, err = tufClient.DownloadTarget(targetInfo.Path, filepath.Join(tufPath, targetInfo.Path))
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
89
pkg/tuf/version.go
Normal file
89
pkg/tuf/version.go
Normal file
@@ -0,0 +1,89 @@
|
||||
package tuf
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
)
|
||||
|
||||
const ThisModulePath = "github.com/docker/attest"
|
||||
|
||||
type VersionChecker interface {
|
||||
// CheckVersion checks if the current version of this library meets the constraints from the TUF repo
|
||||
CheckVersion(tufClient TUFClient) error
|
||||
}
|
||||
|
||||
type InvalidVersionError struct {
|
||||
AttestVersion string
|
||||
VersionConstraint string
|
||||
Errors []error
|
||||
}
|
||||
|
||||
func (e *InvalidVersionError) Error() string {
|
||||
var errsStr strings.Builder
|
||||
for i, err := range e.Errors {
|
||||
if i > 0 {
|
||||
errsStr.WriteString("; ")
|
||||
}
|
||||
errsStr.WriteString(err.Error())
|
||||
}
|
||||
return fmt.Sprintf("%s version %s does not satisfy constraints %s: %s", ThisModulePath, e.AttestVersion, e.VersionConstraint, errsStr.String())
|
||||
}
|
||||
|
||||
func NewVersionChecker() *versionChecker {
|
||||
return &versionChecker{}
|
||||
}
|
||||
|
||||
type versionChecker struct{}
|
||||
|
||||
func (vc *versionChecker) CheckVersion(client TUFClient) error {
|
||||
var attestMod *debug.Module
|
||||
bi, ok := debug.ReadBuildInfo()
|
||||
if !ok {
|
||||
// if we can't read the build info, assume we're good. this should only happen if we're not running in a module
|
||||
return nil
|
||||
}
|
||||
if bi.Main.Path == ThisModulePath {
|
||||
attestMod = &bi.Main
|
||||
} else {
|
||||
for _, dep := range bi.Deps {
|
||||
if dep.Path == ThisModulePath {
|
||||
attestMod = dep
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if attestMod == nil {
|
||||
// if we can't find the attest dep, assume we're good. this should only happen in a test
|
||||
return nil
|
||||
}
|
||||
|
||||
attestVersion, err := semver.NewVersion(attestMod.Version)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse version %s: %w", attestMod.Version, err)
|
||||
}
|
||||
|
||||
// see https://github.com/Masterminds/semver/blob/v3.2.1/README.md#checking-version-constraints
|
||||
// for more information on the expected format of the version constraints in the TUF repo
|
||||
_, versionConstraintsBytes, err := client.DownloadTarget("version-constraints", "")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to download version-constraints: %w", err)
|
||||
}
|
||||
versionConstraints, err := semver.NewConstraint(string(versionConstraintsBytes))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse minimum version: %w", err)
|
||||
}
|
||||
|
||||
ok, errs := versionConstraints.Validate(attestVersion)
|
||||
if !ok {
|
||||
return &InvalidVersionError{
|
||||
AttestVersion: attestVersion.String(),
|
||||
VersionConstraint: versionConstraints.String(),
|
||||
Errors: errs,
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
48
test/testdata/local-policy-fail/doi/policy.rego
vendored
Normal file
48
test/testdata/local-policy-fail/doi/policy.rego
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
package attest
|
||||
|
||||
import rego.v1
|
||||
|
||||
keys := [{
|
||||
"id": "6b241993defaba26558c64f94a94303ce860e7ad9163d801495c91cf57197c75",
|
||||
"key": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZmicqYSY38DprGr42jU0V3ND0ROj\nzSRH1+yjsxhh0bi52Hh/DuOhrSq2KJ5a09lW3ybnDjljowbkof0Y1i9Oow==\n-----END PUBLIC KEY-----",
|
||||
"from": "2023-12-15T14:00:00Z",
|
||||
"to": null,
|
||||
# this key is still active
|
||||
"status": "active",
|
||||
"signing-format": "dssev1",
|
||||
}]
|
||||
|
||||
atts := union({
|
||||
attestations.attestation("https://slsa.dev/provenance/v0.2"),
|
||||
attestations.attestation("https://spdx.dev/Document"),
|
||||
})
|
||||
|
||||
statements contains s if {
|
||||
some att in atts
|
||||
s := attestations.verify_envelope(att, keys)
|
||||
}
|
||||
|
||||
subjects contains subject if {
|
||||
some statement in statements
|
||||
some subject in statement.subject
|
||||
}
|
||||
|
||||
violations contains v if {
|
||||
v := {
|
||||
"type": "missing_attestation",
|
||||
"description": "Attestation missing for subject",
|
||||
"attestation": null,
|
||||
"details": {},
|
||||
}
|
||||
}
|
||||
|
||||
result := {
|
||||
"success": false,
|
||||
"violations": violations,
|
||||
"summary": {
|
||||
"subjects": subjects,
|
||||
"slsa_levels": ["SLSA_BUILD_LEVEL_3"],
|
||||
"verifier": "docker-official-images",
|
||||
"policy_uri": "https://docker.com/official/policy/v0.1",
|
||||
},
|
||||
}
|
||||
@@ -5,12 +5,7 @@ policies:
|
||||
- origin:
|
||||
domain: docker.io
|
||||
prefix: library/
|
||||
name: test-images
|
||||
id: test-images
|
||||
description: Local test images
|
||||
location: doi
|
||||
|
||||
mirrors:
|
||||
- name: test-images
|
||||
mirror:
|
||||
domains: [localhost:5001]
|
||||
prefix: ""
|
||||
files:
|
||||
- path: doi/policy.rego
|
||||
39
test/testdata/local-policy-pass/doi/policy.rego
vendored
Normal file
39
test/testdata/local-policy-pass/doi/policy.rego
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
package attest
|
||||
|
||||
import rego.v1
|
||||
|
||||
keys := [{
|
||||
"id": "6b241993defaba26558c64f94a94303ce860e7ad9163d801495c91cf57197c75",
|
||||
"key": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZmicqYSY38DprGr42jU0V3ND0ROj\nzSRH1+yjsxhh0bi52Hh/DuOhrSq2KJ5a09lW3ybnDjljowbkof0Y1i9Oow==\n-----END PUBLIC KEY-----",
|
||||
"from": "2023-12-15T14:00:00Z",
|
||||
"to": null,
|
||||
# this key is still active
|
||||
"status": "active",
|
||||
"signing-format": "dssev1",
|
||||
}]
|
||||
|
||||
atts := union({
|
||||
attestations.attestation("https://slsa.dev/provenance/v0.2"),
|
||||
attestations.attestation("https://spdx.dev/Document"),
|
||||
})
|
||||
|
||||
statements contains s if {
|
||||
some att in atts
|
||||
s := attestations.verify_envelope(att, keys)
|
||||
}
|
||||
|
||||
subjects contains subject if {
|
||||
some statement in statements
|
||||
some subject in statement.subject
|
||||
}
|
||||
|
||||
result := {
|
||||
"success": true,
|
||||
"violations": set(),
|
||||
"summary": {
|
||||
"subjects": subjects,
|
||||
"slsa_levels": ["SLSA_BUILD_LEVEL_3"],
|
||||
"verifier": "docker-official-images",
|
||||
"policy_uri": "https://docker.com/official/policy/v0.1",
|
||||
},
|
||||
}
|
||||
11
test/testdata/local-policy-pass/mapping.yaml
vendored
Normal file
11
test/testdata/local-policy-pass/mapping.yaml
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
# map repos to policies
|
||||
version: v1
|
||||
kind: policy-mapping
|
||||
policies:
|
||||
- origin:
|
||||
domain: docker.io
|
||||
prefix: library/
|
||||
id: test-images
|
||||
description: Local test images
|
||||
files:
|
||||
- path: doi/policy.rego
|
||||
58
test/testdata/local-policy/doi/data.yaml
vendored
58
test/testdata/local-policy/doi/data.yaml
vendored
@@ -1,58 +0,0 @@
|
||||
config:
|
||||
doi:
|
||||
keys:
|
||||
- id: "f6a29392b1c08891ff456100aa448b4f6bf9c315850e11cc0883fe9c3c4412db"
|
||||
key: |
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE+XOm2uWjLJhpsJtHCFdGic26suOy
|
||||
mCl2pBgCof+AHGFZFca40JL833OT+nRSZJRMPKBGibWqsjFrLdRCkOB7bA==
|
||||
-----END PUBLIC KEY-----
|
||||
from: "2024-01-01T00:00:00Z"
|
||||
to: "2024-01-15T12:00:00Z"
|
||||
# this key was rotated at a planned time
|
||||
status: "rotated"
|
||||
signing-format: "dssev1"
|
||||
- id: "e6f4c70fbba21cbcac44915fff53fd2fdf90dd8849445795fe58014c2b5f8c64"
|
||||
key: |
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZSkTE3si/JkRbuLjaYraS3//YBnX
|
||||
8KtEcgdYKZQPl2DnSl4gPsu3KiVeEBWp5GK06IoZlcBAL3NF0OsUUP+yVg==
|
||||
-----END PUBLIC KEY-----
|
||||
from: "2024-01-15T12:00:00Z"
|
||||
to: "2024-01-15T14:00:00Z"
|
||||
# this key was leaked at a known time, so it revoked from that time
|
||||
# this behaves the same way as "rotated" but might give another failure message
|
||||
status: "revoked"
|
||||
signing-format: "dssev1"
|
||||
- id: "d45980c5cf39a5e1bab9febe3f16c1c0820b97a8fd061b0064e54b0826e856e4"
|
||||
key: |
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEafssq2x1EDQcKDZhuSrCOxWWl5D4
|
||||
JBa9iDJYDnLZp9kPKvv4RnD4rz7Ucfmd0l/zzM45qT29fSBTlguKmnOA8A==
|
||||
-----END PUBLIC KEY-----
|
||||
# this key was leaked at an unknown time, so it's completely distrusted
|
||||
distrust: true
|
||||
status: "revoked"
|
||||
signing-format: "dssev1"
|
||||
- id: "a0c296026645799b2a297913878e81b0aefff2a0c301e97232f717e14402f3e4"
|
||||
key: |
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEgH23D1i2+ZIOtVjmfB7iFvX8AhVN
|
||||
9CPJ4ie9axw+WRHozGnRy99U2dRge3zueBBg2MweF0zrToXGig2v3YOrdw==
|
||||
-----END PUBLIC KEY-----
|
||||
from: "2023-12-15T14:00:00Z"
|
||||
to: null
|
||||
# this key is still active
|
||||
status: "active"
|
||||
signing-format: "dssev1"
|
||||
- id: "b281835e00059de24fb06bd6db06eb0e4a33d7bd7210d7027c209f14b19e812a"
|
||||
key: |
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEgE4Jz6FrLc3lp/YRlbuwOjK4n6ac
|
||||
jVkSDAmFhi3Ir2Jy+cKeEB7iRPcLvBy9qoMZ9E93m1NdWY6KtDo+Qi52Rg==
|
||||
-----END PUBLIC KEY-----
|
||||
from: "2024-01-15T14:00:00Z"
|
||||
to: null
|
||||
# this key is still active
|
||||
status: "active"
|
||||
signing-format: "dssev1"
|
||||
49
test/testdata/local-policy/doi/policy.rego
vendored
49
test/testdata/local-policy/doi/policy.rego
vendored
@@ -1,49 +0,0 @@
|
||||
package docker
|
||||
|
||||
import rego.v1
|
||||
|
||||
import data.config
|
||||
|
||||
splitDigest := split(input.digest, ":")
|
||||
|
||||
digestType := splitDigest[0]
|
||||
|
||||
digest := splitDigest[1]
|
||||
|
||||
allow if {
|
||||
some env in attestations.attestation("https://slsa.dev/verification_summary/v0.1")
|
||||
some statement in verified_statements(config.doi.keys, env)
|
||||
}
|
||||
|
||||
|
||||
verified_statements(keys, env) := statements if {
|
||||
statements := {statement |
|
||||
statement := attestations.verify_envelope(env, keys)
|
||||
some subject in statement.subject
|
||||
valid_subject(subject)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
valid_subject(sub) if {
|
||||
print("valid_subject")
|
||||
print("sub.digest[digestType]:", sub.digest[digestType])
|
||||
print("digest", digest)
|
||||
sub.digest[digestType] == digest
|
||||
print("digest matches")
|
||||
valid_subject_name(sub.name)
|
||||
}
|
||||
|
||||
valid_subject_name(name) if {
|
||||
input.canonical
|
||||
print("is canonical, ignoring name")
|
||||
}
|
||||
|
||||
valid_subject_name(name) if {
|
||||
not input.canonical
|
||||
print("valid_subject_name...")
|
||||
print("name:", name)
|
||||
print("input.purl:", input.purl)
|
||||
name == input.purl
|
||||
print("name match")
|
||||
}
|
||||
25
test/testdata/local-policy/doi/policy_test.rego
vendored
25
test/testdata/local-policy/doi/policy_test.rego
vendored
@@ -1,25 +0,0 @@
|
||||
package docker
|
||||
import rego.v1
|
||||
|
||||
config := {"keys": []}
|
||||
envs := [{"env": "test"}]
|
||||
purl := "pkg:docker/library/alpine:1.2.3"
|
||||
|
||||
statement := {"subject": [{"name": purl, "digest": {"sha256": "dea014f47cd49d694d3a68564eb9e6ae38a7ee9624fd52ec05ccbef3f3fab8a0"}}]}
|
||||
input_digest := "sha256:dea014f47cd49d694d3a68564eb9e6ae38a7ee9624fd52ec05ccbef3f3fab8a0"
|
||||
|
||||
test_with_mock_data if {
|
||||
allow with attestations.attestation as envs
|
||||
with attestations.verify_envelope as statement
|
||||
with input.digest as input_digest
|
||||
with input.purl as purl
|
||||
with input.canonical as false
|
||||
}
|
||||
|
||||
layout_digest := "sha256:da8b190665956ea07890a0273e2a9c96bfe291662f08e2860e868eef69c34620"
|
||||
outout_purl := "pkg:docker/test-image@test?platform=linux%2Famd64"
|
||||
test_with_signed_oci_layout if {
|
||||
allow with input.digest as layout_digest
|
||||
with input.purl as outout_purl
|
||||
with input.canonical as false
|
||||
}
|
||||
5
test/testdata/test-signing-key.pem
vendored
Normal file
5
test/testdata/test-signing-key.pem
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
-----BEGIN EC PRIVATE KEY-----
|
||||
MHcCAQEEIKZEqmmd++eAY3bmPoBdY6nC2wLy4da2yeVZNKCp6Oj2oAoGCCqGSM49
|
||||
AwEHoUQDQgAEZmicqYSY38DprGr42jU0V3ND0ROjzSRH1+yjsxhh0bi52Hh/DuOh
|
||||
rSq2KJ5a09lW3ybnDjljowbkof0Y1i9Oow==
|
||||
-----END EC PRIVATE KEY-----
|
||||
Reference in New Issue
Block a user