65 Commits

Author SHA1 Message Date
Joel Kamp
d97d20eb93 Merge pull request #121 from docker/dependabot/go_modules/google.golang.org/api-0.191.0
feat(deps): bump google.golang.org/api from 0.190.0 to 0.191.0
2024-08-08 15:28:52 -05:00
Joel Kamp
42390b5fc2 Merge branch 'main' into dependabot/go_modules/google.golang.org/api-0.191.0 2024-08-08 15:18:54 -05:00
Joel Kamp
70e6345942 Merge pull request #119 from docker/dependabot/go_modules/github.com/sigstore/cosign/v2-2.4.0
feat(deps): bump github.com/sigstore/cosign/v2 from 2.3.0 to 2.4.0
2024-08-08 15:18:19 -05:00
dependabot[bot]
f853875eea feat(deps): bump github.com/sigstore/cosign/v2 from 2.3.0 to 2.4.0
Bumps [github.com/sigstore/cosign/v2](https://github.com/sigstore/cosign) from 2.3.0 to 2.4.0.
- [Release notes](https://github.com/sigstore/cosign/releases)
- [Changelog](https://github.com/sigstore/cosign/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sigstore/cosign/compare/v2.3.0...v2.4.0)

---
updated-dependencies:
- dependency-name: github.com/sigstore/cosign/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-08 20:11:27 +00:00
Joel Kamp
050497e5a7 Merge pull request #118 from docker/dependabot/go_modules/github.com/sigstore/sigstore/pkg/signature/kms/aws-1.8.8
feat(deps): bump github.com/sigstore/sigstore/pkg/signature/kms/aws from 1.8.7 to 1.8.8
2024-08-08 15:09:20 -05:00
dependabot[bot]
d69334a1e6 feat(deps): bump google.golang.org/api from 0.190.0 to 0.191.0
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.190.0 to 0.191.0.
- [Release notes](https://github.com/googleapis/google-api-go-client/releases)
- [Changelog](https://github.com/googleapis/google-api-go-client/blob/main/CHANGES.md)
- [Commits](https://github.com/googleapis/google-api-go-client/compare/v0.190.0...v0.191.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-08 20:00:21 +00:00
dependabot[bot]
a84268b133 feat(deps): bump github.com/sigstore/sigstore/pkg/signature/kms/aws
Bumps [github.com/sigstore/sigstore/pkg/signature/kms/aws](https://github.com/sigstore/sigstore) from 1.8.7 to 1.8.8.
- [Release notes](https://github.com/sigstore/sigstore/releases)
- [Commits](https://github.com/sigstore/sigstore/compare/v1.8.7...v1.8.8)

---
updated-dependencies:
- dependency-name: github.com/sigstore/sigstore/pkg/signature/kms/aws
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-08 20:00:19 +00:00
Joel Kamp
2cd2e2da96 Merge pull request #117 from docker/dependabot/go_modules/github.com/sigstore/sigstore/pkg/signature/kms/gcp-1.8.8
feat(deps): bump github.com/sigstore/sigstore/pkg/signature/kms/gcp from 1.8.7 to 1.8.8
2024-08-08 14:58:19 -05:00
Joel Kamp
f1ece6893f Merge branch 'main' into dependabot/go_modules/github.com/sigstore/sigstore/pkg/signature/kms/gcp-1.8.8 2024-08-08 14:48:59 -05:00
Joel Kamp
116b9ea770 Merge pull request #120 from docker/refactor-referrers-output
feat!: push attestation artifacts by digest
2024-08-08 14:48:42 -05:00
mrjoelkamp
d291912208 refactor!: move oci output from mirror to oci pkg
BREAKING_CHANGE: output methods to save and push images are now part of the oci pkg
2024-08-08 14:23:46 -05:00
mrjoelkamp
9cad88a687 fix: EmptyConfigImage digest 2024-08-08 13:06:56 -05:00
mrjoelkamp
77ccbc097b feat: use docker/go-containerregistry 2024-08-08 11:37:45 -05:00
mrjoelkamp
45927967c8 test: debug push 2024-08-08 10:04:41 -05:00
mrjoelkamp
9aa56e564d feat: push attestation artifacts by digest 2024-08-07 15:19:48 -05:00
dependabot[bot]
6d0a6de520 feat(deps): bump github.com/sigstore/sigstore/pkg/signature/kms/gcp
Bumps [github.com/sigstore/sigstore/pkg/signature/kms/gcp](https://github.com/sigstore/sigstore) from 1.8.7 to 1.8.8.
- [Release notes](https://github.com/sigstore/sigstore/releases)
- [Commits](https://github.com/sigstore/sigstore/compare/v1.8.7...v1.8.8)

---
updated-dependencies:
- dependency-name: github.com/sigstore/sigstore/pkg/signature/kms/gcp
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-07 09:10:08 +00:00
Joel Kamp
8767951fa2 Merge pull request #114 from docker/dependabot/go_modules/google.golang.org/api-0.190.0
feat(deps): bump google.golang.org/api from 0.189.0 to 0.190.0
2024-08-06 10:15:47 -05:00
Joel Kamp
f18b5877d3 Merge branch 'main' into dependabot/go_modules/google.golang.org/api-0.190.0 2024-08-06 10:09:55 -05:00
Joel Kamp
93fd9daeb9 Merge pull request #116 from docker/dependabot/go_modules/github.com/open-policy-agent/opa-0.67.1
feat(deps): bump github.com/open-policy-agent/opa from 0.67.0 to 0.67.1
2024-08-06 10:08:32 -05:00
Joel Kamp
5df79de1c7 Merge branch 'main' into dependabot/go_modules/github.com/open-policy-agent/opa-0.67.1 2024-08-06 09:37:08 -05:00
Joel Kamp
5b5e43b07a Merge pull request #113 from docker/fix-oci-layout-referrers
fix: let OCI layouts use referrers attestations
2024-08-06 09:24:53 -05:00
dependabot[bot]
4c5135eb1b feat(deps): bump github.com/open-policy-agent/opa from 0.67.0 to 0.67.1
Bumps [github.com/open-policy-agent/opa](https://github.com/open-policy-agent/opa) from 0.67.0 to 0.67.1.
- [Release notes](https://github.com/open-policy-agent/opa/releases)
- [Changelog](https://github.com/open-policy-agent/opa/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-policy-agent/opa/compare/v0.67.0...v0.67.1)

---
updated-dependencies:
- dependency-name: github.com/open-policy-agent/opa
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-06 09:01:35 +00:00
mrjoelkamp
0133423f0d feat: add nil pointer test 2024-08-05 16:50:40 -05:00
mrjoelkamp
501b9b442d feat: add CreateAttestationResolver tests 2024-08-05 16:31:39 -05:00
Joel Kamp
d84ed4821c Merge branch 'main' into fix-oci-layout-referrers 2024-08-05 16:01:36 -05:00
Joel Kamp
c9e2ddd448 Merge pull request #115 from docker/chore--disable-codecov-patch-status
chore: disable codecov patch status
2024-08-05 16:01:25 -05:00
mrjoelkamp
165241de42 chore: disable codecov patch status 2024-08-05 15:56:06 -05:00
mrjoelkamp
c7d17faf05 fix: layout attestation resolver 2024-08-05 15:32:24 -05:00
mrjoelkamp
58021646e3 feat: add oci layout test 2024-08-05 11:24:28 -05:00
mrjoelkamp
3e7a85e9b8 fix: nil pointer dereference 2024-08-05 11:24:05 -05:00
mrjoelkamp
bb7a9a257e chore: remove duplicate code 2024-08-05 11:23:32 -05:00
mrjoelkamp
c690d1090c chore: use prefix const 2024-08-05 11:22:49 -05:00
mrjoelkamp
1d1c258f9c fix: referrers resolver only works for registry resolvers 2024-08-05 10:20:40 -05:00
mrjoelkamp
5d096e226f refactor: fix import cycle for mock resolver 2024-08-05 10:19:50 -05:00
dependabot[bot]
7fc7ceaba0 feat(deps): bump google.golang.org/api from 0.189.0 to 0.190.0
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.189.0 to 0.190.0.
- [Release notes](https://github.com/googleapis/google-api-go-client/releases)
- [Changelog](https://github.com/googleapis/google-api-go-client/blob/main/CHANGES.md)
- [Commits](https://github.com/googleapis/google-api-go-client/compare/v0.189.0...v0.190.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-02 08:34:34 +00:00
mrjoelkamp
78ec0b7666 fix: use referrers repo img spec and resolver 2024-08-01 15:24:35 -05:00
Joel Kamp
053f764b8f Merge branch 'main' into fix-oci-layout-referrers 2024-08-01 13:48:53 -05:00
mrjoelkamp
ad3b8b9e49 fix: let OCI layouts use referrers attestations 2024-08-01 13:41:49 -05:00
James Carnegie
9582e69968 fix: standardize casing of initialisms (#112)
* fix: standardize casing of initialisms
* fix: rename intoto -> inToto and Intoto to InToto
* fix: fix all linting errors
2024-08-01 15:35:15 +01:00
James Carnegie
b0b37f73f3 fix: upgrade go to 1.22.x for testing (#110) 2024-08-01 15:34:35 +01:00
dependabot[bot]
d21fc7853c feat(deps): bump github.com/docker/docker (#105)
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 27.0.3+incompatible to 27.1.0+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v27.0.3...v27.1.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-31 16:50:39 +01:00
Jonny Stoten
008c14e3f3 chore: reformat with gofumpt (#109)
* Reformat with gofumpt

* Suppress issue about laxer perms than 0600

---------

Co-authored-by: Joel Kamp <joel.kamp@docker.com>
2024-07-31 15:48:00 +01:00
Jonny Stoten
fbe9a0c726 Add linting github action (#107)
* Add golangci config

* Add lint action
2024-07-31 14:42:19 +01:00
James Carnegie
9571f17476 fix: add auth to referrers image retrieval (#108) 2024-07-31 14:31:29 +01:00
James Carnegie
63246e2b96 docs: add contribution guidelines (#102)
* docs: add contribution guidelines
2024-07-31 11:08:59 +01:00
Jonny Stoten
91fdf7ece6 Remove dependency on github.com/pkg/errors (#106) 2024-07-30 14:07:19 +01:00
James Carnegie
a1c7bbb991 debt: remove goyaml. Fixup directives (#103) 2024-07-29 17:21:15 +01:00
Jonny Stoten
2ffdfdf0eb docs: first cut of a new README (#99)
Lots of this is taken from image-signer-verifier's README. The stuff on
policy is all new.

Co-authored-by: James Carnegie <kipz@users.noreply.github.com>
2024-07-29 16:43:31 +01:00
dependabot[bot]
781a738b54 feat(deps): bump github.com/open-policy-agent/opa from 0.66.0 to 0.67.0 (#101)
Bumps [github.com/open-policy-agent/opa](https://github.com/open-policy-agent/opa) from 0.66.0 to 0.67.0.
- [Release notes](https://github.com/open-policy-agent/opa/releases)
- [Changelog](https://github.com/open-policy-agent/opa/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-policy-agent/opa/compare/v0.66.0...v0.67.0)

---
updated-dependencies:
- dependency-name: github.com/open-policy-agent/opa
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-26 09:46:59 +01:00
James Carnegie
c735bb0d3f feat: roll out updates on release (#100)
* feat: roll out updates on release

* Use app token. Fix repo
2024-07-25 16:15:35 +01:00
dependabot[bot]
bd2c4d7d8a feat(deps): bump github.com/sigstore/cosign/v2 from 2.2.4 to 2.3.0 (#98)
* feat(deps): bump github.com/sigstore/cosign/v2 from 2.2.4 to 2.3.0

Bumps [github.com/sigstore/cosign/v2](https://github.com/sigstore/cosign) from 2.2.4 to 2.3.0.
- [Release notes](https://github.com/sigstore/cosign/releases)
- [Changelog](https://github.com/sigstore/cosign/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sigstore/cosign/compare/v2.2.4...v2.3.0)

---
updated-dependencies:
- dependency-name: github.com/sigstore/cosign/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* update to go 1.22.5

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mrjoelkamp <joel.kamp@docker.com>
2024-07-24 13:30:56 +01:00
dependabot[bot]
03ba59c6b9 feat(deps): bump google.golang.org/api from 0.188.0 to 0.189.0 (#97) 2024-07-23 19:08:01 +00:00
dependabot[bot]
ac82c65d7c feat(deps): bump github.com/aws/aws-sdk-go-v2/config (#93) 2024-07-23 19:03:53 +00:00
Joel Kamp
24a81bbfe1 Merge pull request #96 from docker/chore-update-dev-root
chore: update dev root
2024-07-23 08:03:03 -05:00
mrjoelkamp
1e3c120272 fix: test targets file no ext 2024-07-22 16:03:23 -05:00
mrjoelkamp
d252a7f4d7 chore: update oci test data 2024-07-22 15:57:35 -05:00
mrjoelkamp
02421f8cf5 chore: update http test data 2024-07-22 15:52:14 -05:00
mrjoelkamp
a6cd978bc0 chore: update dev root 2024-07-22 15:23:28 -05:00
James Carnegie
efb73f4cae Use DSSE artifactType in referrers (#95)
* bug: Use DSSE media types for artifactType

* Don't serialize DSSE extension if not present

* Update pkg/attestation/types.go

Co-authored-by: Joel Kamp <joel.kamp@docker.com>

* Don't error on no referrers

---------

Co-authored-by: Joel Kamp <joel.kamp@docker.com>
2024-07-22 18:17:12 +01:00
James Carnegie
5e68d94ad4 set artifactType correctly for referrers fallback (#94)
* set artifactType correctly for referrers fallback
2024-07-19 16:39:35 +01:00
dependabot[bot]
10d4f129b5 feat(deps): bump github.com/theupdateframework/go-tuf/v2 from 2.0.0-20240504210453-5a634eb214ae to 2.0.0 (#92)
* feat(deps): bump github.com/theupdateframework/go-tuf/v2

Bumps [github.com/theupdateframework/go-tuf/v2](https://github.com/theupdateframework/go-tuf) from 2.0.0-20240504210453-5a634eb214ae to 2.0.0.
- [Release notes](https://github.com/theupdateframework/go-tuf/releases)
- [Changelog](https://github.com/theupdateframework/go-tuf/blob/master/.goreleaser.yaml)
- [Commits](https://github.com/theupdateframework/go-tuf/commits/v2.0.0)

---
updated-dependencies:
- dependency-name: github.com/theupdateframework/go-tuf/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* Tidy go.mod

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jonny Stoten <jonny.stoten@docker.com>
2024-07-17 17:01:09 +01:00
James Carnegie
de5668aca2 chore: fix linting errors (#91) 2024-07-16 12:52:33 +01:00
dependabot[bot]
79566ff70a feat(deps): bump github.com/sigstore/sigstore/pkg/signature/kms/aws (#88)
Bumps [github.com/sigstore/sigstore/pkg/signature/kms/aws](https://github.com/sigstore/sigstore) from 1.8.6 to 1.8.7.
- [Release notes](https://github.com/sigstore/sigstore/releases)
- [Commits](https://github.com/sigstore/sigstore/compare/v1.8.6...v1.8.7)

---
updated-dependencies:
- dependency-name: github.com/sigstore/sigstore/pkg/signature/kms/aws
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-16 11:46:03 +01:00
dependabot[bot]
d01395144b feat(deps): bump github.com/sigstore/sigstore/pkg/signature/kms/gcp (#89)
Bumps [github.com/sigstore/sigstore/pkg/signature/kms/gcp](https://github.com/sigstore/sigstore) from 1.8.6 to 1.8.7.
- [Release notes](https://github.com/sigstore/sigstore/releases)
- [Commits](https://github.com/sigstore/sigstore/compare/v1.8.6...v1.8.7)

---
updated-dependencies:
- dependency-name: github.com/sigstore/sigstore/pkg/signature/kms/gcp
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-16 11:32:17 +01:00
James Carnegie
065b354d3c Make referrers attestations OCI compliant (#80)
* Single attestation when creating VSA

* Create single layer images for referrers attestations

* Move mock to test package. Add artifacts test

* Add test for envelope detection

* Add tests for image/index saving

* Add mirror tests

* Remove AttestationImage field from AttestationManifest

* Update naming. strictReferers != laxReferrers

* Add specific test for SaveReferrers
2024-07-16 10:05:17 +01:00
138 changed files with 3301 additions and 1490 deletions

19
.github/workflows/lint.yml vendored Normal file
View File

@@ -0,0 +1,19 @@
name: lint code
on:
pull_request:
workflow_dispatch:
jobs:
lint:
runs-on: ubuntu-latest
steps:
- name: Set git to use LF
run: git config --global core.autocrlf false
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: 1.22.x
- name: golangci-lint
uses: golangci/golangci-lint-action@v6
with:
version: v1.59
only-new-issues: true

24
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,24 @@
name: release
on:
release:
types: [published]
jobs:
trigger_attest_update:
name: Update attest lib - ALL
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Generate GitHub App Token
id: app-token
uses: actions/create-github-app-token@31c86eb3b33c9b601a1f60f98dcbfd1d70f379b4 # v1.10.3
with:
app-id: ${{ vars.ATTEST_RELEASE_APP_ID }}
private-key: ${{ secrets.ATTEST_RELEASE_APP_PRIVATE_KEY }}
repositories: "attest-actions"
- name: Send repository_dispatch event
uses: peter-evans/repository-dispatch@v3.0.0
with:
token: ${{ steps.app-token.outputs.token }}
event-type: update_attest_all
repository: docker/attest-actions
client-payload: '{"attest_version": "${{ github.ref_name }}"}'

View File

@@ -12,7 +12,7 @@ jobs:
id-token: write
strategy:
matrix:
go-version: [1.21.x]
go-version: [1.22.x]
# temp disable windows tests see https://github.com/docker/image-signer-verifier/pull/154
# os: [ubuntu-latest, macos-latest, windows-latest]
os: [ubuntu-latest, macos-latest]

36
.golangci.yaml Normal file
View File

@@ -0,0 +1,36 @@
run:
timeout: 5m
go: "1.22"
linters-settings:
gocritic:
enabled-tags:
- performance
lll:
line-length: 200
misspell:
locale: US
linters:
disable-all: true
enable:
- errcheck
- forcetypeassert
- gocritic
- goconst
- godot
- gofmt
- gofumpt
- goimports
- gosec
- gosimple
- govet
- importas
- ineffassign
- misspell
- revive # replacement for golint
- staticcheck
- typecheck
- unused
- whitespace

85
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,85 @@
# Contribute to the attest Library
Want to hack on the attest library? This guide will help you to find out how to contribute.
This page contains information about reporting issues as well as some tips and guidelines useful to experienced open source contributors. Finally, make sure you read our [community guidelines](#community-guidelines) before you start participating.
## Topics
* [Reporting Security Issues](#reporting-security-issues)
* [Design and Cleanup Proposals](#design-and-cleanup-proposals)
* [Reporting Issues](#reporting-other-issues)
* [Quick Contribution Tips and Guidelines](#quick-contribution-tips-and-guidelines)
* [Community Guidelines](#community-guidelines)
## Reporting security issues
The attest maintainers take security seriously. If you discover a security issue, please bring it to their attention right away!
Please **DO NOT** file a public issue, instead send your report privately to [security@docker.com](mailto:security@docker.com).
Security reports are greatly appreciated and we will publicly thank you for it, although we keep your name confidential if you request it. We also like to send gifts—if you're into schwag, make sure to let us know. We currently do not offer a paid security bounty program, but are not ruling it out in the future.
## Reporting other issues
A great way to contribute to the project is to send a detailed report when you encounter an issue. We always appreciate a well-written, thorough bug report, and will thank you for it!
Check that [our issue database](https://github.com/docker/attest/issues) doesn't already include that problem or suggestion before submitting an issue. If you find a match, you can use the "subscribe" button to get notified on updates. Do *not* leave random "+1" or "I have this too" comments. Those comments can become annoying very quickly. Instead, use [GitHub reactions](https://docs.github.com/en/free-pro-team@latest/github/writing-on-github/using-emojis).
### How to report a bug
* **Use a clear and descriptive title** for the issue to identify the problem.
* **Describe the exact steps which reproduce the problem** in as many details as possible. When listing steps, **don't just say what you did, but explain how you did it**.
* **Provide specific examples to demonstrate the steps**. Include links to files or GitHub projects, or copy/pasteable snippets, which you use in those examples. If you're providing snippets in the issue, use [Markdown code blocks](https://help.github.com/articles/markdown-basics/#multiple-lines).
* **Describe the behavior you observed after following the steps** and point out what exactly is the problem with that behavior.
* **Explain which behavior you expected to see instead and why.**
* **Include screenshots and animated GIFs** which show you following the described steps and clearly demonstrate the problem.
* **If the problem is related to performance or memory**, include a [CPU profile capture](https://blog.golang.org/profiling-go-programs) with your report.
* **If the problem wasn't triggered by a specific action**, describe what you were doing before the problem happened.
* **Include the version of attest you are using**.
* **Include the name and version of the OS you're using**.
## Quick contribution tips and guidelines
This section gives a brief overview of how to propose a change to attest.
### Contribution flow
1. Fork the repository on GitHub.
2. Create a topic branch from where you want to base your work.
3. Make commits of logical units.
4. Make sure your commit messages are in the proper format (see below).
5. Push your changes to a topic branch in your fork of the repository.
6. Submit a pull request to the original repository.
### Format of the commit message
We follow a rough convention for commit messages [borrowed from Angular](https://www.conventionalcommits.org/en/v1.0.0/).
- **feat**: A new feature
- **fix**: A bug fix
- **docs**: Documentation only changes
- **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
- **refactor**: A code change that neither fixes a bug nor adds a feature
- **perf**: A code change that improves performance
- **test**: Adding missing or correcting existing tests
- **chore**: Changes to the build process or auxiliary tools and libraries such as documentation generation
### Code review process
All submissions, including submissions by project members, require review. We use GitHub pull requests for this purpose.
### Tips for contributors
1. All code should be formatted with `gofmt -s`.
2. All code should pass the default levels of [`golint`](https://github.com/golang/lint).
3. All code should follow the guidelines covered in [Effective Go](http://golang.org/doc/effective_go.html) and [Go Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments).
4. Comment the code. Tell us the why, the history, and the context.
5. Document _all_ public declarations and methods. Declare expectations, caveats, and anything else that may be important. If a type gets exported, having the comments already there will ensure it's ready.
6. Variable name length should be proportional to its context and no longer. `noCommaALongVariableNameLikeThisIsNotMoreClearWhenASimpleCommentWouldDo`. In practice, short methods will have short variable names and globals will have longer names.
7. No underscores in package names. If you need a compound name, step back, and re-examine why you need a compound name. If you still think you need a compound name, lose the underscore.
8. No utils or helpers packages. If a function is not general enough to warrant its own package, it has not been written generally enough to be a part of a util package. Just leave it unexported and well-documented.
9. All tests should run with `go test` and outside tooling should not be required. No, we don't need another unit testing framework.
10. Even though we call these "rules" above, they are actually just guidelines. Since you've read all the rules, you now know that.
If you are having trouble getting into the mood of idiomatic Go, we recommend reading through [Effective Go](https://go.dev/doc/effective_go). The [Go Blog](https://go.dev/blog/) is also a great resource. Drinking the kool-aid is a lot easier than going thirsty.

376
README.md
View File

@@ -1,16 +1,372 @@
# attest
library to create, verify, and evaluate policy for attestations on container images
# `attest`
<div align="center">
Library to create attestation signatures on container images, and verify images against policy.
[![Go Reference](https://pkg.go.dev/badge/github.com/docker/attest.svg)](https://pkg.go.dev/github.com/docker/attest)
[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/docker/attest/test.yml?branch=main)](https://github.com/docker/attest/actions/workflows/test.yml)
[![codecov](https://codecov.io/gh/docker/attest/graph/badge.svg?token=cGT0f1ACKg)](https://codecov.io/gh/docker/attest)
# usage
## signing and verifying attestations
See [example_sign_test.go](./pkg/attest/example_sign_test.go)
</div>
See [example_verify_test.go](./pkg/attest/example_verify_test.go)
# Table of Contents
## mirroring TUF repositories to OCI
See [example_mirror_test.go](./pkg/mirror/example_mirror_test.go)
- [`attest`](#attest)
- [Table of Contents](#table-of-contents)
- [What is this?](#what-is-this)
- [Features](#features)
- [Installation](#installation)
- [Usage](#usage)
- [Verifying Image Attestations](#verifying-image-attestations)
- [Signing Attestations](#signing-attestations)
- [Rego Policy](#rego-policy)
- [Writing Policy](#writing-policy)
- [Input](#input)
- [Builtin Functions](#builtin-functions)
- [Policy Mapping](#policy-mapping)
- [Public Key IDs](#public-key-ids)
- [Transparency Logging](#transparency-logging)
- [Verification Summary Attestation (VSA)](#verification-summary-attestation-vsa)
- [Example VSA](#example-vsa)
- [API Reference](#api-reference)
- [Project Layout](#project-layout)
- [Versioning](#versioning)
### using `go-tuf` OCI registry client
See [example_registry_test.go](./pkg/tuf/example_registry_test.go)
# What is this?
`attest` is a library for signing and verifying [in-toto](https://in-toto.io/) attestations on container images.
Examples of attestations include statements about the provenance and SBOM of an image.
This library can be used to verify these attestations using Rego policy.
Policy can be used to check whether an attestation is correctly signed, and that the contents of the attestation are correct.
Our overall goal with this project is adoption of the ideas into other open-source projects, rather than to create another standalone tool.
It would be a great outcome if this library was no longer needed because the functionality was built into other tools.
# Features
- Sign in-toto attestations
- Push attestations to container registries using OCI 1.1 compatible artifacts
- Verify attestations on container images using Rego policy and attestations fetched using OCI 1.1 referrers
# Installation
```shell
$ go get github.com/docker/attest
```
# Usage
## Verifying Image Attestations
An image's attestations can be verified against a policy using the `attest.Verify` function.
This function takes an [oci.ImageSpec](https://github.com/docker/attest/blob/781a738b54b9549c1dabfd7ea3f7ea582514ddec/pkg/oci/types.go#L35-L41) for the image to verify, and a set of options for policy resolution.
By default, the policy is resolved from the [the Docker TUF repository](https://github.com/docker/tuf), but the options can be used to specify an alternative TUF repository, a local policy directory, and/or a policy ID to use.
See [Policy Mapping](#policy-mapping) for more details.
The `attest.Verify` function returns a `VerificationSummary` object, which contains the results of the policy evaluation.
See [example_verify_test.go](./pkg/attest/example_verify_test.go) for an example of how to verify an image against a policy.
## Signing Attestations
in-toto statements can be signed directly using the `attestation.SignInTotoStatement` function.
This function takes a statement and DSSE signer, and returns a signed DSSE envelope containing a copy of the original statement.
For the common use case of signing a statement and adding it to a manifest, e.g. for pushing to a registry as a referrer to the image being attested, the `attestation.AttestationManifest` type can be used.
See [example_attestation_manifest_test.go](./pkg/attestation/example_attestation_manifest_test.go)
See also [example_sign_test.go](./pkg/attest/example_sign_test.go) for an example of how to sign all attached in-toto statements on an image, e.g. those produced by buildkit.
# Rego Policy
An image policy consists of one or more `rego` files and, optionally, `json` or `yaml` data files.
The policies for trusted namespaces `docker.io/docker` and `docker.io/library` are stored in [the Docker TUF root](https://github.com/docker/tuf) under the `docker` and `doi` target sub-directories respectively.
## Writing Policy
`attest` uses [Open Policy Agent](https://www.openpolicyagent.org/) (OPA) for policy evaluation, and policies are written in Rego.
A full guide to writing Rego policies is available in the [Rego documentation](https://www.openpolicyagent.org/docs/latest/policy-language/).
For attest, a policy must contain at a minimum a `result` rule in a package called `attest` that returns an object matching the schema defined by the [`policy.Result`](https://github.com/docker/attest/blob/bd2c4d7d8aa497754b674412b09628be8d02fab5/pkg/policy/types.go#L23-L27) struct.
For example:
```rego
package attest
import rego.v1
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",
},
}
```
The meanings of the fields in the `result` object are as follows:
- `success` (bool): whether the policy passes
- `violations` (set): a set of strings describing any policy violations
- `summary` (object): a summary of the policy evaluation, used to construct a Verification Summary Attestation (VSA)
- `subjects` (set): a set of strings representing the subjects of each attestation that was evaluated
- `slsa_levels` (list): a list of strings representing the SLSA levels that the policy complies with
- `verifier` (string): the entity that verified the policy
- `policy_uri` (string): the URI of the policy
The `violations` set may contain policy violations even if `success` is `true`.
This can be useful if there are attestations that are invalid, but are not required by the policy.
### Input
The input to the policy is an object with the following fields:
- `digest` (string): the digest of the image being verified
- `purl` (string): the package URL of the image being verified
- `is_canonical` (bool): whether the image being verified was referenced by a 'canonical' name, i.e. one that contains a digest
### Builtin Functions
There are two builtin functions provided by `attest` that can be used to help with policy evaluation:
- `attest.fetch(predicate_type)`: fetches all attestations for the input image with the given predicate type.
For example, `attest.fetch("https://spdx.dev/Document")` will fetch all SPDX SBOM attestations for the input image.
- `attest.verify(attestation, options)`: verifies the DSSE envelope of the given attestation, and returns the statement.
The options object can contain the following fields:
- `keys` (array): keys to use for signature verification. Each key contains the following fields:
- `id` (string): the key ID as specified in [Public Key IDs](#public-key-ids)
- `key` (string): the PEM-encoded public key
- `from` (string): the time from which the key is valid, or `null` if the key was always valid (default: `null`)
- `status` (string): `active` if the key is active, otherwise the reason the key is inactive.
This is only used in error messages if the `from` date is in the past
- `distrust` (bool): whether the key should be distrusted (default: `false`).
If `true`, the key will be considered invalid
- `signing-format` (string): the format of the signing key, must be `dssev1`
- `skip_tl` (bool): whether to skip transparency log entry verification (see [Transparency Logging](#transparency-logging)) (default: `false`)
Both `attest.fetch` and `attest.verify` return an object with the following fields:
- `value`: the return value of the function if successful
- `error`: an error message if the function failed
This is to allow the policy to easily construct a violation if an error occurs, which isn't usually possible with custom functions in Rego.
The return value of `attest.fetch` is an attestation which can be passed to `attest.verify`.
## Policy Mapping
A `mapping.yaml` file is stored at the root of TUF targets and contains the mapping from repository name to files containing the corresponding policy.
A simple mapping file might look like this:
```yaml
version: v1
kind: policy-mapping
policies:
- id: docker-official-images
description: Docker Official Images
files:
- path: doi/policy.rego
rules:
- pattern: "^docker[.]io/library/(.*)$"
policy-id: docker-official-images
```
The `policies` section contains a list of policies, each with an `id` and a `description`, and a list of `files` containing the policy.
The `rules` section contains a list of rules that map regex expressions to policies.
If the `pattern` regex matches the repository name, the policy with the `policy-id` is used to evaluate the image.
In the above example, any repository in the `docker.io/library` namespace will be evaluated against the policy in `doi/policy.rego`.
Sometimes it is necessary to rewrite the repository name before evaluating the policy.
This can be useful when the repository name which is used to reference the image is different from the repository name in the attestations.
For example, when mirroring images from a public registry to a private registry, the repository name in the attestations will be the public registry, but the image will be referenced by the name of the private registry.
An example of a mapping file with rewrite rules might look like this:
```yaml
version: v1
kind: policy-mapping
policies:
- id: docker-official-images
description: Docker Official Images
files:
- path: doi/policy.rego
rules:
- pattern: "^docker[.]io/library/(.*)$"
policy-id: docker-official-images
- pattern: "^public[.]ecr[.]aws/docker/library/(.*)$"
rewrite: docker.io/library/$1
```
As before, any repository in the `docker.io/library` namespace will be evaluated against the policy in `doi/policy.rego`.
The second rule will rewrite any repository in the `public.ecr.aws/docker/library` namespace to `docker.io/library`.
This means two things:
1. The rules are evaluated again using the rewritten repository name until a policy is found (in this case the first rule will match); and
2. The rewritten name is passed into the actual policy when it is evaluated.
The `rewrite` field is not a simple string replacement, but a regex replacement.
This means that the `rewrite` field can contain capture groups that are referenced in the `pattern` field.
For example, the `rewrite` field in the example above contains `$1`, which is a reference to the first capture group in the `pattern` field.
> [!IMPORTANT]
> It's important to remember to escape the `.` character in the `pattern` field, as it is a special character in regex.
> This is why the `.` character is surrounded by `[]` in the example above.
>
> It's also important to make use of the `^` and `$` characters in the `pattern` field to ensure that the regex matches the entire repository name.
> This is to prevent the regex from matching a subset of the repository name, e.g. `docker.io/library` matching `notdocker.io/library`.
Local policy can also be specified via a local `mapping.yaml`, which can be used to create new mirrors of policies described in the Docker TUF root, as well as describing entirely independent policies. For example:
```golang
// configure policy options
opts := &policy.PolicyOptions{
TufClient: tufClient,
LocalPolicyDir: "<policy-dir>", // overrides TUF policy for local policy files if set
PolicyId: "<policy-id>", // set to ignore policy mapping and select a policy by id
}
src, err := oci.ParseImageSpec(image, oci.WithPlatform(platform))
if err != nil {
panic(err)
}
// verify attestations
result, err := attest.Verify(context.Background(), src, opts)
if err != nil {
panic(err)
}
```
where `<policy-dir>` is a directory containing a `mapping.yaml` file, and any policy files referenced in the `mapping.yaml`. For example:
```
├── myimages
│ ├── data.yaml
| ├── keys.yaml
│ └── policy.rego
└── mapping.yaml
```
> [!NOTE]
>
> `PolicyId` can also be set to select a policy by ID, completely ignoring the `rules` section of the mapping file.
The rules section of a local `mapping.yaml` can refer to the policies described in the `mapping.yaml` file in the Docker TUF root to specify additional mirrors to which the referenced policy can be applied.
For example, it might be desirable to mirror `docker.io/library` to a local registry for testing:
```yaml
version: v1
kind: policy-mapping
rules:
- pattern: "^localhost:5001/(.*)$"
rewrite: docker.io/library/$1
```
The rewritten repository name will match the `docker-official-images` polict in the TUF managed `mapping.yaml`.
> [!WARNING]
> Local `mapping.yaml` policies take precendence over TUF managed policies, so for example, it's possible to apply a custom policy to `docker.io/library` namespace:
>
> ```yaml
> version: v1
> kind: policy-mapping
> policies:
> - id: mydoi
> description: my doi policy
> files:
> - path: "mypolicy.rego"
>
> rules:
> - pattern: "^docker[.]io/library/(.*)$"
> policy-id: mydoi
> ```
# Public Key IDs
When signing attestations, a key-id is generated from the public key and added to envelope.
This is used at verification time to look up the public key.
To generate a key-id from a public key, use `openssl` as follows:
```shell
openssl pkey -in <public-key.pem> -pubin -outform DER | openssl dgst -sha256
```
# Transparency Logging
`attest` supports transparency logging for attestation signatures.
This serves two purposes:
1. the transparency log is a mechanism to ensure that all attestations are logged in a tamper-evident way, and that the logs are publicly auditable; and
2. the transparency log is a trusted source of timestamps for attestations, which allows signatures to be verified even if the key used to sign the attestation has expired.
By default, transparency logging is enabled and the logs are stored in the [public-good Rekor](https://docs.sigstore.dev/logging/overview/) instance.
Another transparency log can be used by creating an implementation of the [tl.TL](https://github.com/docker/attest/blob/781a738b54b9549c1dabfd7ea3f7ea582514ddec/pkg/tlog/tl.go#L57-L62) interface and using [`tl.WithTL`](https://github.com/docker/attest/blob/781a738b54b9549c1dabfd7ea3f7ea582514ddec/pkg/tlog/tl.go#L37) to set in on a context.
Alternatively, transparency logging can be disabled when signing by using `SkipTL` in the `SigningOptions`, and when verifying by using `skip_tl` in the options to `attest.verify` in the Rego policy.
# Verification Summary Attestation (VSA)
Verification of attestations can be expensive, especially when the attestations are large.
For example, an SBOM attestation can be several megabytes in size.
An alternative to consumers verifying the full attestation is to have a trusted entity verify the attestation and publish a [SLSA Verification Summary Attestation](https://slsa.dev/spec/v1.0/verification_summary) (VSA) to the registry.
The VSA can then be verified by the consumer without needing to verify the full attestation, as long as the consumer trusts the entity that signed the VSA.
This is useful when the consumer only needs to know that the attestation was verified by a trusted entity, and does not need to know the details of the attestation.
A useful pattern is to have apply a policy to a third-party image at initial ingress, then publish a VSA when publishing the image to an internal registry to attest that the image complies with the policy.
The VSA can be verified very quickly, for example in a Kubernetes admission controller.
`attest` always generates a [SLSA VSA](https://slsa.dev/spec/v1.0/verification_summary) when verifying attestations on an image.
The VSA can be signed and published to the registry using the signing functions mentioned in [Signing Attestations](#signing-attestations).
## Example VSA
```json
{
"_type": "https://in-toto.io/Statement/v1",
"subject": [
{
"name": "pkg:docker/example.org/example-image@1.0?platform=linux%2Famd64",
"digest": {
"sha256": "49f717386e5462e945232569a97a05831cb83bef8c3369be3bb7ea1793686960"
}
}
],
"predicateType": "https://slsa.dev/verification_summary/v1",
"predicate": {
"verifier": {
"id": "https://example.org/internal-verifier"
},
"timeVerified": "2024-04-19T08:00:00.01Z",
"resourceUri": "pkg:docker/example.org/example-image@1.0?platform=linux%2Famd64&digest=sha256%3A49f717386e5462e945232569a97a05831cb83bef8c3369be3bb7ea1793686960",
"policy": {
"uri": "https://example.org/internal-policy/v1"
},
"verificationResult": "PASSED",
"verifiedLevels": ["SLSA_BUILD_LEVEL_3"]
}
}
```
# API Reference
Full API reference can be found at [pkg.go.dev/github.com/docker/attest](https://pkg.go.dev/github.com/docker/attest).
# Project Layout
- [pkg/](https://pkg.go.dev/github.com/docker/image-signer-verifier/pkg) => packages that are okay to import for other projects
- [internal/](https://pkg.go.dev/github.com/docker/image-signer-verifier/pkg) => packages that are only for project internal purposes
- [scripts/](scripts/) => build scripts
- [test/](test/) => data for use in tests
# Versioning
`attest` uses [Semantic Versioning](https://semver.org/).
As such, until `attest` reaches version 1.0.0, breaking changes may be introduced in minor versions.
> Anything MAY change at any time. The public API SHOULD NOT be considered stable.

View File

@@ -1,2 +1,5 @@
ignore:
- "internal/test"
coverage:
status:
patch: false

100
go.mod
View File

@@ -1,44 +1,44 @@
module github.com/docker/attest
go 1.22.1
go 1.22.5
require (
github.com/Masterminds/semver/v3 v3.2.1
github.com/aws/aws-sdk-go-v2/config v1.27.26
github.com/aws/aws-sdk-go-v2/config v1.27.27
github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20231024185945-8841054dbdb8
github.com/containerd/containerd v1.7.19
github.com/containerd/platforms v0.2.1
github.com/distribution/reference v0.6.0
github.com/go-openapi/runtime v0.28.0
github.com/go-openapi/strfmt v0.23.0
github.com/google/go-containerregistry v0.20.0
github.com/google/go-containerregistry v0.20.1
github.com/hashicorp/go-cleanhttp v0.5.2
github.com/in-toto/in-toto-golang v0.9.0
github.com/open-policy-agent/opa v0.66.0
github.com/open-policy-agent/opa v0.67.1
github.com/opencontainers/image-spec v1.1.0
github.com/package-url/packageurl-go v0.1.3
github.com/pkg/errors v0.9.1
github.com/secure-systems-lab/go-securesystemslib v0.8.0
github.com/sigstore/cosign/v2 v2.2.4
github.com/sigstore/cosign/v2 v2.4.0
github.com/sigstore/rekor v1.3.6
github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.6
github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.8.6
github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.8
github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.8.8
github.com/stretchr/testify v1.9.0
github.com/testcontainers/testcontainers-go v0.32.0
github.com/testcontainers/testcontainers-go/modules/registry v0.32.0
github.com/theupdateframework/go-tuf/v2 v2.0.0-20240504210453-5a634eb214ae // for https://github.com/theupdateframework/go-tuf/pull/632
google.golang.org/api v0.188.0
gopkg.in/yaml.v3 v3.0.1
github.com/theupdateframework/go-tuf/v2 v2.0.0
google.golang.org/api v0.191.0
sigs.k8s.io/yaml v1.4.0
)
// fork with changes to support ArtifactType (https://github.com/google/go-containerregistry/pull/1931)
replace github.com/google/go-containerregistry => github.com/docker/go-containerregistry v0.0.0-20240808132857-c8bfc44af7c8
require (
cloud.google.com/go v0.115.0 // indirect
cloud.google.com/go/auth v0.7.0 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect
cloud.google.com/go/compute/metadata v0.4.0 // indirect
cloud.google.com/go/iam v1.1.10 // indirect
cloud.google.com/go/kms v1.18.2 // indirect
cloud.google.com/go/longrunning v0.5.9 // indirect
cloud.google.com/go/auth v0.7.3 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.3 // indirect
cloud.google.com/go/compute/metadata v0.5.0 // indirect
cloud.google.com/go/iam v1.1.12 // indirect
cloud.google.com/go/kms v1.18.4 // indirect
cloud.google.com/go/longrunning v0.5.11 // indirect
dario.cat/mergo v1.0.0 // indirect
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
@@ -48,7 +48,7 @@ require (
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.30.3 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.17.26 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.17.27 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15 // indirect
@@ -57,8 +57,8 @@ require (
github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.24.1 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 // indirect
github.com/aws/aws-sdk-go-v2/service/kms v1.34.1 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.22.3 // indirect
github.com/aws/aws-sdk-go-v2/service/kms v1.35.3 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.22.4 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 // indirect
github.com/aws/smithy-go v1.20.3 // indirect
@@ -67,17 +67,17 @@ require (
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/containerd v1.7.20 // indirect
github.com/containerd/log v0.1.0 // indirect
github.com/containerd/platforms v0.2.1 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.15.1 // indirect
github.com/cpuguy83/dockercfg v0.3.1 // indirect
github.com/cyberphone/json-canonicalization v0.0.0-20231217050601-ba74d44ecf5f // indirect
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 v26.1.3+incompatible // indirect
github.com/docker/cli v27.1.1+incompatible // indirect
github.com/docker/distribution v2.8.3+incompatible // indirect
github.com/docker/docker v27.0.3+incompatible // indirect
github.com/docker/docker v27.1.0+incompatible // indirect
github.com/docker/docker-credential-helpers v0.8.1 // indirect
github.com/docker/go-connections v0.5.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
@@ -86,7 +86,7 @@ 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-jose/go-jose/v4 v4.0.2 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
@@ -101,15 +101,14 @@ require (
github.com/gobwas/glob v0.2.3 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/certificate-transparency-go v1.1.8 // indirect
github.com/google/s2a-go v0.1.7 // indirect
github.com/google/certificate-transparency-go v1.2.1 // indirect
github.com/google/s2a-go v0.1.8 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
github.com/googleapis/gax-go/v2 v2.12.5 // indirect
github.com/googleapis/gax-go/v2 v2.13.0 // indirect
github.com/gorilla/mux v1.8.1 // indirect
github.com/hashicorp/go-retryablehttp v0.7.6 // indirect
github.com/hashicorp/go-retryablehttp v0.7.7 // 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
@@ -129,17 +128,19 @@ require (
github.com/moby/sys/user v0.1.0 // indirect
github.com/moby/term v0.5.0 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/nozzle/throttler v0.0.0-20180817012639-2ea982251481 // indirect
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.2.2 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // 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.53.0 // indirect
github.com/prometheus/procfs v0.15.0 // indirect
github.com/prometheus/common v0.55.0 // indirect
github.com/prometheus/procfs v0.15.1 // 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
@@ -147,7 +148,8 @@ require (
github.com/shibumi/go-pathspec v1.3.0 // indirect
github.com/shirou/gopsutil/v3 v3.24.4 // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/sigstore/sigstore v1.8.6 // indirect
github.com/sigstore/protobuf-specs v0.3.2 // indirect
github.com/sigstore/sigstore v1.8.8 // indirect
github.com/sigstore/timestamp-authority v1.2.2 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
@@ -155,10 +157,11 @@ require (
github.com/spf13/cast v1.6.0 // indirect
github.com/spf13/cobra v1.8.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.18.2 // indirect
github.com/spf13/viper v1.19.0 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect
github.com/tchap/go-patricia/v2 v2.3.1 // indirect
github.com/testcontainers/testcontainers-go v0.32.0 // 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.14 // indirect
@@ -172,29 +175,30 @@ require (
go.mongodb.org/mongo-driver v1.15.0 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.52.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 // indirect
go.opentelemetry.io/otel v1.27.0 // indirect
go.opentelemetry.io/otel/metric v1.27.0 // indirect
go.opentelemetry.io/otel/sdk v1.27.0 // indirect
go.opentelemetry.io/otel/trace v1.27.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect
go.opentelemetry.io/otel v1.28.0 // indirect
go.opentelemetry.io/otel/metric v1.28.0 // indirect
go.opentelemetry.io/otel/sdk v1.28.0 // indirect
go.opentelemetry.io/otel/trace v1.28.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
golang.org/x/crypto v0.25.0 // indirect
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/mod v0.19.0 // indirect
golang.org/x/net v0.27.0 // indirect
golang.org/x/oauth2 v0.21.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/oauth2 v0.22.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.22.0 // indirect
golang.org/x/term v0.22.0 // indirect
golang.org/x/text v0.16.0 // indirect
golang.org/x/time v0.5.0 // indirect
google.golang.org/genproto v0.0.0-20240708141625-4ad9e859172b // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240708141625-4ad9e859172b // indirect
google.golang.org/grpc v1.64.1 // indirect
golang.org/x/time v0.6.0 // indirect
google.golang.org/genproto v0.0.0-20240730163845-b1a4ccb954bf // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240725223205-93522f1f2a9f // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240730163845-b1a4ccb954bf // indirect
google.golang.org/grpc v1.65.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/klog/v2 v2.120.1 // indirect
)

270
go.sum
View File

@@ -1,22 +1,22 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.115.0 h1:CnFSK6Xo3lDYRoBKEcAtia6VSC837/ZkJuRduSFnr14=
cloud.google.com/go v0.115.0/go.mod h1:8jIM5vVgoAEoiVxQ/O4BFTfHqulPZgs/ufEzMcFMdWU=
cloud.google.com/go/auth v0.7.0 h1:kf/x9B3WTbBUHkC+1VS8wwwli9TzhSt0vSTVBmMR8Ts=
cloud.google.com/go/auth v0.7.0/go.mod h1:D+WqdrpcjmiCgWrXmLLxOVq1GACoE36chW6KXoEvuIw=
cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4=
cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q=
cloud.google.com/go/compute/metadata v0.4.0 h1:vHzJCWaM4g8XIcm8kopr3XmDA4Gy/lblD3EhhSux05c=
cloud.google.com/go/compute/metadata v0.4.0/go.mod h1:SIQh1Kkb4ZJ8zJ874fqVkslA29PRXuleyj6vOzlbK7M=
cloud.google.com/go/iam v1.1.10 h1:ZSAr64oEhQSClwBL670MsJAW5/RLiC6kfw3Bqmd5ZDI=
cloud.google.com/go/iam v1.1.10/go.mod h1:iEgMq62sg8zx446GCaijmA2Miwg5o3UbO+nI47WHJps=
cloud.google.com/go/kms v1.18.2 h1:EGgD0B9k9tOOkbPhYW1PHo2W0teamAUYMOUIcDRMfPk=
cloud.google.com/go/kms v1.18.2/go.mod h1:YFz1LYrnGsXARuRePL729oINmN5J/5e7nYijgvfiIeY=
cloud.google.com/go/longrunning v0.5.9 h1:haH9pAuXdPAMqHvzX0zlWQigXT7B0+CL4/2nXXdBo5k=
cloud.google.com/go/longrunning v0.5.9/go.mod h1:HD+0l9/OOW0za6UWdKJtXoFAX/BGg/3Wj8p10NeWF7c=
cuelabs.dev/go/oci/ociregistry v0.0.0-20240314152124-224736b49f2e h1:GwCVItFUPxwdsEYnlUcJ6PJxOjTeFFCKOh6QWg4oAzQ=
cuelabs.dev/go/oci/ociregistry v0.0.0-20240314152124-224736b49f2e/go.mod h1:ApHceQLLwcOkCEXM1+DyCXTHEJhNGDpJ2kmV6axsx24=
cuelang.org/go v0.8.1 h1:VFYsxIFSPY5KgSaH1jQ2GxHOrbu6Ga3kEI70yCZwnOg=
cuelang.org/go v0.8.1/go.mod h1:CoDbYolfMms4BhWUlhD+t5ORnihR7wvjcfgyO9lL5FI=
cloud.google.com/go/auth v0.7.3 h1:98Vr+5jMaCZ5NZk6e/uBgf60phTk/XN84r8QEWB9yjY=
cloud.google.com/go/auth v0.7.3/go.mod h1:HJtWUx1P5eqjy/f6Iq5KeytNpbAcGolPhOgyop2LlzA=
cloud.google.com/go/auth/oauth2adapt v0.2.3 h1:MlxF+Pd3OmSudg/b1yZ5lJwoXCEaeedAguodky1PcKI=
cloud.google.com/go/auth/oauth2adapt v0.2.3/go.mod h1:tMQXOfZzFuNuUxOypHlQEXgdfX5cuhwU+ffUuXRJE8I=
cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY=
cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY=
cloud.google.com/go/iam v1.1.12 h1:JixGLimRrNGcxvJEQ8+clfLxPlbeZA6MuRJ+qJNQ5Xw=
cloud.google.com/go/iam v1.1.12/go.mod h1:9LDX8J7dN5YRyzVHxwQzrQs9opFFqn0Mxs9nAeB+Hhg=
cloud.google.com/go/kms v1.18.4 h1:dYN3OCsQ6wJLLtOnI8DGUwQ5shMusXsWCCC+s09ATsk=
cloud.google.com/go/kms v1.18.4/go.mod h1:SG1bgQ3UWW6/KdPo9uuJnzELXY5YTTMJtDYvajiQ22g=
cloud.google.com/go/longrunning v0.5.11 h1:Havn1kGjz3whCfoD8dxMLP73Ph5w+ODyZB9RUsDxtGk=
cloud.google.com/go/longrunning v0.5.11/go.mod h1:rDn7//lmlfWV1Dx6IB4RatCPenTwwmqXuiP0/RgoEO4=
cuelabs.dev/go/oci/ociregistry v0.0.0-20240404174027-a39bec0462d2 h1:BnG6pr9TTr6CYlrJznYUDj6V7xldD1W+1iXPum0wT/w=
cuelabs.dev/go/oci/ociregistry v0.0.0-20240404174027-a39bec0462d2/go.mod h1:pK23AUVXuNzzTpfMCA06sxZGeVQ/75FdVtW249de9Uo=
cuelang.org/go v0.9.2 h1:pfNiry2PdRBr02G/aKm5k2vhzmqbAOoaB4WurmEbWvs=
cuelang.org/go v0.9.2/go.mod h1:qpAYsLOf7gTM1YdEg6cxh553uZ4q9ZDWlPbtZr9q1Wk=
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
@@ -29,12 +29,12 @@ github.com/AliyunContainerService/ack-ram-tool/pkg/credentials/alibabacloudsdkgo
github.com/AliyunContainerService/ack-ram-tool/pkg/credentials/alibabacloudsdkgo/helper v0.2.0/go.mod h1:GgeIE+1be8Ivm7Sh4RgwI42aTtC9qrcj+Y9Y6CjJhJs=
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU=
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.10.0 h1:n1DH8TPV4qqPTje2RcUBYwtrTWlabVp4n46+74X2pn4=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.10.0/go.mod h1:HDcZnuGbiyppErN6lB+idp4CKhjbc8gwjto6OPpyggM=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1 h1:sO0/P7g68FrryJzljemN+6GTssUXdANk6aJ7T1ZxnsQ=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1/go.mod h1:h8hyGFDsU5HMivxiS2iYFZsgDbU9OnnJ163x5UGVKYo=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2 h1:LqbJ/WzJUwBf8UiaSzgX7aMclParm9/5Vgp+TY51uBQ=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2/go.mod h1:yInRyqWXAuaPrgI7p70+lDDgh3mlBohis29jGMISnmc=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.13.0 h1:GJHeeA2N7xrG3q30L2UXDyuWRzDM900/65j70wcM4Ww=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.13.0/go.mod h1:l38EPgmsp71HHLq9j7De57JcKOWPyhrsW1Awm1JS6K0=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 h1:tfLQ34V6F7tVSwoTf/4lH5sE0o6eCJuNDTmH09nDpbc=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.1.0 h1:DRiANoJTiW6obBQe3SqZizkuV1PEgfiiGivmVocDy64=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.1.0/go.mod h1:qLIye2hwb/ZouqhpSD9Zn3SJipvpEnz1Ywl3VUk9Y0s=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0 h1:D3occbWoio4EBLkbkevetNMAVX197GkzbUMtqjGWn80=
@@ -102,14 +102,14 @@ github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/aws/aws-sdk-go v1.54.6 h1:HEYUib3yTt8E6vxjMWM3yAq5b+qjj/6aKA62mkgux9g=
github.com/aws/aws-sdk-go v1.54.6/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU=
github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
github.com/aws/aws-sdk-go-v2 v1.30.3 h1:jUeBtG0Ih+ZIFH0F4UkmL9w3cSpaMv9tYYDbzILP8dY=
github.com/aws/aws-sdk-go-v2 v1.30.3/go.mod h1:nIQjQVp5sfpQcTc9mPSr1B0PaWK5ByX9MOoDadSN4lc=
github.com/aws/aws-sdk-go-v2/config v1.27.26 h1:T1kAefbKuNum/AbShMsZEro6eRkeOT8YILfE9wyjAYQ=
github.com/aws/aws-sdk-go-v2/config v1.27.26/go.mod h1:ivWHkAWFrw/nxty5Fku7soTIVdqZaZ7dw+tc5iGW3GA=
github.com/aws/aws-sdk-go-v2/credentials v1.17.26 h1:tsm8g/nJxi8+/7XyJJcP2dLrnK/5rkFp6+i2nhmz5fk=
github.com/aws/aws-sdk-go-v2/credentials v1.17.26/go.mod h1:3vAM49zkIa3q8WT6o9Ve5Z0vdByDMwmdScO0zvThTgI=
github.com/aws/aws-sdk-go-v2/config v1.27.27 h1:HdqgGt1OAP0HkEDDShEl0oSYa9ZZBSOmKpdpsDMdO90=
github.com/aws/aws-sdk-go-v2/config v1.27.27/go.mod h1:MVYamCg76dFNINkZFu4n4RjDixhVr51HLj4ErWzrVwg=
github.com/aws/aws-sdk-go-v2/credentials v1.17.27 h1:2raNba6gr2IfA0eqqiP2XiQ0UVOpGPgDSi0I9iAP+UI=
github.com/aws/aws-sdk-go-v2/credentials v1.17.27/go.mod h1:gniiwbGahQByxan6YjQUMcW4Aov6bLC3m+evgcoN4r4=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 h1:KreluoV8FZDEtI6Co2xuNk/UqI9iwMrOx/87PBNIKqw=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11/go.mod h1:SeSUYBLsMYFoRvHE0Tjvn7kbxaUhl75CJi1sbfhMxkU=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15 h1:SoNJ4RlFEQEbtDcCEt+QG56MY4fm4W8rYirAmq+/DdU=
@@ -126,10 +126,10 @@ github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 h1:dT3MqvG
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3/go.mod h1:GlAeCkHwugxdHaueRr4nhPuY+WW+gR8UjlcqzPr1SPI=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 h1:HGErhhrxZlQ044RiM+WdoZxp0p+EGM62y3L6pwA4olE=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17/go.mod h1:RkZEx4l0EHYDJpWppMJ3nD9wZJAa8/0lq9aVC+r2UII=
github.com/aws/aws-sdk-go-v2/service/kms v1.34.1 h1:VsKBn6WADI3Nn3WjBMzeRww9WHXeVLi7zyuSrqjRCBQ=
github.com/aws/aws-sdk-go-v2/service/kms v1.34.1/go.mod h1:5F6kXrPBxv0l1t8EO44GuG4W82jGJwaRE0B+suEGnNY=
github.com/aws/aws-sdk-go-v2/service/sso v1.22.3 h1:Fv1vD2L65Jnp5QRsdiM64JvUM4Xe+E0JyVsRQKv6IeA=
github.com/aws/aws-sdk-go-v2/service/sso v1.22.3/go.mod h1:ooyCOXjvJEsUw7x+ZDHeISPMhtwI3ZCB7ggFMcFfWLU=
github.com/aws/aws-sdk-go-v2/service/kms v1.35.3 h1:UPTdlTOwWUX49fVi7cymEN6hDqCwe3LNv1vi7TXUutk=
github.com/aws/aws-sdk-go-v2/service/kms v1.35.3/go.mod h1:gjDP16zn+WWalyaUqwCCioQ8gU8lzttCCc9jYsiQI/8=
github.com/aws/aws-sdk-go-v2/service/sso v1.22.4 h1:BXx0ZIxvrJdSgSvKTZ+yRBeSqqgPM89VPlulEcl37tM=
github.com/aws/aws-sdk-go-v2/service/sso v1.22.4/go.mod h1:ooyCOXjvJEsUw7x+ZDHeISPMhtwI3ZCB7ggFMcFfWLU=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 h1:yiwVzJW2ZxZTurVbYWA7QOrAaCYQR72t0wrSBfoesUE=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4/go.mod h1:0oxfLkpz3rQ/CHlx5hB7H69YUpFiI1tql6Q6Ne+1bCw=
github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 h1:ZsDKRLXGWHk8WdtyYMoGNO7bTudrvuKpDKgMVRlepGE=
@@ -142,12 +142,14 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/buildkite/agent/v3 v3.62.0 h1:yvzSjI8Lgifw883I8m9u8/L/Thxt4cLFd5aWPn3gg70=
github.com/buildkite/agent/v3 v3.62.0/go.mod h1:jN6SokGXrVNNIpI0BGQ+j5aWeI3gin8F+3zwA5Q6gqM=
github.com/buildkite/go-pipeline v0.3.2 h1:SW4EaXNwfjow7xDRPGgX0Rcx+dPj5C1kV9LKCLjWGtM=
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/buildkite/agent/v3 v3.76.2 h1:SweFq3e0N20RikWsVeOXzTjfr0AoOskxm9c0bcNyI0E=
github.com/buildkite/agent/v3 v3.76.2/go.mod h1:9ffbmJD7d7C/nOcElj6Qm+uIj1QoYh3NNvka4rkKkss=
github.com/buildkite/go-pipeline v0.10.0 h1:EDffu+LfMY2k5u+iEdo6Jn3obGKsrL5wicc1O/yFeRs=
github.com/buildkite/go-pipeline v0.10.0/go.mod h1:eMH1kiav5VeiTiu0Mk2/M7nZhKyFeL4iGj7Y7rj4f3w=
github.com/buildkite/interpolate v0.1.3 h1:OFEhqji1rNTRg0u9DsSodg63sjJQEb1uWbENq9fUOBM=
github.com/buildkite/interpolate v0.1.3/go.mod h1:UNVe6A+UfiBNKbhAySrBbZFZFxQ+DXr9nWen6WVt/A8=
github.com/buildkite/roko v1.2.0 h1:hbNURz//dQqNl6Eo9awjQOVOZwSDJ8VEbBDxSfT9rGQ=
github.com/buildkite/roko v1.2.0/go.mod h1:23R9e6nHxgedznkwwfmqZ6+0VJZJZ2Sg/uVcp2cP46I=
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=
@@ -178,16 +180,16 @@ github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUo
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.19 h1:/xQ4XRJ0tamDkdzrrBAUy/LE5nCcxFKdBm4EcPrSMEE=
github.com/containerd/containerd v1.7.19/go.mod h1:h4FtNYUUMB4Phr6v+xG89RYKj9XccvbNSCKjdufCrkc=
github.com/containerd/containerd v1.7.20 h1:Sl6jQYk3TRavaU83h66QMbI2Nqg9Jm6qzwX57Vsn1SQ=
github.com/containerd/containerd v1.7.20/go.mod h1:52GsS5CwquuqPuLncsXwG0t2CiUce+KsNHJZQJvAgR0=
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A=
github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw=
github.com/containerd/stargz-snapshotter/estargz v0.15.1 h1:eXJjw9RbkLFgioVaTG+G/ZW/0kEe2oEKCdS/ZxIyoCU=
github.com/containerd/stargz-snapshotter/estargz v0.15.1/go.mod h1:gr2RNwukQ/S9Nv33Lt6UC7xEx58C+LHRdoqbEKjz1Kk=
github.com/coreos/go-oidc/v3 v3.10.0 h1:tDnXHnLyiTVyT/2zLDGj09pFPkhND8Gl8lnTRhoEaJU=
github.com/coreos/go-oidc/v3 v3.10.0/go.mod h1:5j11xcw0D3+SGxn6Z/WFADsgcWVMyNAlSQupk0KK3ac=
github.com/coreos/go-oidc/v3 v3.11.0 h1:Ia3MxdwpSw702YW0xgfmP1GVCMA9aEFWu12XUZ3/OtI=
github.com/coreos/go-oidc/v3 v3.11.0/go.mod h1:gE3LgjOgFoHi9a4ce4/tJczr0Ai2/BoDhf0r5lltWI0=
github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E=
github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
@@ -216,16 +218,18 @@ github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi
github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
github.com/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 v26.1.3+incompatible h1:bUpXT/N0kDE3VUHI2r5VMsYQgi38kYuoC0oL9yt3lqc=
github.com/docker/cli v26.1.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/cli v27.1.1+incompatible h1:goaZxOqs4QKxznZjjBWKONQci/MywhtRv2oNn0GkeZE=
github.com/docker/cli v27.1.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v27.0.3+incompatible h1:aBGI9TeQ4MPlhquTQKq9XbK79rKFVwXNUAYz9aXyEBE=
github.com/docker/docker v27.0.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v27.1.0+incompatible h1:rEHVQc4GZ0MIQKifQPHSFGV/dVgaZafgRf8fCPtDYBs=
github.com/docker/docker v27.1.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker-credential-helpers v0.8.1 h1:j/eKUktUltBtMzKqmfLB0PAgqYyMHOp5vfsD1807oKo=
github.com/docker/docker-credential-helpers v0.8.1/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-containerregistry v0.0.0-20240808132857-c8bfc44af7c8 h1:T/wutVfQ1Oj4H5tbP5IZL5l6PZqzvapVJ5cB4Wy4Ucc=
github.com/docker/go-containerregistry v0.0.0-20240808132857-c8bfc44af7c8/go.mod h1:z38EKdKh4h7IP2gSfUUqEvalZBqs6AoLeWfUy34nQC8=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
@@ -259,8 +263,8 @@ github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k=
github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
github.com/go-jose/go-jose/v4 v4.0.1 h1:QVEPDE3OluqXBQZDcnNvQrInro2h0e4eqNbnZSWqS6U=
github.com/go-jose/go-jose/v4 v4.0.1/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY=
github.com/go-jose/go-jose/v4 v4.0.2 h1:R3l3kkBds16bO7ZFAEEcofK0MkrAJt3jlJznWZG0nvk=
github.com/go-jose/go-jose/v4 v4.0.2/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
@@ -305,8 +309,8 @@ github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/glog v1.2.0 h1:uCdmnmatrKCgMBlM4rMuJZWOkPDqdbZPnrMXDY4gI68=
github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
github.com/golang/glog v1.2.1 h1:OptwRhECazUx5ix5TTWC3EZhsZEHWcYWY4FQHTIubm4=
github.com/golang/glog v1.2.1/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -327,8 +331,8 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/certificate-transparency-go v1.1.8 h1:LGYKkgZF7satzgTak9R4yzfJXEeYVAjV6/EAEJOf1to=
github.com/google/certificate-transparency-go v1.1.8/go.mod h1:bV/o8r0TBKRf1X//iiiSgWrvII4d7/8OiA+3vG26gI8=
github.com/google/certificate-transparency-go v1.2.1 h1:4iW/NwzqOqYEEoCBEFP+jPbBXbLqMpq3CifMyOnDUME=
github.com/google/certificate-transparency-go v1.2.1/go.mod h1:bvn/ytAccv+I6+DGkqpvSsEdiVGramgaSC6RD3tEmeE=
github.com/google/flatbuffers v2.0.8+incompatible h1:ivUb1cGomAB101ZM1T0nOiWz9pSrTMoa9+EiY7igmkM=
github.com/google/flatbuffers v2.0.8+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 h1:0VpGH+cDhbDtdcweoyCVsF3fhN8kejK6rFe/2FFX2nU=
@@ -344,8 +348,6 @@ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-containerregistry v0.20.0 h1:wRqHpOeVh3DnenOrPy9xDOLdnLatiGuuNRVelR2gSbg=
github.com/google/go-containerregistry v0.20.0/go.mod h1:YCMFNQeeXeLF+dnhhWkqDItx/JSkH01j1Kis4PsjzFI=
github.com/google/go-github/v55 v55.0.0 h1:4pp/1tNMB9X/LuAhs5i0KQAE40NmiR/y6prLNb9x9cg=
github.com/google/go-github/v55 v55.0.0/go.mod h1:JLahOTA1DnXzhxEymmFF5PP2tSS9JVNj68mSZNDwskA=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
@@ -353,8 +355,8 @@ github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM=
github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA=
github.com/google/tink/go v1.7.0 h1:6Eox8zONGebBFcCBqkVmt60LaWZa6xg1cl/DwAh/J1w=
github.com/google/tink/go v1.7.0/go.mod h1:GAUOd+QE3pgj9q8VKIGTCP33c/B7eb4NhxLcgTJZStM=
github.com/google/trillian v1.6.0 h1:jMBeDBIkINFvS2n6oV5maDqfRlxREAc6CW9QYWQ0qT4=
@@ -364,8 +366,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
github.com/googleapis/gax-go/v2 v2.12.5 h1:8gw9KZK8TiVKB6q3zHY3SBzLnrGp6HQjyfYBYGmXdxA=
github.com/googleapis/gax-go/v2 v2.12.5/go.mod h1:BUDKcWo+RaKq5SC9vVYL0wLADa3VcfswbOMMRmB9H3E=
github.com/googleapis/gax-go/v2 v2.13.0 h1:yitjD5f7jQHhyDsnhKEBU52NdvvdSeGzlAnDPT0hH1s=
github.com/googleapis/gax-go/v2 v2.13.0/go.mod h1:Z/fvTZXF8/uw7Xu5GuslPw+bplx6SS338j1Is2S+B7A=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
@@ -379,8 +381,8 @@ github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB1
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.6 h1:TwRYfx2z2C4cLbXmT8I5PgP/xmuqASDyiVuGYfs9GZM=
github.com/hashicorp/go-retryablehttp v0.7.6/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk=
github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU=
github.com/hashicorp/go-retryablehttp v0.7.7/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=
@@ -391,8 +393,8 @@ github.com/hashicorp/go-sockaddr v1.0.5 h1:dvk7TIXCZpmfOlM+9mlcrWmWjw/wlKT+VDq2w
github.com/hashicorp/go-sockaddr v1.0.5/go.mod h1:uoUUmtwU7n9Dv3O4SNLeFvg0SxQ3lyjsj6+CCykpaxI=
github.com/hashicorp/hcl v1.0.1-vault-5 h1:kI3hhbbyzr4dldA8UdTb7ZlVVlI2DACdCfz31RPDgJM=
github.com/hashicorp/hcl v1.0.1-vault-5/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM=
github.com/hashicorp/vault/api v1.12.2 h1:7YkCTE5Ni90TcmYHDBExdt4WGJxhpzaHqR6uGbQb/rE=
github.com/hashicorp/vault/api v1.12.2/go.mod h1:LSGf1NGT1BnvFFnKVtnvcaLBM2Lz+gJdpL6HUYed8KE=
github.com/hashicorp/vault/api v1.14.0 h1:Ah3CFLixD5jmjusOgm8grfN9M0d+Y8fVR2SW0K6pJLU=
github.com/hashicorp/vault/api v1.14.0/go.mod h1:pV9YLxBGSz+cItFDd8Ii4G17waWOQ32zVjMWHe/cOqk=
github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef h1:A9HsByNhogrvm9cWb28sjiS3i7tcKCkflWFEkHfuAgM=
github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef/go.mod h1:lADxMC39cJJqL93Duh1xhAs4I2Zs8mKS89XWXFGp9cs=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
@@ -491,8 +493,8 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw=
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
github.com/open-policy-agent/opa v0.66.0 h1:DbrvfJQja0FBRcPOB3Z/BOckocN+M4ApNWyNhSRJt0w=
github.com/open-policy-agent/opa v0.66.0/go.mod h1:EIgNnJcol7AvQR/IcWLwL13k64gHVbNAVG46b2G+/EY=
github.com/open-policy-agent/opa v0.67.1 h1:rzy26J6g1X+CKknAcx0Vfbt41KqjuSzx4E0A8DAZf3E=
github.com/open-policy-agent/opa v0.67.1/go.mod h1:aqKlHc8E2VAAylYE9x09zJYr/fYzGX+JKne89UGqFzk=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
@@ -520,10 +522,10 @@ github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJL
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.53.0 h1:U2pL9w9nmJwJDa4qqLQ3ZaePJ6ZTwt7cMD3AG3+aLCE=
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/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
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=
@@ -553,22 +555,26 @@ github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFt
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
github.com/sigstore/cosign/v2 v2.2.4 h1:iY4vtEacmu2hkNj1Fh+8EBqBwKs2DHM27/lbNWDFJro=
github.com/sigstore/cosign/v2 v2.2.4/go.mod h1:JZlRD2uaEjVAvZ1XJ3QkkZJhTqSDVtLaet+C/TMR81Y=
github.com/sigstore/fulcio v1.4.5 h1:WWNnrOknD0DbruuZWCbN+86WRROpEl3Xts+WT2Ek1yc=
github.com/sigstore/fulcio v1.4.5/go.mod h1:oz3Qwlma8dWcSS/IENR/6SjbW4ipN0cxpRVfgdsjMU8=
github.com/sigstore/cosign/v2 v2.4.0 h1:2NdidNgClg+oXr/fDIr37E/BE6j00gqgUhSiBK2kjSQ=
github.com/sigstore/cosign/v2 v2.4.0/go.mod h1:j+fH1DCUkcn92qp6ezDj4JbGMri6eG1nLJC+hs64rvc=
github.com/sigstore/fulcio v1.5.1 h1:Iasy1zfNjaq8BV4S8o6pXspLDU28PQC2z07GmOu9zpM=
github.com/sigstore/fulcio v1.5.1/go.mod h1:W1A/UHrTopy1IBZPMtHmxg7GPYAu+vt5dRXM3W6yjPo=
github.com/sigstore/protobuf-specs v0.3.2 h1:nCVARCN+fHjlNCk3ThNXwrZRqIommIeNKWwQvORuRQo=
github.com/sigstore/protobuf-specs v0.3.2/go.mod h1:RZ0uOdJR4OB3tLQeAyWoJFbNCBFrPQdcokntde4zRBA=
github.com/sigstore/rekor v1.3.6 h1:QvpMMJVWAp69a3CHzdrLelqEqpTM3ByQRt5B5Kspbi8=
github.com/sigstore/rekor v1.3.6/go.mod h1:JDTSNNMdQ/PxdsS49DJkJ+pRJCO/83nbR5p3aZQteXc=
github.com/sigstore/sigstore v1.8.6 h1:g066b/Nw5r5oxhNv4XqJUUzVcyf1b07itUueiQe7rZM=
github.com/sigstore/sigstore v1.8.6/go.mod h1:UOBrJd9JBQ81DrkpGljzsIFXEtfC30raHvLWFWG857U=
github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.6 h1:uVcT1JT4lLkmBQII25PvgP/nyvi4HvTMNXzoHqQqEHE=
github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.6/go.mod h1:VJ/745ojKNQKbZ1ykO5Vebtnq9vGt8zcgKemQIibBIE=
github.com/sigstore/sigstore/pkg/signature/kms/azure v1.8.3 h1:xgbPRCr2npmmsuVVteJqi/ERw9+I13Wou7kq0Yk4D8g=
github.com/sigstore/sigstore/pkg/signature/kms/azure v1.8.3/go.mod h1:G4+I83FILPX6MtnoaUdmv/bRGEVtR3JdLeJa/kXdk/0=
github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.8.6 h1:CFtW7RIQ4fOtBzl+1YAnAmcACL4B+Qr/S7PXPdJ+54s=
github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.8.6/go.mod h1:rhX2eca5kAqUTwQxQLMnOLmvSxbqF9JZ3rFOoDpQX5w=
github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.8.3 h1:h9G8j+Ds21zqqulDbA/R/ft64oQQIyp8S7wJYABYSlg=
github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.8.3/go.mod h1:zgCeHOuqF6k7A7TTEvftcA9V3FRzB7mrPtHOhXAQBnc=
github.com/sigstore/sigstore v1.8.8 h1:B6ZQPBKK7Z7tO3bjLNnlCMG+H66tO4E/+qAphX8T/hg=
github.com/sigstore/sigstore v1.8.8/go.mod h1:GW0GgJSCTBJY3fUOuGDHeFWcD++c4G8Y9K015pwcpDI=
github.com/sigstore/sigstore-go v0.5.1 h1:5IhKvtjlQBeLnjKkzMELNG4tIBf+xXQkDzhLV77+/8Y=
github.com/sigstore/sigstore-go v0.5.1/go.mod h1:TuOfV7THHqiDaUHuJ5+QN23RP/YoKmsbwJpY+aaYPN0=
github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.8 h1:2zHmUvaYCwV6LVeTo+OAkTm8ykOGzA9uFlAjwDPAUWM=
github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.8/go.mod h1:OEhheBplZinUsm7W9BupafztVZV3ldkAxEHbpAeC0Pk=
github.com/sigstore/sigstore/pkg/signature/kms/azure v1.8.8 h1:RKk4Z+qMaLORUdT7zntwMqKiYAej1VQlCswg0S7xNSY=
github.com/sigstore/sigstore/pkg/signature/kms/azure v1.8.8/go.mod h1:dMJdlBWKHMu2xf0wIKpbo7+QfG+RzVkBB3nHP8EMM5o=
github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.8.8 h1:89Xtxj8oqZt3UlSpCP4wApFvnQ2Z/dgowW5QOVhQigI=
github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.8.8/go.mod h1:Wa4xn/H3pU/yW/6tHiMXTpObBtBSGC5q29KYFEPKN6o=
github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.8.8 h1:Zte3Oogkd8m+nu2oK3yHtGmN++TZWh2Lm6q2iSprT1M=
github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.8.8/go.mod h1:j00crVw6ki4/WViXflw0zWgNALrAzZT+GbIK8v7Xlz4=
github.com/sigstore/timestamp-authority v1.2.2 h1:X4qyutnCQqJ0apMewFyx+3t7Tws00JQ/JonBiu3QvLE=
github.com/sigstore/timestamp-authority v1.2.2/go.mod h1:nEah4Eq4wpliDjlY342rXclGSO7Kb9hoRrl9tqLW13A=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
@@ -585,10 +591,10 @@ github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ=
github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk=
github.com/spiffe/go-spiffe/v2 v2.2.0 h1:9Vf06UsvsDbLYK/zJ4sYsIsHmMFknUD+feA7IYoWMQY=
github.com/spiffe/go-spiffe/v2 v2.2.0/go.mod h1:Urzb779b3+IwDJD2ZbN8fVl3Aa8G4N/PiUe6iXC0XxU=
github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
github.com/spiffe/go-spiffe/v2 v2.3.0 h1:g2jYNb/PDMB8I7mBGL2Zuq/Ur6hUhoroxGQFyD6tTj8=
github.com/spiffe/go-spiffe/v2 v2.3.0/go.mod h1:Oxsaio7DBgSNqhAO9i/9tLClaVlfRok7zvJnTV8ZyIY=
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=
@@ -617,8 +623,8 @@ github.com/thales-e-security/pool v0.0.2 h1:RAPs4q2EbWsTit6tpzuvTFlgFRJ3S8Evf5gt
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/theupdateframework/go-tuf/v2 v2.0.0 h1:rD8d9RotYBprZVgC+9oyTZ5MmawepnTSTqoDuxjWgbs=
github.com/theupdateframework/go-tuf/v2 v2.0.0/go.mod h1:baB22nBHeHBCeuGZcIlctNq4P61PcOdyARlplg5xmLA=
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=
@@ -633,8 +639,8 @@ github.com/transparency-dev/merkle v0.0.2 h1:Q9nBoQcZcgPamMkGn7ghV8XiTZ/kRxn1yCG
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=
github.com/vbatts/tar-split v0.11.5/go.mod h1:yZbwRsSeGjusneWgA781EKej9HF8vme8okylkAeNKLk=
github.com/xanzy/go-gitlab v0.102.0 h1:ExHuJ1OTQ2yt25zBMMj0G96ChBirGYv8U7HyUiYkZ+4=
github.com/xanzy/go-gitlab v0.102.0/go.mod h1:ETg8tcj4OhrB84UEgeE8dSuV/0h4BBL1uOV/qK0vlyI=
github.com/xanzy/go-gitlab v0.107.0 h1:P2CT9Uy9yN9lJo3FLxpMZ4xj6uWcpnigXsjvqJ6nd2Y=
github.com/xanzy/go-gitlab v0.107.0/go.mod h1:wKNKh3GkYDMOsGmnfuX+ITCmDuSDWFO0G+C4AygL9RY=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
@@ -656,26 +662,26 @@ 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.52.0 h1:vS1Ao/R55RNV4O7TA2Qopok8yN+X0LIP6RVWLFkprck=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.52.0/go.mod h1:BMsdeOxN04K0L5FNUBfjFdvwWGNe/rkmSwH4Aelu/X0=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 h1:9l89oX4ba9kHbBol3Xin3leYJ+252h0zszDtBwyKe2A=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0/go.mod h1:XLZfZboOJWHNKUv7eH0inh0E9VV6eWDFB/9yJyTLPp0=
go.opentelemetry.io/otel v1.27.0 h1:9BZoF3yMK/O1AafMiQTVu0YDj5Ea4hPhxCs7sGva+cg=
go.opentelemetry.io/otel v1.27.0/go.mod h1:DMpAK8fzYRzs+bi3rS5REupisuqTheUlSZJ1WnZaPAQ=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0 h1:R9DE4kQ4k+YtfLI2ULwX82VtNQ2J8yZmA7ZIF/D+7Mc=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0/go.mod h1:OQFyQVrDlbe+R7xrEyDr/2Wr67Ol0hRUgsfA+V5A95s=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 h1:qFffATk0X+HD+f1Z8lswGiOQYKHRlzfmdJm0wEaVrFA=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0/go.mod h1:MOiCmryaYtc+V0Ei+Tx9o5S1ZjA7kzLucuVuyzBZloQ=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg=
go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo=
go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0 h1:R3X6ZXmNPRR8ul6i3WgFURCHzaXjHdm0karRG/+dj3s=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0/go.mod h1:QWFXnDavXWwMx2EEcZsf3yxgEKAqsxQ+Syjp+seyInw=
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.27.0 h1:hvj3vdEKyeCi4YaYfNjv2NUje8FqKqUY8IlF0FxV/ik=
go.opentelemetry.io/otel/metric v1.27.0/go.mod h1:mVFgmRlhljgBiuk/MP/oKylr4hs85GZAylncepAX/ak=
go.opentelemetry.io/otel/sdk v1.27.0 h1:mlk+/Y1gLPLn84U4tI8d3GNJmGT/eXe3ZuOXN9kTWmI=
go.opentelemetry.io/otel/sdk v1.27.0/go.mod h1:Ha9vbLwJE6W86YstIywK2xFfPjbWlCuwPtMkKdz/Y4A=
go.opentelemetry.io/otel/trace v1.27.0 h1:IqYb813p7cmbHk0a5y6pD5JPakbVfftRXABGt5/Rscw=
go.opentelemetry.io/otel/trace v1.27.0/go.mod h1:6RiD1hkAprV4/q+yd2ln1HG9GoPx39SuvvstaLBl+l4=
go.opentelemetry.io/proto/otlp v1.2.0 h1:pVeZGk7nXDC9O2hncA6nHldxEjm6LByfA2aN8IOkz94=
go.opentelemetry.io/proto/otlp v1.2.0/go.mod h1:gGpR8txAl5M03pDhMC79G6SdqNV26naRm/KDsgaHD8A=
go.step.sm/crypto v0.44.2 h1:t3p3uQ7raP2jp2ha9P6xkQF85TJZh+87xmjSLaib+jk=
go.step.sm/crypto v0.44.2/go.mod h1:x1439EnFhadzhkuaGX7sz03LEMQ+jV4gRamf5LCZJQQ=
go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q=
go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s=
go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE=
go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg=
go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g=
go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI=
go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0=
go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8=
go.step.sm/crypto v0.51.1 h1:ktUg/2hetEMiBAqgz502ktZDGoDoGrcHFg3XpkmkvvA=
go.step.sm/crypto v0.51.1/go.mod h1:PdrhttNU/tG9/YsVd4fdlysBN+UV503p0o2irFZQlAw=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
@@ -700,8 +706,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.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/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8=
golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -724,8 +730,8 @@ golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs=
golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA=
golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -733,8 +739,8 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-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/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -783,8 +789,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@@ -796,33 +802,33 @@ golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4f
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.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.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg=
golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
google.golang.org/api v0.188.0 h1:51y8fJ/b1AaaBRJr4yWm96fPcuxSo0JcegXE3DaHQHw=
google.golang.org/api v0.188.0/go.mod h1:VR0d+2SIiWOYG3r/jdm7adPW9hI2aRv9ETOSCQ9Beag=
google.golang.org/api v0.191.0 h1:cJcF09Z+4HAB2t5qTQM1ZtfL/PemsLFkcFG67qq2afk=
google.golang.org/api v0.191.0/go.mod h1:tD5dsFGxFza0hnQveGfVk9QQYKcfp+VzgRqyXFxE0+E=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20240708141625-4ad9e859172b h1:dSTjko30weBaMj3eERKc0ZVXW4GudCswM3m+P++ukU0=
google.golang.org/genproto v0.0.0-20240708141625-4ad9e859172b/go.mod h1:FfBgJBJg9GcpPvKIuHSZ/aE1g2ecGL74upMzGZjiGEY=
google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 h1:0+ozOGcrp+Y8Aq8TLNN2Aliibms5LEzsq99ZZmAGYm0=
google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094/go.mod h1:fJ/e3If/Q67Mj99hin0hMhiNyCRmt6BQ2aWIJshUSJw=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240708141625-4ad9e859172b h1:04+jVzTs2XBnOZcPsLnmrTGqltqJbZQ1Ey26hjYdQQ0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240708141625-4ad9e859172b/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
google.golang.org/genproto v0.0.0-20240730163845-b1a4ccb954bf h1:OqdXDEakZCVtDiZTjcxfwbHPCT11ycCEsTKesBVKvyY=
google.golang.org/genproto v0.0.0-20240730163845-b1a4ccb954bf/go.mod h1:mCr1K1c8kX+1iSBREvU3Juo11CB+QOEWxbRS01wWl5M=
google.golang.org/genproto/googleapis/api v0.0.0-20240725223205-93522f1f2a9f h1:b1Ln/PG8orm0SsBbHZWke8dDp2lrCD4jSmfglFpTZbk=
google.golang.org/genproto/googleapis/api v0.0.0-20240725223205-93522f1f2a9f/go.mod h1:AHT0dDg3SoMOgZGnZk29b5xTbPHMoEC8qthmBLJCpys=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240730163845-b1a4ccb954bf h1:liao9UHurZLtiEwBgT9LMOnKYsHze6eA6w1KQCMVN2Q=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240730163845-b1a4ccb954bf/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA=
google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0=
google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc=
google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ=
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=
@@ -869,12 +875,12 @@ k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw=
k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780=
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA=
k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI=
k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0 h1:jgGTlFYnhF1PM1Ax/lAlxUPE+KfCIXHaathvJg1C3ak=
k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
sigs.k8s.io/release-utils v0.7.7 h1:JKDOvhCk6zW8ipEOkpTGDH/mW3TI+XqtPp16aaQ79FU=
sigs.k8s.io/release-utils v0.7.7/go.mod h1:iU7DGVNi3umZJ8q6aHyUFzsDUIaYwNnNKGHo3YE5E3s=
sigs.k8s.io/release-utils v0.8.4 h1:4QVr3UgbyY/d9p74LBhg0njSVQofUsAZqYOzVZBhdBw=
sigs.k8s.io/release-utils v0.8.4/go.mod h1:m1bHfscTemQp+z+pLCZnkXih9n0+WukIUU70n6nFnU0=
sigs.k8s.io/structured-merge-diff/v4 v4.3.0 h1:UZbZAZfX0wV2zr7YZorDz6GXROfDFj6LvqCRm4VUVKk=
sigs.k8s.io/structured-merge-diff/v4 v4.3.0/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08=
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=

View File

@@ -1,42 +1,42 @@
{
"signatures": [
{
"keyid": "b7474a42f2588fa92ed4a2ebea6047a7b1b2f7351f1cfe0912732c0d0fb0fc09",
"sig": "3064023037bbb03c3472b140572a7d5a2895bd80e74435bbcb7053949731f81b104c6d05a0876590cd6a2e94d7ed619426a2f6fa02303adc8c9006fa5506fdd7ea87d2960074a537ad8bf2459f2863e806b47682cbb2f9b01b7502eaf5437a1a68fdaaeac114"
"keyid": "76d0a7e1ff8617ce99627d0fa5c9809f2c0f0d52e0bf65c7b84c031608d25221",
"sig": "3065023000f7d0a866576e94eaabc173b9233d4c8fcfa495527088f9022dff5a553f7a457da1015a6d0fc714f84848ec627387360231009fa70b2eebbe15241a2ec9b96a094ebd28661e30b8c3d1eab8d694df2b340bda511c489393630c9a9dacde42c99e9fa1"
}
],
"signed": {
"_type": "root",
"consistent_snapshot": true,
"expires": "2034-04-02T17:00:22Z",
"expires": "2034-05-29T20:14:11Z",
"keys": {
"198f00ff96ea7cbfa7eac480cc9bfc43ce13bb434b901011ab777856533997d3": {
"keytype": "ecdsa",
"keyval": {
"public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEgDpP6O0sEt2R+l84WlfmqPBsFSby\nxJsJ6YmeUVgDk/wk9++8IAR6YBYewaKye56gMnIYjTFbyOI8WomA2NQFBw==\n-----END PUBLIC KEY-----\n"
},
"scheme": "ecdsa-sha2-nistp256",
"x-tuf-on-ci-online-uri": "awskms:arn:aws:kms:us-east-1:175142243308:key/fbd8dab6-5677-4b57-87e6-8369c45b3b61"
},
"b7474a42f2588fa92ed4a2ebea6047a7b1b2f7351f1cfe0912732c0d0fb0fc09": {
"76d0a7e1ff8617ce99627d0fa5c9809f2c0f0d52e0bf65c7b84c031608d25221": {
"keytype": "ecdsa",
"keyval": {
"public": "-----BEGIN PUBLIC KEY-----\nMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE3+asmp2GD6UijwWvMezwVG/BwFLuQa3o\nT6eRxFvkILGpVDbZ92ZYWidHl9LZ/eJUjhIjuVEkNVKoenw5KjKl8veP3MthZrQA\nSkYytOIwkidZo9Rk2dczbDcFSJvLGsmd\n-----END PUBLIC KEY-----\n"
},
"scheme": "ecdsa-sha2-nistp384",
"x-tuf-on-ci-keyowner": "@mrjoelkamp"
},
"bdd1703ecbde8812614b112a6551d58de3ad681048fd90fca2a3e491edd8afe5": {
"keytype": "ecdsa",
"keyval": {
"public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEgDpP6O0sEt2R+l84WlfmqPBsFSby\nxJsJ6YmeUVgDk/wk9++8IAR6YBYewaKye56gMnIYjTFbyOI8WomA2NQFBw==\n-----END PUBLIC KEY-----\n"
},
"scheme": "ecdsa-sha2-nistp256",
"x-tuf-on-ci-online-uri": "awskms:arn:aws:kms:us-east-1:175142243308:key/fbd8dab6-5677-4b57-87e6-8369c45b3b61"
}
},
"roles": {
"root": {
"keyids": [
"b7474a42f2588fa92ed4a2ebea6047a7b1b2f7351f1cfe0912732c0d0fb0fc09"
"76d0a7e1ff8617ce99627d0fa5c9809f2c0f0d52e0bf65c7b84c031608d25221"
],
"threshold": 1
},
"snapshot": {
"keyids": [
"198f00ff96ea7cbfa7eac480cc9bfc43ce13bb434b901011ab777856533997d3"
"bdd1703ecbde8812614b112a6551d58de3ad681048fd90fca2a3e491edd8afe5"
],
"threshold": 1,
"x-tuf-on-ci-expiry-period": 3650,
@@ -44,13 +44,13 @@
},
"targets": {
"keyids": [
"b7474a42f2588fa92ed4a2ebea6047a7b1b2f7351f1cfe0912732c0d0fb0fc09"
"76d0a7e1ff8617ce99627d0fa5c9809f2c0f0d52e0bf65c7b84c031608d25221"
],
"threshold": 1
},
"timestamp": {
"keyids": [
"198f00ff96ea7cbfa7eac480cc9bfc43ce13bb434b901011ab777856533997d3"
"bdd1703ecbde8812614b112a6551d58de3ad681048fd90fca2a3e491edd8afe5"
],
"threshold": 1,
"x-tuf-on-ci-expiry-period": 3650,

View File

@@ -16,11 +16,13 @@ var prodRoot []byte
var defaultRoot = prodRoot
type RootName string
type EmbeddedRoot struct {
Data []byte
Name RootName
}
type (
RootName string
EmbeddedRoot struct {
Data []byte
Name RootName
}
)
var (
RootDev = EmbeddedRoot{Data: devRoot, Name: "dev"}

View File

@@ -22,12 +22,12 @@ import (
)
const (
USE_MOCK_TL = true
USE_MOCK_KMS = true
USE_MOCK_POLICY = true
UseMockTL = true
UseMockKMS = true
UseMockPolicy = true
AwsRegion = "us-east-1"
AwsKmsKeyArn = "arn:aws:kms:us-east-1:175142243308:alias/doi-signing" // sandbox
AWSRegion = "us-east-1"
AWSKMSKeyARN = "arn:aws:kms:us-east-1:175142243308:alias/doi-signing" // sandbox
)
func CreateTempDir(t *testing.T, dir, pattern string) string {
@@ -46,9 +46,17 @@ func CreateTempDir(t *testing.T, dir, pattern string) string {
return tempDir
}
func GetMockSigner(_ context.Context) (dsse.SignerVerifier, error) {
priv, err := os.ReadFile(filepath.Join("..", "..", "test", "testdata", "test-signing-key.pem"))
if err != nil {
return nil, err
}
return signerverifier.LoadKeyPair(priv)
}
func Setup(t *testing.T) (context.Context, dsse.SignerVerifier) {
var tl tlog.TL
if USE_MOCK_TL {
if UseMockTL {
tl = tlog.GetMockTL()
} else {
tl = &tlog.RekorTL{}
@@ -56,8 +64,8 @@ func Setup(t *testing.T) (context.Context, dsse.SignerVerifier) {
ctx := tlog.WithTL(context.Background(), tl)
var policyEvaluator policy.PolicyEvaluator
if USE_MOCK_POLICY {
var policyEvaluator policy.Evaluator
if UseMockPolicy {
policyEvaluator = policy.GetMockPolicy()
} else {
policyEvaluator = policy.NewRegoEvaluator(true)
@@ -67,13 +75,13 @@ func Setup(t *testing.T) (context.Context, dsse.SignerVerifier) {
var signer dsse.SignerVerifier
var err error
if USE_MOCK_KMS {
if UseMockKMS {
signer, err = GetMockSigner(ctx)
if err != nil {
t.Fatal(err)
}
} else {
signer, err = signerverifier.GetAWSSigner(ctx, AwsKmsKeyArn, AwsRegion)
signer, err = signerverifier.GetAWSSigner(ctx, AWSKMSKeyARN, AWSRegion)
if err != nil {
t.Fatal(err)
}
@@ -82,14 +90,6 @@ func Setup(t *testing.T) (context.Context, dsse.SignerVerifier) {
return ctx, signer
}
func GetMockSigner(ctx context.Context) (dsse.SignerVerifier, error) {
priv, err := os.ReadFile(filepath.Join("..", "..", "test", "testdata", "test-signing-key.pem"))
if err != nil {
return nil, err
}
return signerverifier.LoadKeyPair(priv)
}
type AnnotatedStatement struct {
OCIDescriptor *v1.Descriptor
InTotoStatement *intoto.Statement
@@ -104,7 +104,8 @@ func ExtractStatementsFromIndex(idx v1.ImageIndex, mediaType string) ([]*Annotat
var statements []*AnnotatedStatement
for _, mf := range mfs2.Manifests {
for i := range mfs2.Manifests {
mf := &mfs2.Manifests[i]
if mf.Annotations[attestation.DockerReferenceType] != "attestation-manifest" {
continue
}
@@ -133,10 +134,10 @@ func ExtractStatementsFromIndex(idx v1.ImageIndex, mediaType string) ([]*Annotat
return nil, fmt.Errorf("failed to get layer contents: %w", err)
}
defer r.Close()
var intotoStatement = new(intoto.Statement)
inTotoStatement := new(intoto.Statement)
var desc *v1.Descriptor
if strings.HasSuffix(string(mt), "+dsse") {
var env = new(attestation.Envelope)
env := new(attestation.Envelope)
err = json.NewDecoder(r).Decode(env)
if err != nil {
return nil, fmt.Errorf("failed to decode env: %w", err)
@@ -145,7 +146,7 @@ func ExtractStatementsFromIndex(idx v1.ImageIndex, mediaType string) ([]*Annotat
if err != nil {
return nil, fmt.Errorf("failed to decode payload: %w", err)
}
err = json.Unmarshal([]byte(payload), intotoStatement)
err = json.Unmarshal([]byte(payload), inTotoStatement)
if err != nil {
return nil, fmt.Errorf("failed to decode %s statement: %w", mediaType, err)
}
@@ -167,7 +168,7 @@ func ExtractStatementsFromIndex(idx v1.ImageIndex, mediaType string) ([]*Annotat
}
statements = append(statements, &AnnotatedStatement{
OCIDescriptor: desc,
InTotoStatement: intotoStatement,
InTotoStatement: inTotoStatement,
Annotations: annotations,
})
}

View File

@@ -8,9 +8,7 @@ import (
"github.com/stretchr/testify/assert"
)
var (
UnsignedTestImage = filepath.Join("..", "..", "test", "testdata", "unsigned-test-image")
)
var UnsignedTestImage = filepath.Join("..", "..", "test", "testdata", "unsigned-test-image")
const (
ExpectedStatements = 4

View File

@@ -5,7 +5,6 @@ import (
"github.com/docker/attest/pkg/attest"
"github.com/docker/attest/pkg/attestation"
"github.com/docker/attest/pkg/mirror"
"github.com/docker/attest/pkg/oci"
"github.com/docker/attest/pkg/signerverifier"
v1 "github.com/google/go-containerregistry/pkg/v1"
@@ -13,7 +12,7 @@ import (
"github.com/google/go-containerregistry/pkg/v1/mutate"
)
func ExampleSign_remote() {
func ExampleSignStatements_remote() {
// configure signerverifier
// local signer (unsafe for production)
signer, err := signerverifier.GenKeyPair()
@@ -27,7 +26,7 @@ func ExampleSign_remote() {
// configure signing options
opts := &attestation.SigningOptions{
Replace: true, // replace unsigned intoto statements with signed intoto attestations, otherwise leave in place
SkipTL: true, // skip trust logging to a transparency log
}
// load image index with unsigned attestation-manifests
@@ -49,13 +48,13 @@ func ExampleSign_remote() {
panic(err)
}
signedIndex := attIdx.Index
signedIndex, err = attestation.AddImagesToIndex(signedIndex, signedManifests)
signedIndex, err = attestation.UpdateIndexImages(signedIndex, signedManifests)
if err != nil {
panic(err)
}
// push image index with signed attestation-manifests
err = mirror.PushIndexToRegistry(signedIndex, ref)
err = oci.PushIndexToRegistry(signedIndex, ref)
if err != nil {
panic(err)
}
@@ -66,11 +65,11 @@ func ExampleSign_remote() {
Add: signedIndex,
Descriptor: v1.Descriptor{
Annotations: map[string]string{
oci.OciReferenceTarget: attIdx.Name,
oci.OCIReferenceTarget: attIdx.Name,
},
},
})
err = mirror.SaveIndexAsOCILayout(idx, path)
err = oci.SaveIndexAsOCILayout(idx, path)
if err != nil {
panic(err)
}

View File

@@ -13,7 +13,7 @@ import (
"github.com/docker/attest/pkg/tuf"
)
func createTufClient(outputPath string) (*tuf.TufClient, error) {
func createTufClient(outputPath string) (*tuf.Client, error) {
// using oci tuf metadata and targets
metadataURI := "registry-1.docker.io/docker/tuf-metadata:latest"
targetsURI := "registry-1.docker.io/docker/tuf-targets"
@@ -21,7 +21,7 @@ func createTufClient(outputPath string) (*tuf.TufClient, error) {
// metadataURI := "https://docker.github.io/tuf-staging/metadata"
// targetsURI := "https://docker.github.io/tuf-staging/targets"
return tuf.NewTufClient(embed.RootStaging.Data, outputPath, metadataURI, targetsURI, tuf.NewMockVersionChecker())
return tuf.NewClient(embed.RootStaging.Data, outputPath, metadataURI, targetsURI, tuf.NewMockVersionChecker())
}
func ExampleVerify_remote() {
@@ -41,18 +41,18 @@ func ExampleVerify_remote() {
platform := "linux/amd64"
// configure policy options
opts := &policy.PolicyOptions{
TufClient: tufClient,
opts := &policy.Options{
TUFClient: tufClient,
LocalTargetsDir: filepath.Join(home, ".docker", "policy"), // location to store policy files downloaded from TUF
LocalPolicyDir: "", // overrides TUF policy for local policy files if set
PolicyId: "", // set to ignore policy mapping and select a policy by id
PolicyID: "", // set to ignore policy mapping and select a policy by id
}
// verify attestations
src, err := oci.ParseImageSpec(image, oci.WithPlatform(platform))
if err != nil {
panic(err)
}
// verify attestations
result, err := attest.Verify(context.Background(), src, opts)
if err != nil {
panic(err)

View File

@@ -9,8 +9,8 @@ import (
"github.com/secure-systems-lab/go-securesystemslib/dsse"
)
// this is only relevant if there are (unsigned) in-toto statements
func SignStatements(ctx context.Context, idx v1.ImageIndex, signer dsse.SignerVerifier, opts *attestation.SigningOptions) ([]*attestation.AttestationManifest, error) {
// this is only relevant if there are (unsigned) in-toto statements.
func SignStatements(ctx context.Context, idx v1.ImageIndex, signer dsse.SignerVerifier, opts *attestation.SigningOptions) ([]*attestation.Manifest, error) {
// extract attestation manifests from index
attestationManifests, err := attestation.GetAttestationManifestsFromIndex(idx)
if err != nil {
@@ -18,8 +18,11 @@ func SignStatements(ctx context.Context, idx v1.ImageIndex, signer dsse.SignerVe
}
// sign every attestation layer in each manifest
for _, manifest := range attestationManifests {
for _, layer := range manifest.Attestation.Layers {
manifest.AddAttestation(ctx, signer, layer.Statement, opts)
for _, layer := range manifest.OriginalLayers {
err = manifest.AddAttestation(ctx, signer, layer.Statement, opts)
if err != nil {
return nil, fmt.Errorf("failed to sign attestation layer %w", err)
}
}
}
return attestationManifests, nil

View File

@@ -2,6 +2,9 @@ package attest
import (
"encoding/json"
"fmt"
"net/http/httptest"
"net/url"
"path/filepath"
"testing"
@@ -9,10 +12,8 @@ import (
"github.com/docker/attest/pkg/attestation"
"github.com/docker/attest/pkg/oci"
"github.com/docker/attest/pkg/policy"
"github.com/google/go-containerregistry/pkg/registry"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/empty"
"github.com/google/go-containerregistry/pkg/v1/layout"
"github.com/google/go-containerregistry/pkg/v1/mutate"
"github.com/google/go-containerregistry/pkg/v1/static"
"github.com/google/go-containerregistry/pkg/v1/types"
intoto "github.com/in-toto/in-toto-golang/in_toto"
@@ -47,37 +48,25 @@ func TestSignVerifyOCILayout(t *testing.T) {
{"no provenance (replace)", NoProvenanceImage, 0, 2, true},
{"no provenance (no replace)", NoProvenanceImage, 2, 2, false},
}
policyOpts := &policy.PolicyOptions{
policyOpts := &policy.Options{
LocalPolicyDir: PassPolicyDir,
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
outputLayout := test.CreateTempDir(t, "", TestTempDir)
opts := &attestation.SigningOptions{
Replace: tc.replace,
}
opts := &attestation.SigningOptions{}
attIdx, err := oci.IndexFromPath(tc.TestImage)
require.NoError(t, err)
signedManifests, err := SignStatements(ctx, attIdx.Index, signer, opts)
require.NoError(t, err)
signedIndex := attIdx.Index
signedIndex, err = attestation.AddImagesToIndex(signedIndex, signedManifests)
signedIndex, err = attestation.UpdateIndexImages(signedIndex, signedManifests, attestation.WithReplacedLayers(tc.replace))
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)
spec, err := oci.ParseImageSpec(oci.LocalPrefix + outputLayout)
require.NoError(t, err)
src, err := oci.ParseImageSpec("oci://" + outputLayout)
err = oci.SaveIndex([]*oci.ImageSpec{spec}, signedIndex, attIdx.Name)
require.NoError(t, err)
policy, err := Verify(ctx, src, policyOpts)
policy, err := Verify(ctx, spec, policyOpts)
require.NoError(t, err)
assert.Equalf(t, OutcomeSuccess, policy.Outcome, "Policy should have been found")
@@ -102,6 +91,7 @@ func TestSignVerifyOCILayout(t *testing.T) {
}
func TestAddSignedLayerAnnotations(t *testing.T) {
ctx, signer := test.Setup(t)
testCases := []struct {
name string
replace bool
@@ -115,27 +105,30 @@ func TestAddSignedLayerAnnotations(t *testing.T) {
data := []byte("signed")
testLayer := static.NewLayer(data, types.MediaType(intoto.PayloadType))
mediaType := types.OCIManifestSchema1
opts := &attestation.SigningOptions{
Replace: tc.replace,
}
originalLayer := &attestation.AttestationLayer{
Layer: testLayer,
Statement: &intoto.Statement{},
opts := &attestation.SigningOptions{}
originalLayer := &attestation.Layer{
Layer: testLayer,
Statement: &intoto.Statement{
StatementHeader: intoto.StatementHeader{
PredicateType: attestation.VSAPredicateType,
},
},
Annotations: map[string]string{"test": "test"},
}
manifest := &attestation.AttestationManifest{
MediaType: mediaType,
Attestation: &attestation.AttestationImage{
Image: empty.Image,
Layers: []*attestation.AttestationLayer{
originalLayer,
},
manifest := &attestation.Manifest{
OriginalDescriptor: &v1.Descriptor{
MediaType: mediaType,
},
OriginalLayers: []*attestation.Layer{
originalLayer,
},
SubjectDescriptor: &v1.Descriptor{},
}
err := manifest.AddOrReplaceLayer(originalLayer, opts)
newImg := manifest.Attestation.Image
err := manifest.AddAttestation(ctx, signer, originalLayer.Statement, opts)
require.NoError(t, err)
newImg, err := manifest.BuildAttestationImage(attestation.WithReplacedLayers(tc.replace))
require.NoError(t, err)
mf, _ := newImg.RawManifest()
type Annotations struct {
@@ -152,3 +145,88 @@ func TestAddSignedLayerAnnotations(t *testing.T) {
})
}
}
func TestSimpleStatementSigning(t *testing.T) {
ctx, signer := test.Setup(t)
empty := types.MediaType("application/vnd.oci.empty.v1+json")
testCases := []struct {
name string
replace bool
}{
{"replaced", true},
{"not replaced", false},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
opts := &attestation.SigningOptions{}
statement := &intoto.Statement{
StatementHeader: intoto.StatementHeader{
PredicateType: attestation.VSAPredicateType,
},
}
statement2 := &intoto.Statement{
StatementHeader: intoto.StatementHeader{
PredicateType: attestation.VSAPredicateType,
},
}
digest, err := v1.NewHash("sha256:da8b190665956ea07890a0273e2a9c96bfe291662f08e2860e868eef69c34620")
require.NoError(t, err)
subject := &v1.Descriptor{
MediaType: "application/vnd.oci.image.manifest.v1+json",
Digest: digest,
}
manifest, err := NewAttestationManifest(subject)
require.NoError(t, err)
err = manifest.AddAttestation(ctx, signer, statement, opts)
require.NoError(t, err)
err = manifest.AddAttestation(ctx, signer, statement2, opts)
require.NoError(t, err)
// fake that the manfifest was loaded from a real image
manifest.OriginalLayers = manifest.SignedLayers
envelopes, err := oci.ExtractEnvelopes(manifest, attestation.VSAPredicateType)
require.NoError(t, err)
assert.Len(t, envelopes, 2)
newImg, err := manifest.BuildAttestationImage(attestation.WithReplacedLayers(tc.replace))
require.NoError(t, err)
layers, err := newImg.Layers()
require.NoError(t, err)
if tc.replace {
assert.Len(t, layers, 2)
} else {
assert.Len(t, layers, 4)
}
newImgs, err := manifest.BuildReferringArtifacts()
require.NoError(t, err)
assert.Len(t, newImgs, 2)
for _, img := range newImgs {
mf, err := img.Manifest()
require.NoError(t, err)
assert.Contains(t, mf.ArtifactType, "application/vnd.in-toto")
assert.Contains(t, mf.ArtifactType, "+dsse")
assert.Equal(t, subject.MediaType, mf.MediaType)
assert.Equal(t, empty, mf.Config.MediaType)
assert.Equal(t, int64(2), mf.Config.Size)
assert.Equal(t, "{}", string(mf.Config.Data))
layers, err := img.Layers()
require.NoError(t, err)
assert.Len(t, layers, 1)
}
server := httptest.NewServer(registry.New(registry.WithReferrersSupport(true)))
defer server.Close()
u, err := url.Parse(server.URL)
require.NoError(t, err)
indexName := fmt.Sprintf("%s/repo:root", u.Host)
output, err := oci.ParseImageSpecs(indexName)
require.NoError(t, err)
err = oci.SaveReferrers(manifest, output)
require.NoError(t, err)
})
}
}

View File

@@ -4,6 +4,7 @@ import (
"fmt"
"github.com/docker/attest/pkg/policy"
v1 "github.com/google/go-containerregistry/pkg/v1"
intoto "github.com/in-toto/in-toto-golang/in_toto"
)
@@ -27,9 +28,10 @@ func (o Outcome) StringForVSA() (string, error) {
}
type VerificationResult struct {
Outcome Outcome
Policy *policy.Policy
Input *policy.PolicyInput
VSA *intoto.Statement
Violations []policy.Violation
Outcome Outcome
Policy *policy.Policy
Input *policy.Input
VSA *intoto.Statement
Violations []policy.Violation
SubjectDescriptor *v1.Descriptor
}

View File

@@ -11,10 +11,11 @@ import (
"github.com/docker/attest/pkg/config"
"github.com/docker/attest/pkg/oci"
"github.com/docker/attest/pkg/policy"
v1 "github.com/google/go-containerregistry/pkg/v1"
intoto "github.com/in-toto/in-toto-golang/in_toto"
)
func Verify(ctx context.Context, src *oci.ImageSpec, opts *policy.PolicyOptions) (result *VerificationResult, err error) {
func Verify(ctx context.Context, src *oci.ImageSpec, opts *policy.Options) (result *VerificationResult, err error) {
// so that we can resolve mapping from the image name earlier
detailsResolver, err := policy.CreateImageDetailsResolver(src)
if err != nil {
@@ -60,16 +61,16 @@ func Verify(ctx context.Context, src *oci.ImageSpec, opts *policy.PolicyOptions)
return result, nil
}
func ToPolicyResult(p *policy.Policy, input *policy.PolicyInput, result *policy.Result) (*VerificationResult, error) {
func toVerificationResult(p *policy.Policy, input *policy.Input, result *policy.Result) (*VerificationResult, error) {
dgst, err := oci.SplitDigest(input.Digest)
if err != nil {
return nil, fmt.Errorf("failed to split digest: %w", err)
}
subject := intoto.Subject{
Name: input.Purl,
Name: input.PURL,
Digest: dgst,
}
resourceUri, err := attestation.ToVSAResourceURI(subject)
resourceURI, err := attestation.ToVSAResourceURI(subject)
if err != nil {
return nil, fmt.Errorf("failed to create resource uri: %w", err)
}
@@ -102,7 +103,7 @@ func ToPolicyResult(p *policy.Policy, input *policy.PolicyInput, result *policy.
ID: result.Summary.Verifier,
},
TimeVerified: time.Now().UTC().Format(time.RFC3339),
ResourceUri: resourceUri,
ResourceURI: resourceURI,
Policy: attestation.VSAPolicy{URI: result.Summary.PolicyURI},
VerificationResult: outcomeStr,
VerifiedLevels: result.Summary.SLSALevels,
@@ -112,10 +113,11 @@ func ToPolicyResult(p *policy.Policy, input *policy.PolicyInput, result *policy.
}
func VerifyAttestations(ctx context.Context, resolver oci.AttestationResolver, pctx *policy.Policy) (*VerificationResult, error) {
digest, err := resolver.ImageDigest(ctx)
desc, err := resolver.ImageDescriptor(ctx)
if err != nil {
return nil, fmt.Errorf("failed to get image digest: %w", err)
return nil, fmt.Errorf("failed to get image descriptor: %w", err)
}
digest := desc.Digest.String()
name, err := resolver.ImageName(ctx)
if err != nil {
return nil, fmt.Errorf("failed to get image name: %w", err)
@@ -141,9 +143,9 @@ func VerifyAttestations(ctx context.Context, resolver oci.AttestationResolver, p
if err != nil {
return nil, fmt.Errorf("failed to convert ref to purl: %w", err)
}
input := &policy.PolicyInput{
input := &policy.Input{
Digest: digest,
Purl: purl,
PURL: purl,
IsCanonical: canonical,
}
@@ -155,5 +157,20 @@ func VerifyAttestations(ctx context.Context, resolver oci.AttestationResolver, p
if err != nil {
return nil, fmt.Errorf("policy evaluation failed: %w", err)
}
return ToPolicyResult(pctx, input, result)
verificationResult, err := toVerificationResult(pctx, input, result)
if err != nil {
return nil, fmt.Errorf("failed to convert to policy result: %w", err)
}
verificationResult.SubjectDescriptor = desc
return verificationResult, nil
}
func NewAttestationManifest(subject *v1.Descriptor) (*attestation.Manifest, error) {
return &attestation.Manifest{
OriginalDescriptor: &v1.Descriptor{
MediaType: "application/vnd.oci.image.manifest.v1+json",
},
OriginalLayers: []*attestation.Layer{},
SubjectDescriptor: subject,
}, nil
}

View File

@@ -10,20 +10,15 @@ import (
"github.com/docker/attest/internal/test"
"github.com/docker/attest/pkg/attestation"
"github.com/docker/attest/pkg/config"
"github.com/docker/attest/pkg/oci"
"github.com/docker/attest/pkg/policy"
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 (
ExampleAttestation = filepath.Join("..", "..", "test", "testdata", "example_attestation.json")
)
var ExampleAttestation = filepath.Join("..", "..", "test", "testdata", "example_attestation.json")
const (
LinuxAMD64 = "linux/amd64"
@@ -33,7 +28,7 @@ func TestVerifyAttestations(t *testing.T) {
ex, err := os.ReadFile(ExampleAttestation)
assert.NoError(t, err)
var env = new(attestation.Envelope)
env := new(attestation.Envelope)
err = json.Unmarshal(ex, env)
assert.NoError(t, err)
resolver := &oci.MockResolver{
@@ -51,9 +46,8 @@ func TestVerifyAttestations(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
mockPE := policy.MockPolicyEvaluator{
EvaluateFunc: func(ctx context.Context, resolver oci.AttestationResolver, pctx *policy.Policy, input *policy.PolicyInput) (*policy.Result, error) {
EvaluateFunc: func(_ context.Context, _ oci.AttestationResolver, _ *policy.Policy, _ *policy.Input) (*policy.Result, error) {
return policy.AllowedResult(), tc.policyEvaluationError
},
}
@@ -77,37 +71,27 @@ func TestVSA(t *testing.T) {
// setup an image with signed attestations
outputLayout := test.CreateTempDir(t, "", TestTempDir)
opts := &attestation.SigningOptions{
Replace: true,
}
opts := &attestation.SigningOptions{}
attIdx, err := oci.IndexFromPath(UnsignedTestImage)
assert.NoError(t, err)
signedManifests, err := SignStatements(ctx, attIdx.Index, signer, opts)
require.NoError(t, err)
signedIndex := attIdx.Index
signedIndex, err = attestation.AddImagesToIndex(signedIndex, signedManifests)
signedIndex, err = attestation.UpdateIndexImages(signedIndex, signedManifests)
require.NoError(t, err)
// output signed attestations
idx := v1.ImageIndex(empty.Index)
idx = mutate.AppendManifests(idx, mutate.IndexAddendum{
Add: signedIndex,
Descriptor: v1.Descriptor{
Annotations: map[string]string{
oci.OciReferenceTarget: attIdx.Name,
},
},
})
_, err = layout.Write(outputLayout, idx)
spec, err := oci.ParseImageSpec(oci.LocalPrefix+outputLayout, oci.WithPlatform(LinuxAMD64))
require.NoError(t, err)
err = oci.SaveIndex([]*oci.ImageSpec{spec}, signedIndex, attIdx.Name)
assert.NoError(t, err)
// mocked vsa query should pass
policyOpts := &policy.PolicyOptions{
LocalPolicyDir: PassPolicyDir,
policyOpts := &policy.Options{
LocalPolicyDir: PassPolicyDir,
AttestationStyle: config.AttestationStyleAttached,
}
src, err := oci.ParseImageSpec("oci://"+outputLayout, oci.WithPlatform(LinuxAMD64))
require.NoError(t, err)
results, err := Verify(ctx, src, policyOpts)
results, err := Verify(ctx, spec, policyOpts)
require.NoError(t, err)
assert.Equal(t, OutcomeSuccess, results.Outcome)
assert.Empty(t, results.Violations)
@@ -122,7 +106,8 @@ func TestVSA(t *testing.T) {
assert.Len(t, results.VSA.Subject, 1)
require.IsType(t, attestation.VSAPredicate{}, results.VSA.Predicate)
attestationPredicate := results.VSA.Predicate.(attestation.VSAPredicate)
attestationPredicate, ok := results.VSA.Predicate.(attestation.VSAPredicate)
require.True(t, ok)
assert.Equal(t, "PASSED", attestationPredicate.VerificationResult)
assert.Equal(t, "docker-official-images", attestationPredicate.Verifier.ID)
@@ -136,37 +121,27 @@ func TestVerificationFailure(t *testing.T) {
// setup an image with signed attestations
outputLayout := test.CreateTempDir(t, "", TestTempDir)
opts := &attestation.SigningOptions{
Replace: true,
}
opts := &attestation.SigningOptions{}
attIdx, err := oci.IndexFromPath(UnsignedTestImage)
assert.NoError(t, err)
signedManifests, err := SignStatements(ctx, attIdx.Index, signer, opts)
require.NoError(t, err)
signedIndex := attIdx.Index
signedIndex, err = attestation.AddImagesToIndex(signedIndex, signedManifests)
signedIndex, err = attestation.UpdateIndexImages(signedIndex, signedManifests, attestation.WithReplacedLayers(true))
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)
spec, err := oci.ParseImageSpec(oci.LocalPrefix+outputLayout, oci.WithPlatform(LinuxAMD64))
require.NoError(t, err)
err = oci.SaveIndex([]*oci.ImageSpec{spec}, signedIndex, attIdx.Name)
assert.NoError(t, err)
// mocked vsa query should fail
policyOpts := &policy.PolicyOptions{
LocalPolicyDir: FailPolicyDir,
policyOpts := &policy.Options{
LocalPolicyDir: FailPolicyDir,
AttestationStyle: config.AttestationStyleAttached,
}
src, err := oci.ParseImageSpec("oci://"+outputLayout, oci.WithPlatform(LinuxAMD64))
require.NoError(t, err)
results, err := Verify(ctx, src, policyOpts)
results, err := Verify(ctx, spec, policyOpts)
require.NoError(t, err)
assert.Equal(t, OutcomeFailure, results.Outcome)
assert.Len(t, results.Violations, 1)
@@ -181,7 +156,8 @@ func TestVerificationFailure(t *testing.T) {
assert.Len(t, results.VSA.Subject, 1)
require.IsType(t, attestation.VSAPredicate{}, results.VSA.Predicate)
attestationPredicate := results.VSA.Predicate.(attestation.VSAPredicate)
attestationPredicate, ok := results.VSA.Predicate.(attestation.VSAPredicate)
require.True(t, ok)
assert.Equal(t, "FAILED", attestationPredicate.VerificationResult)
assert.Equal(t, "docker-official-images", attestationPredicate.Verifier.ID)
@@ -215,14 +191,13 @@ func TestSignVerify(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
opts := &attestation.SigningOptions{
Replace: true,
SkipTL: tc.signTL,
SkipTL: tc.signTL,
}
signedManifests, err := SignStatements(ctx, attIdx.Index, signer, opts)
require.NoError(t, err)
signedIndex := attIdx.Index
signedIndex, err = attestation.AddImagesToIndex(signedIndex, signedManifests)
signedIndex, err = attestation.UpdateIndexImages(signedIndex, signedManifests, attestation.WithReplacedLayers(true))
require.NoError(t, err)
imageName := tc.imageName
@@ -230,24 +205,15 @@ func TestSignVerify(t *testing.T) {
imageName = attIdx.Name
}
// 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: imageName,
},
},
})
_, err = layout.Write(outputLayout, idx)
assert.NoError(t, err)
spec, err := oci.ParseImageSpec(oci.LocalPrefix+outputLayout, oci.WithPlatform(LinuxAMD64))
require.NoError(t, err)
err = oci.SaveIndex([]*oci.ImageSpec{spec}, signedIndex, imageName)
require.NoError(t, err)
policyOpts := &policy.PolicyOptions{
policyOpts := &policy.Options{
LocalPolicyDir: tc.policyDir,
}
src, err := oci.ParseImageSpec("oci://"+outputLayout, oci.WithPlatform(LinuxAMD64))
require.NoError(t, err)
results, err := Verify(ctx, src, policyOpts)
results, err := Verify(ctx, spec, policyOpts)
if tc.expectError {
require.Error(t, err)
return
@@ -258,7 +224,7 @@ func TestSignVerify(t *testing.T) {
require.NoError(t, err)
expectedPURL, _, err := oci.RefToPURL(attIdx.Name, platform)
require.NoError(t, err)
assert.Equal(t, expectedPURL, results.Input.Purl)
assert.Equal(t, expectedPURL, results.Input.PURL)
})
}
}

View File

@@ -1,6 +1,7 @@
package attestation
import (
"bytes"
"context"
"encoding/json"
"fmt"
@@ -17,54 +18,52 @@ import (
"github.com/secure-systems-lab/go-securesystemslib/dsse"
)
// GetAttestationManifestsFromIndex extracts all attestation manifests from an index
func GetAttestationManifestsFromIndex(index v1.ImageIndex) ([]*AttestationManifest, error) {
// GetAttestationManifestsFromIndex extracts all attestation manifests from an index.
func GetAttestationManifestsFromIndex(index v1.ImageIndex) ([]*Manifest, error) {
idx, err := index.IndexManifest()
if err != nil {
return nil, fmt.Errorf("failed to extract IndexManifest from ImageIndex: %w", err)
}
subjects := make(map[string]*v1.Descriptor)
for _, subject := range idx.Manifests {
subjects[subject.Digest.String()] = &subject
for i := range idx.Manifests {
subject := &idx.Manifests[i]
subjects[subject.Digest.String()] = subject
}
var attestationManifests []*AttestationManifest
for _, manifest := range idx.Manifests {
if manifest.Annotations[DockerReferenceType] == AttestationManifestType {
subject := subjects[manifest.Annotations[DockerReferenceDigest]]
var attestationManifests []*Manifest
for i := range idx.Manifests {
desc := idx.Manifests[i]
if desc.Annotations[DockerReferenceType] == AttestationManifestType {
subject := subjects[desc.Annotations[DockerReferenceDigest]]
if subject == nil {
return nil, fmt.Errorf("failed to find subject for attestation manifest: %w", err)
}
attestationImage, err := index.Image(manifest.Digest)
attestationImage, err := index.Image(desc.Digest)
if err != nil {
return nil, fmt.Errorf("failed to extract attestation image with digest %s: %w", manifest.Digest.String(), err)
return nil, fmt.Errorf("failed to extract attestation image with digest %s: %w", desc.Digest.String(), err)
}
attestationLayers, err := GetAttestationsFromImage(attestationImage)
if err != nil {
return nil, fmt.Errorf("failed to get attestations from image: %w", err)
}
attestationManifests = append(attestationManifests,
&AttestationManifest{
Descriptor: &manifest,
SubjectDescriptor: subject,
Attestation: &AttestationImage{
Layers: attestationLayers,
Image: attestationImage},
MediaType: manifest.MediaType,
Annotations: manifest.Annotations,
Digest: manifest.Digest})
&Manifest{
OriginalDescriptor: &desc,
SubjectDescriptor: subject,
OriginalLayers: attestationLayers,
})
}
}
return attestationManifests, nil
}
// GetAttestationsFromImage extracts all attestation layers from an image
func GetAttestationsFromImage(image v1.Image) ([]*AttestationLayer, error) {
// GetAttestationsFromImage extracts all attestation layers from an image.
func GetAttestationsFromImage(image v1.Image) ([]*Layer, error) {
layers, err := image.Layers()
if err != nil {
return nil, fmt.Errorf("failed to extract layers from image: %w", err)
}
var attestationLayers []*AttestationLayer
var attestationLayers []*Layer
for _, layer := range layers {
// parse layer blob as json
r, err := layer.Uncompressed()
@@ -83,34 +82,28 @@ func GetAttestationsFromImage(image v1.Image) ([]*AttestationLayer, error) {
// copy original annotations
ann := maps.Clone(layerDesc.Annotations)
// only decode intoto statements
var stmt = new(intoto.Statement)
stmt := new(intoto.Statement)
if mt == types.MediaType(intoto.PayloadType) {
err = json.NewDecoder(r).Decode(&stmt)
if err != nil {
return nil, fmt.Errorf("failed to decode statement layer contents: %w", err)
}
}
attestationLayers = append(attestationLayers, &AttestationLayer{Layer: layer, MediaType: mt, Statement: stmt, Annotations: ann})
attestationLayers = append(attestationLayers, &Layer{Layer: layer, Statement: stmt, Annotations: ann})
}
return attestationLayers, nil
}
func (manifest *AttestationManifest) AddAttestation(ctx context.Context, signer dsse.SignerVerifier, statement *intoto.Statement, opts *SigningOptions) error {
func (manifest *Manifest) AddAttestation(ctx context.Context, signer dsse.SignerVerifier, statement *intoto.Statement, opts *SigningOptions) error {
layer, err := createSignedImageLayer(ctx, statement, signer, opts)
if err != nil {
return fmt.Errorf("failed to create signed layer: %w", err)
}
newImg, newDesc, err := addLayerToImage(manifest, layer, opts)
if err != nil {
return fmt.Errorf("failed to add signed layers to image: %w", err)
}
manifest.Attestation.Image = newImg
manifest.Descriptor = newDesc
manifest.SignedLayers = append(manifest.SignedLayers, layer)
return nil
}
func createSignedImageLayer(ctx context.Context, statement *intoto.Statement, signer dsse.SignerVerifier, opts *SigningOptions) (*AttestationLayer, error) {
func createSignedImageLayer(ctx context.Context, statement *intoto.Statement, signer dsse.SignerVerifier, opts *SigningOptions) (*Layer, error) {
// sign the statement
env, err := SignInTotoStatement(ctx, statement, signer, opts)
if err != nil {
@@ -125,9 +118,8 @@ func createSignedImageLayer(ctx context.Context, statement *intoto.Statement, si
if err != nil {
return nil, fmt.Errorf("failed to marshal envelope: %w", err)
}
return &AttestationLayer{
return &Layer{
Statement: statement,
MediaType: types.MediaType(intoto.PayloadType),
Annotations: map[string]string{
InTotoPredicateType: statement.PredicateType,
InTotoReferenceLifecycleStage: LifecycleStageExperimental,
@@ -148,104 +140,202 @@ func SignInTotoStatement(ctx context.Context, statement *intoto.Statement, signe
return env, nil
}
func addLayerToImage(
manifest *AttestationManifest,
layer *AttestationLayer,
opts *SigningOptions) (v1.Image, *v1.Descriptor, error) {
err := manifest.AddOrReplaceLayer(layer, opts)
if err != nil {
return nil, nil, fmt.Errorf("failed to add signed layers: %w", err)
}
newImg := manifest.Attestation.Image
if !opts.SkipSubject {
newImg = mutate.Subject(newImg, *manifest.SubjectDescriptor).(v1.Image)
}
newDesc, err := partial.Descriptor(newImg)
if err != nil {
return nil, nil, fmt.Errorf("failed to get descriptor: %w", err)
}
cf, err := manifest.Attestation.Image.ConfigFile()
if err != nil {
return nil, nil, fmt.Errorf("failed to get config file: %w", err)
}
newDesc.Platform = cf.Platform()
if newDesc.Platform == nil {
newDesc.Platform = &v1.Platform{
Architecture: "unknown",
OS: "unknown",
}
}
newDesc.MediaType = manifest.MediaType
newDesc.Annotations = manifest.Annotations
return newImg, newDesc, nil
}
// AddOrReplaceLayer adds signed layers to a new or existing attestation image
// NOTE: the pointers attestation.AttestationLayer.Statement are compared when replacing,
// so make sure you are signing a layer extracted from the original attestation-manifest image!
func (manifest *AttestationManifest) AddOrReplaceLayer(signedLayer *AttestationLayer, opts *SigningOptions) error {
var err error
// always create a new image from all the layers
newImg := empty.Image
newImg = mutate.Annotations(newImg, map[string]string{
DockerReferenceType: AttestationManifestType,
DockerReferenceDigest: manifest.SubjectDescriptor.Digest.String(),
}).(v1.Image)
newImg = mutate.MediaType(newImg, manifest.MediaType)
newImg = mutate.ConfigMediaType(newImg, "application/vnd.oci.image.config.v1+json")
add := mutate.Addendum{
Layer: signedLayer.Layer,
Annotations: signedLayer.Annotations,
}
newImg, err = mutate.Append(newImg, add)
if err != nil {
return fmt.Errorf("failed to add signed layer to image: %w", err)
}
layers := make([]*AttestationLayer, 0)
for _, layer := range manifest.Attestation.Layers {
if layer.Statement == signedLayer.Statement && opts.Replace {
continue
}
add := mutate.Addendum{
Layer: layer.Layer,
Annotations: layer.Annotations,
}
newImg, err = mutate.Append(newImg, add)
layers = append(layers, layer)
if err != nil {
return fmt.Errorf("failed to add layer to image: %w", err)
}
}
manifest.Attestation.Layers = append(layers, signedLayer)
manifest.Attestation.Image = newImg
return nil
}
func AddImageToIndex(
func UpdateIndexImage(
idx v1.ImageIndex,
manifest *AttestationManifest,
manifest *Manifest,
options ...func(*ManifestImageOptions) error,
) (v1.ImageIndex, error) {
idx = mutate.RemoveManifests(idx, match.Digests(manifest.Digest))
image, err := manifest.BuildAttestationImage(options...)
if err != nil {
return nil, fmt.Errorf("failed to build image: %w", err)
}
newDesc, err := partial.Descriptor(image)
if err != nil {
return nil, fmt.Errorf("failed to get descriptor: %w", err)
}
newDesc.Platform = &v1.Platform{
Architecture: "unknown",
OS: "unknown",
}
newDesc.MediaType = manifest.OriginalDescriptor.MediaType
newDesc.Annotations = manifest.OriginalDescriptor.Annotations
idx = mutate.RemoveManifests(idx, match.Digests(manifest.OriginalDescriptor.Digest))
idx = mutate.AppendManifests(idx, mutate.IndexAddendum{
Add: manifest.Attestation.Image,
Descriptor: *manifest.Descriptor,
Add: image,
Descriptor: *newDesc,
})
return idx, nil
}
func AddImagesToIndex(
idx v1.ImageIndex,
manifests []*AttestationManifest,
) (v1.ImageIndex, error) {
for _, manifest := range manifests {
var err error
idx, err = AddImageToIndex(idx, manifest)
func UpdateIndexImages(idx v1.ImageIndex, manifest []*Manifest, options ...func(*ManifestImageOptions) error) (v1.ImageIndex, error) {
var err error
for _, m := range manifest {
idx, err = UpdateIndexImage(idx, m, options...)
if err != nil {
return nil, fmt.Errorf("failed to add image to index: %w", err)
}
}
return idx, nil
}
func newOptions(options ...func(*ManifestImageOptions) error) (*ManifestImageOptions, error) {
opts := &ManifestImageOptions{}
for _, opt := range options {
err := opt(opts)
if err != nil {
return nil, err
}
}
return opts, nil
}
func WithoutSubject(skipSubject bool) func(*ManifestImageOptions) error {
return func(r *ManifestImageOptions) error {
r.skipSubject = skipSubject
return nil
}
}
func WithReplacedLayers(replaceLayers bool) func(*ManifestImageOptions) error {
return func(r *ManifestImageOptions) error {
r.replaceLayers = replaceLayers
return nil
}
}
// build an image with signed attestations, optionally replacing existing layers with signed layers.
func (manifest *Manifest) BuildAttestationImage(options ...func(*ManifestImageOptions) error) (v1.Image, error) {
opts, err := newOptions(options...)
if err != nil {
return nil, fmt.Errorf("failed to create options: %w", err)
}
resultLayers := manifest.SignedLayers
for _, existingLayer := range manifest.OriginalLayers {
var found bool
for _, signedLayer := range manifest.SignedLayers {
if existingLayer.Statement == signedLayer.Statement {
found = true
// copy over original annotations
for k, v := range existingLayer.Annotations {
signedLayer.Annotations[k] = v
}
break
}
}
// add existing layers if they've not been signed or we're not replacing them
if !found || !opts.replaceLayers {
resultLayers = append(resultLayers, existingLayer)
}
}
// so that we attach all attestations to a single attestations image - as per current buildkit
opts.laxReferrers = true
newImg, err := buildImage(resultLayers, manifest.OriginalDescriptor, manifest.SubjectDescriptor, opts)
if err != nil {
return nil, fmt.Errorf("failed to build image: %w", err)
}
return newImg, nil
}
// build an image per attestation (layer) suitable for use as Referrers.
func (manifest *Manifest) BuildReferringArtifacts() ([]v1.Image, error) {
var images []v1.Image
for _, layer := range manifest.SignedLayers {
opts := &ManifestImageOptions{}
newImg, err := buildImage([]*Layer{layer}, manifest.OriginalDescriptor, manifest.SubjectDescriptor, opts)
if err != nil {
return nil, fmt.Errorf("failed to build image: %w", err)
}
images = append(images, newImg)
}
return images, nil
}
// build an image containing only layers.
func buildImage(layers []*Layer, manifest *v1.Descriptor, subject *v1.Descriptor, opts *ManifestImageOptions) (v1.Image, error) {
newImg := empty.Image
var err error
if len(layers) == 0 {
return nil, fmt.Errorf("no layers supplied to build image")
}
// NB: if we add the subject before the layers, it does not end up being computed/serialized in the output for some reason
// TODO - recreate this bug and push upstream
for _, layer := range layers {
add := mutate.Addendum{
Layer: layer.Layer,
Annotations: layer.Annotations,
}
newImg, err = mutate.Append(newImg, add)
if err != nil {
return nil, fmt.Errorf("failed to add layer to image: %w", err)
}
}
// this is for attaching attestations to an attestation image in the index
if opts.laxReferrers {
newImg = mutate.ConfigMediaType(newImg, "application/vnd.oci.image.config.v1+json")
} else {
dsseMediatType, err := DSSEMediaType(layers[0].Statement.PredicateType)
if err != nil {
return nil, fmt.Errorf("failed to get DSSE media type: %w", err)
}
newImg = mutate.ArtifactType(newImg, dsseMediatType)
newImg = mutate.ConfigMediaType(newImg, "application/vnd.oci.empty.v1+json")
}
// we need to set this even when we set the artifact type otherwise things break (even the go-container-registry client)
// even though it's allowed to be empty by spec when setting artifact type
newImg = mutate.MediaType(newImg, manifest.MediaType)
// see note above - must be added after the layers!
if !opts.skipSubject {
subject.Platform = nil
ok := false
newImg, ok = mutate.Subject(newImg, *subject).(v1.Image)
if !ok {
return nil, fmt.Errorf("failed to set subject: %w", err)
}
}
if !opts.laxReferrers {
// as per https://github.com/opencontainers/image-spec/blob/main/manifest.md#guidance-for-an-empty-descriptor
newImg = &EmptyConfigImage{newImg}
}
return newImg, nil
}
type EmptyConfigImage struct {
v1.Image
}
func (i *EmptyConfigImage) RawConfigFile() ([]byte, error) {
return []byte("{}"), nil
}
func (i *EmptyConfigImage) Manifest() (*v1.Manifest, error) {
mf, err := i.Image.Manifest()
if err != nil {
return nil, fmt.Errorf("failed to get manifest: %w", err)
}
mf.Config = v1.Descriptor{
MediaType: "application/vnd.oci.empty.v1+json",
Size: 2,
Digest: v1.Hash{Algorithm: "sha256", Hex: "44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a"},
Data: []byte("{}"),
}
return mf, nil
}
func (i *EmptyConfigImage) RawManifest() ([]byte, error) {
mf, err := i.Manifest()
if err != nil {
return nil, fmt.Errorf("failed to get manifest: %w", err)
}
return json.Marshal(mf)
}
func (i *EmptyConfigImage) Digest() (v1.Hash, error) {
mb, err := i.RawManifest()
if err != nil {
return v1.Hash{}, err
}
digest, _, err := v1.SHA256(bytes.NewReader(mb))
return digest, err
}

View File

@@ -0,0 +1,86 @@
package attestation_test
import (
"context"
"time"
"github.com/docker/attest/pkg/attest"
"github.com/docker/attest/pkg/attestation"
"github.com/docker/attest/pkg/oci"
"github.com/docker/attest/pkg/signerverifier"
v1 "github.com/google/go-containerregistry/pkg/v1"
intoto "github.com/in-toto/in-toto-golang/in_toto"
"github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/common"
)
func ExampleManifest() {
// 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 := &attestation.SigningOptions{
SkipTL: true, // skip trust logging to a transparency log
}
ref := "docker/image-signer-verifier:latest"
digest, err := v1.NewHash("sha256:da8b190665956ea07890a0273e2a9c96bfe291662f08e2860e868eef69c34620")
if err != nil {
panic(err)
}
desc := &v1.Descriptor{
Digest: digest,
Size: 1234,
MediaType: "application/vnd.oci.image.manifest.v1+json",
}
// the in-toto statement to be signed
statement := &intoto.Statement{
StatementHeader: intoto.StatementHeader{
PredicateType: attestation.VSAPredicateType,
Subject: []intoto.Subject{{Name: ref, Digest: common.DigestSet{digest.Algorithm: digest.Hex}}},
Type: intoto.StatementInTotoV01,
},
Predicate: attestation.VSAPredicate{
Verifier: attestation.VSAVerifier{
ID: "test-verifier",
},
TimeVerified: time.Now().UTC().Format(time.RFC3339),
ResourceURI: "some-uri",
Policy: attestation.VSAPolicy{URI: "some-uri"},
VerificationResult: "PASSED",
VerifiedLevels: []string{"SLSA_BUILD_LEVEL_1"},
},
}
// create a new manifest to hold the attestation
manifest, err := attest.NewAttestationManifest(desc)
if err != nil {
panic(err)
}
// sign and add the attestation to the manifest
err = manifest.AddAttestation(context.Background(), signer, statement, opts)
if err != nil {
panic(err)
}
output, err := oci.ParseImageSpecs("docker/image-signer-verifier-referrers:latest")
if err != nil {
panic(err)
}
// save the manifest to the registry as a referrers artifact
err = oci.SaveReferrers(manifest, output)
if err != nil {
panic(err)
}
}

View File

@@ -8,14 +8,15 @@ import (
"testing"
"github.com/docker/attest/internal/test"
"github.com/docker/attest/internal/util"
"github.com/docker/attest/pkg/attest"
"github.com/docker/attest/pkg/attestation"
"github.com/docker/attest/pkg/config"
"github.com/docker/attest/pkg/mirror"
"github.com/docker/attest/pkg/oci"
"github.com/docker/attest/pkg/policy"
"github.com/google/go-containerregistry/pkg/name"
"github.com/google/go-containerregistry/pkg/registry"
"github.com/google/go-containerregistry/pkg/v1/empty"
"github.com/google/go-containerregistry/pkg/v1/remote"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -40,7 +41,6 @@ func TestAttestationReferenceTypes(t *testing.T) {
name string
server *httptest.Server
referrersServer *httptest.Server
skipSubject bool
useDigest bool
referrersRepo string
attestationSource config.AttestationStyle
@@ -50,16 +50,6 @@ func TestAttestationReferenceTypes(t *testing.T) {
name: "referrers support, defaults",
server: httptest.NewServer(registry.New(registry.WithReferrersSupport(true))),
},
{
name: "no referrers support",
server: httptest.NewServer(registry.New()),
},
{
name: "attached attestations",
server: httptest.NewServer(registry.New(registry.WithReferrersSupport(true))),
skipSubject: true,
attestationSource: config.AttestationStyleAttached,
},
{
name: "use digest",
server: httptest.NewServer(registry.New(registry.WithReferrersSupport(true))),
@@ -68,7 +58,7 @@ func TestAttestationReferenceTypes(t *testing.T) {
{
name: "attached attestations, referrers repo (mismatched args)",
server: httptest.NewServer(registry.New(registry.WithReferrersSupport(true))),
expectFailure: true, //mismatched args
expectFailure: true, // mismatched args
attestationSource: config.AttestationStyleAttached,
referrersRepo: "referrers",
},
@@ -102,8 +92,7 @@ func TestAttestationReferenceTypes(t *testing.T) {
require.NoError(t, err)
opts := &attestation.SigningOptions{
Replace: true,
SkipSubject: tc.skipSubject,
SkipTL: true,
}
attIdx, err := oci.IndexFromPath(UnsignedTestImage)
require.NoError(t, err)
@@ -111,26 +100,27 @@ func TestAttestationReferenceTypes(t *testing.T) {
indexName := fmt.Sprintf("%s/repo:root", u.Host)
require.NoError(t, err)
outputRepo := indexName
if tc.referrersServer != nil {
ru, err := url.Parse(s.URL)
require.NoError(t, err)
repo := fmt.Sprintf("%s/referrers", ru.Host)
tc.referrersRepo = repo
signedManifests, err := attest.SignStatements(ctx, attIdx.Index, signer, opts)
require.NoError(t, err)
err = mirror.PushIndexToRegistry(attIdx.Index, indexName)
require.NoError(t, err)
for _, img := range signedManifests {
err = mirror.PushImageToRegistry(img.Attestation.Image, fmt.Sprintf("%s:tag-does-not-matter", repo))
require.NoError(t, err)
}
} else {
signedManifests, err := attest.SignStatements(ctx, attIdx.Index, signer, opts)
require.NoError(t, err)
signedIndex := attIdx.Index
signedIndex, err = attestation.AddImagesToIndex(signedIndex, signedManifests)
require.NoError(t, err)
err = mirror.PushIndexToRegistry(signedIndex, indexName)
tc.referrersRepo = fmt.Sprintf("%s/referrers", ru.Host)
outputRepo = tc.referrersRepo
}
// sign all the statements in the index
signedManifests, err := attest.SignStatements(ctx, attIdx.Index, signer, opts)
require.NoError(t, err)
// push subject image so that it can be resolved
require.NoError(t, err)
err = oci.PushIndexToRegistry(attIdx.Index, indexName)
require.NoError(t, err)
// upload referrers
output, err := oci.ParseImageSpec(outputRepo)
require.NoError(t, err)
for _, attIdx := range signedManifests {
err = oci.SaveReferrers(attIdx, []*oci.ImageSpec{output})
require.NoError(t, err)
}
@@ -148,7 +138,7 @@ func TestAttestationReferenceTypes(t *testing.T) {
ref = fmt.Sprintf("%s/repo@%s", u.Host, idxDigest.String())
}
policyOpts := &policy.PolicyOptions{
policyOpts := &policy.Options{
LocalPolicyDir: LocalPolicy,
}
@@ -169,26 +159,23 @@ func TestAttestationReferenceTypes(t *testing.T) {
require.NoError(t, err)
assert.Equal(t, attest.OutcomeSuccess, results.Outcome)
if !tc.skipSubject {
// can evaluate policy using referrers
if tc.useDigest {
p, err := oci.ParsePlatform(platform)
require.NoError(t, err)
options := oci.WithOptions(ctx, p)
subjectRef, err := name.ParseReference(indexName)
require.NoError(t, err)
desc, err := remote.Image(subjectRef, options...)
require.NoError(t, err)
subjectDigest, err := desc.Digest()
require.NoError(t, err)
ref = fmt.Sprintf("%s/repo@%s", u.Host, subjectDigest.String())
}
src, err := oci.ParseImageSpec(ref, oci.WithPlatform(platform))
if tc.useDigest {
p, err := oci.ParsePlatform(platform)
require.NoError(t, err)
results, err = attest.Verify(ctx, src, policyOpts)
options := oci.WithOptions(ctx, p)
subjectRef, err := name.ParseReference(indexName)
require.NoError(t, err)
assert.Equal(t, attest.OutcomeSuccess, results.Outcome)
desc, err := remote.Image(subjectRef, options...)
require.NoError(t, err)
subjectDigest, err := desc.Digest()
require.NoError(t, err)
ref = fmt.Sprintf("%s/repo@%s", u.Host, subjectDigest.String())
}
src, err = oci.ParseImageSpec(ref, oci.WithPlatform(platform))
require.NoError(t, err)
results, err = attest.Verify(ctx, src, policyOpts)
require.NoError(t, err)
assert.Equal(t, attest.OutcomeSuccess, results.Outcome)
}
})
}
@@ -213,47 +200,75 @@ func TestReferencesInDifferentRepo(t *testing.T) {
refServer: httptest.NewServer(registry.New(registry.WithReferrersSupport(true))),
},
} {
t.Run(tc.name, func(t *testing.T) {
server := tc.server
defer server.Close()
serverUrl, err := url.Parse(server.URL)
server := tc.server
defer server.Close()
serverURL, err := url.Parse(server.URL)
require.NoError(t, err)
refServer := tc.refServer
defer refServer.Close()
refServerURL, err := url.Parse(refServer.URL)
require.NoError(t, err)
opts := &attestation.SigningOptions{
SkipTL: true,
}
attIdx, err := oci.IndexFromPath(UnsignedTestImage)
require.NoError(t, err)
indexName := fmt.Sprintf("%s/%s:latest", serverURL.Host, repoName)
err = oci.PushIndexToRegistry(attIdx.Index, indexName)
require.NoError(t, err)
signedManifests, err := attest.SignStatements(ctx, attIdx.Index, signer, opts)
require.NoError(t, err)
// push signed attestation image to the ref server
for _, signedManifest := range signedManifests {
// push references using subject-digest.att convention
image, err := signedManifest.BuildAttestationImage()
require.NoError(t, err)
err = oci.PushImageToRegistry(image, fmt.Sprintf("%s/%s:tag-does-not-matter", refServerURL.Host, repoName))
require.NoError(t, err)
refServer := tc.refServer
defer refServer.Close()
refServerUrl, err := url.Parse(refServer.URL)
refServerURL, err := url.Parse(refServer.URL)
require.NoError(t, err)
opts := &attestation.SigningOptions{
Replace: true,
SkipTL: true,
SkipTL: true,
}
attIdx, err := oci.IndexFromPath(UnsignedTestImage)
require.NoError(t, err)
indexName := fmt.Sprintf("%s/%s:latest", serverUrl.Host, repoName)
err = mirror.PushIndexToRegistry(attIdx.Index, indexName)
indexName := fmt.Sprintf("%s/%s:latest", serverURL.Host, repoName)
err = oci.PushIndexToRegistry(attIdx.Index, indexName)
require.NoError(t, err)
signedManifests, err := attest.SignStatements(ctx, attIdx.Index, signer, opts)
require.NoError(t, err)
// push signed attestation image to the ref server
for _, img := range signedManifests {
for _, mf := range signedManifests {
// push references using subject-digest.att convention
err = mirror.PushImageToRegistry(img.Attestation.Image, fmt.Sprintf("%s/%s:tag-does-not-matter", refServerUrl.Host, repoName))
imgs, err := mf.BuildReferringArtifacts()
require.NoError(t, err)
for _, img := range imgs {
err = oci.PushImageToRegistry(img, fmt.Sprintf("%s/%s:tag-does-not-matter", refServerURL.Host, repoName))
require.NoError(t, err)
}
}
mfs2, err := attIdx.Index.IndexManifest()
require.NoError(t, err)
for _, mf := range mfs2.Manifests {
//skip signed/unsigned attestations
// skip signed/unsigned attestations
if mf.Annotations[attestation.DockerReferenceType] == attestation.AttestationManifestType {
continue
}
// can evaluate policy using referrers in a different repo
referencedImage := fmt.Sprintf("%s@%s", indexName, mf.Digest.String())
policyOpts := &policy.PolicyOptions{
policyOpts := &policy.Options{
LocalPolicyDir: PassPolicyDir,
}
src, err := oci.ParseImageSpec(referencedImage)
@@ -262,6 +277,64 @@ func TestReferencesInDifferentRepo(t *testing.T) {
require.NoError(t, err)
assert.Equal(t, attest.OutcomeSuccess, results.Outcome)
}
})
}
}
}
func TestCorrectArtifactTypeInTagFallback(t *testing.T) {
ctx, signer := test.Setup(t)
server := httptest.NewServer(registry.New())
defer server.Close()
serverURL, err := url.Parse(server.URL)
require.NoError(t, err)
repoName := "repo"
opts := &attestation.SigningOptions{
SkipTL: true,
}
attIdx, err := oci.IndexFromPath(UnsignedTestImage)
require.NoError(t, err)
indexName := fmt.Sprintf("%s/%s:latest", serverURL.Host, repoName)
err = oci.PushIndexToRegistry(attIdx.Index, indexName)
require.NoError(t, err)
signedManifests, err := attest.SignStatements(ctx, attIdx.Index, signer, opts)
require.NoError(t, err)
// this should create and maintain an index of referrers
for _, mf := range signedManifests {
imgs, err := mf.BuildReferringArtifacts()
require.NoError(t, err)
for _, img := range imgs {
err = oci.PushImageToRegistry(img, fmt.Sprintf("%s/%s:tag-does-not-matter", serverURL.Host, repoName))
require.NoError(t, err)
mf, err := img.Manifest()
require.NoError(t, err)
subject := mf.Subject
subjectRef, err := name.ParseReference(fmt.Sprintf("%s/%s:sha256-%s", serverURL.Host, repoName, subject.Digest.Hex))
require.NoError(t, err)
idx, err := remote.Index(subjectRef)
require.NoError(t, err)
imf, err := idx.IndexManifest()
require.NoError(t, err)
for _, m := range imf.Manifests {
assert.Contains(t, m.ArtifactType, "application/vnd.in-toto")
assert.Contains(t, m.ArtifactType, "+dsse")
}
}
}
}
func TestEmptyConfigImageDigest(t *testing.T) {
empty := empty.Image
img := attestation.EmptyConfigImage{empty}
mf, err := img.RawManifest()
require.NoError(t, err)
hash := util.SHA256Hex(mf)
digest, err := img.Digest()
require.NoError(t, err)
assert.Equal(t, digest.Hex, hash)
}

View File

@@ -10,7 +10,7 @@ import (
"github.com/secure-systems-lab/go-securesystemslib/dsse"
)
// SignDSSE signs a payload with a given signer and uploads the signature to the transparency log
// SignDSSE signs a payload with a given signer and uploads the signature to the transparency log.
func SignDSSE(ctx context.Context, payload []byte, signer dsse.SignerVerifier, opts *SigningOptions) (*Envelope, error) {
payloadType := intoto.PayloadType
env := new(Envelope)
@@ -28,13 +28,13 @@ func SignDSSE(ctx context.Context, payload []byte, signer dsse.SignerVerifier, o
}
// get Key ID from signer
keyId, err := signer.KeyID()
keyID, err := signer.KeyID()
if err != nil {
return nil, fmt.Errorf("error getting public key ID: %w", err)
}
dsseSig := Signature{
KeyID: keyId,
dsseSig := &Signature{
KeyID: keyID,
Sig: base64Encoding.EncodeToString(sig),
}
if !opts.SkipTL {
@@ -42,22 +42,22 @@ func SignDSSE(ctx context.Context, payload []byte, signer dsse.SignerVerifier, o
if err != nil {
return nil, fmt.Errorf("failed to log to rekor: %w", err)
}
dsseSig.Extension = *ext
dsseSig.Extension = ext
}
// add signature to dsse envelope
env.Signatures = []Signature{dsseSig}
env.Signatures = []*Signature{dsseSig}
return env, nil
}
// returns a new envelope with the transparency log entry added to the signature extension
// returns a new envelope with the transparency log entry added to the signature extension.
func logSignature(ctx context.Context, t tlog.TL, sig *[]byte, encPayload *[]byte, signer dsse.SignerVerifier) (*Extension, error) {
// get Key ID from signer
keyId, err := signer.KeyID()
keyID, err := signer.KeyID()
if err != nil {
return nil, fmt.Errorf("error getting public key ID: %w", err)
}
entry, err := t.UploadLogEntry(ctx, keyId, *encPayload, *sig, signer)
entry, err := t.UploadLogEntry(ctx, keyID, *encPayload, *sig, signer)
if err != nil {
return nil, fmt.Errorf("error uploading TL entry: %w", err)
}
@@ -66,10 +66,10 @@ func logSignature(ctx context.Context, t tlog.TL, sig *[]byte, encPayload *[]byt
return nil, fmt.Errorf("error unmarshaling tl entry: %w", err)
}
return &Extension{
Kind: DockerDsseExtKind,
Ext: DockerDsseExtension{
Tl: DockerTlExtension{
Kind: RekorTlExtKind,
Kind: DockerDSSEExtKind,
Ext: &DockerDSSEExtension{
TL: &DockerTLExtension{
Kind: RekorTLExtKind,
Data: entryObj, // transparency log entry metadata
},
},

View File

@@ -44,20 +44,20 @@ func TestSignVerifyAttestation(t *testing.T) {
// signer.Public() used here for test purposes
ecPub, ok := signer.Public().(*ecdsa.PublicKey)
assert.True(t, ok)
pem, err := signerverifier.ToPEM(ecPub)
pem, err := signerverifier.ConvertToPEM(ecPub)
assert.NoError(t, err)
keyId, err := signerverifier.KeyID(ecPub)
keyID, err := signerverifier.KeyID(ecPub)
assert.NoError(t, err)
badKeyPriv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
require.NoError(t, err)
badKey := &badKeyPriv.PublicKey
badPEM, err := signerverifier.ToPEM(badKey)
badPEM, err := signerverifier.ConvertToPEM(badKey)
require.NoError(t, err)
testCases := []struct {
name string
keyId string
keyID string
pem []byte
distrust bool
from time.Time
@@ -67,7 +67,7 @@ func TestSignVerifyAttestation(t *testing.T) {
}{
{
name: "all OK",
keyId: keyId,
keyID: keyID,
pem: pem,
distrust: false,
from: time.Time{},
@@ -77,17 +77,17 @@ func TestSignVerifyAttestation(t *testing.T) {
},
{
name: "key not found",
keyId: "someotherkey",
keyID: "someotherkey",
pem: pem,
distrust: false,
from: time.Time{},
to: nil,
status: "active",
expectedError: fmt.Sprintf("key not found: %s", keyId),
expectedError: fmt.Sprintf("key not found: %s", keyID),
},
{
name: "key distrusted",
keyId: keyId,
keyID: keyID,
pem: pem,
distrust: true,
from: time.Time{},
@@ -97,7 +97,7 @@ func TestSignVerifyAttestation(t *testing.T) {
},
{
name: "key not yet valid",
keyId: keyId,
keyID: keyID,
pem: pem,
distrust: false,
from: time.Now().Add(time.Hour),
@@ -107,7 +107,7 @@ func TestSignVerifyAttestation(t *testing.T) {
},
{
name: "key already revoked",
keyId: keyId,
keyID: keyID,
pem: pem,
distrust: false,
from: time.Time{},
@@ -117,7 +117,7 @@ func TestSignVerifyAttestation(t *testing.T) {
},
{
name: "bad key",
keyId: keyId,
keyID: keyID,
pem: badPEM,
distrust: false,
from: time.Time{},
@@ -129,8 +129,8 @@ func TestSignVerifyAttestation(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
keyMeta := attestation.KeyMetadata{
ID: tc.keyId,
keyMeta := &attestation.KeyMetadata{
ID: tc.keyID,
PEM: string(tc.pem),
Distrust: tc.distrust,
From: tc.from,

View File

@@ -5,7 +5,6 @@ import (
"fmt"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/types"
intoto "github.com/in-toto/in-toto-golang/in_toto"
v02 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.2"
ociv1 "github.com/opencontainers/image-spec/specs-go/v1"
@@ -16,8 +15,8 @@ const (
AttestationManifestType = "attestation-manifest"
InTotoPredicateType = "in-toto.io/predicate-type"
DockerReferenceDigest = "vnd.docker.reference.digest"
DockerDsseExtKind = "application/vnd.docker.attestation-verification.v1+json"
RekorTlExtKind = "Rekor"
DockerDSSEExtKind = "application/vnd.docker.attestation-verification.v1+json"
RekorTLExtKind = "Rekor"
OCIDescriptorDSSEMediaType = ociv1.MediaTypeDescriptor + "+dsse"
InTotoReferenceLifecycleStage = "vnd.docker.lifecycle-stage"
LifecycleStageExperimental = "experimental"
@@ -25,70 +24,63 @@ const (
var base64Encoding = base64.StdEncoding.Strict()
type AttestationLayer struct {
type Layer struct {
Statement *intoto.Statement
Layer v1.Layer
MediaType types.MediaType
Annotations map[string]string
}
type AttestationImage struct {
Layers []*AttestationLayer
Image v1.Image
}
type Manifest struct {
OriginalDescriptor *v1.Descriptor
OriginalLayers []*Layer
type SignedAttestationImage struct {
Image v1.Image
Descriptor *v1.Descriptor
AttestationManifest *AttestationManifest
}
type AttestationManifest struct {
Descriptor *v1.Descriptor
Attestation *AttestationImage
MediaType types.MediaType
Annotations map[string]string
Digest v1.Hash
// accumulated during signing
SignedLayers []*Layer
// details of subject image
SubjectName string
SubjectDescriptor *v1.Descriptor
}
// the following types are needed until https://github.com/secure-systems-lab/dsse/pull/61 is merged
type ManifestImageOptions struct {
// how to output the image
skipSubject bool
replaceLayers bool
laxReferrers bool
}
// the following types are needed until https://github.com/secure-systems-lab/dsse/pull/61 is merged.
type Envelope struct {
PayloadType string `json:"payloadType"`
Payload string `json:"payload"`
Signatures []Signature `json:"signatures"`
PayloadType string `json:"payloadType"`
Payload string `json:"payload"`
Signatures []*Signature `json:"signatures"`
}
type Signature struct {
KeyID string `json:"keyid"`
Sig string `json:"sig"`
Extension Extension `json:"extension"`
KeyID string `json:"keyid"`
Sig string `json:"sig"`
Extension *Extension `json:"extension,omitempty"`
}
type Extension struct {
Kind string `json:"kind"`
Ext DockerDsseExtension `json:"ext"`
Kind string `json:"kind"`
Ext *DockerDSSEExtension `json:"ext"`
}
type DockerDsseExtension struct {
Tl DockerTlExtension `json:"tl"`
type DockerDSSEExtension struct {
TL *DockerTLExtension `json:"tl"`
}
type DockerTlExtension struct {
type DockerTLExtension struct {
Kind string `json:"kind"`
Data any `json:"data"`
}
type VerifyOptions struct {
Keys []KeyMetadata `json:"keys"`
SkipTL bool `json:"skip_tl"`
Keys []*KeyMetadata `json:"keys"`
SkipTL bool `json:"skip_tl"`
}
type SigningOptions struct {
// replace unsigned statements with signed attestations
Replace bool
// don't log to the configured transparency log
SkipTL bool
// don't add OCI subject field to attestation image
SkipSubject bool
}
func DSSEMediaType(predicateType string) (string, error) {

View File

@@ -27,8 +27,10 @@ type KeyMetadata struct {
Distrust bool `json:"distrust,omitempty"`
}
type Keys []KeyMetadata
type KeysMap map[string]KeyMetadata
type (
Keys []*KeyMetadata
KeysMap map[string]*KeyMetadata
)
func VerifyDSSE(ctx context.Context, env *Envelope, opts *VerifyOptions) ([]byte, error) {
// enforce payload type
@@ -58,8 +60,8 @@ func VerifyDSSE(ctx context.Context, env *Envelope, opts *VerifyOptions) ([]byte
return payload, nil
}
func verifySignature(ctx context.Context, sig Signature, payload []byte, opts *VerifyOptions) error {
keys := make(map[string]KeyMetadata, len(opts.Keys))
func verifySignature(ctx context.Context, sig *Signature, payload []byte, opts *VerifyOptions) error {
keys := make(map[string]*KeyMetadata, len(opts.Keys))
for _, key := range opts.Keys {
keys[key.ID] = key
}
@@ -72,26 +74,25 @@ func verifySignature(ctx context.Context, sig Signature, payload []byte, opts *V
return fmt.Errorf("key %s is distrusted", keyMeta.ID)
}
// TODO: this is unmarshalling with MarshalPKIXPublicKey only for us to marshal it again
publicKey, err := signerverifier.Parse([]byte(keyMeta.PEM))
publicKey, err := signerverifier.ParsePublicKey([]byte(keyMeta.PEM))
if err != nil {
return fmt.Errorf("failed to parse public key: %w", err)
}
if !opts.SkipTL {
t := tlog.GetTL(ctx)
if sig.Extension.Kind == "" {
return fmt.Errorf("error missing signature extension kind")
if sig.Extension == nil || sig.Extension.Kind == "" {
return fmt.Errorf("error missing signature extension")
}
if sig.Extension.Kind != DockerDsseExtKind {
if sig.Extension.Kind != DockerDSSEExtKind {
return fmt.Errorf("error unsupported signature extension kind: %s", sig.Extension.Kind)
}
// verify TL entry
if sig.Extension.Ext.Tl.Kind != RekorTlExtKind {
return fmt.Errorf("error unsupported TL extension kind: %s", sig.Extension.Ext.Tl.Kind)
if sig.Extension.Ext.TL.Kind != RekorTLExtKind {
return fmt.Errorf("error unsupported TL extension kind: %s", sig.Extension.Ext.TL.Kind)
}
entry := sig.Extension.Ext.Tl.Data
entry := sig.Extension.Ext.TL.Data
entryBytes, err := json.Marshal(entry)
if err != nil {
return fmt.Errorf("failed to marshal TL entry: %w", err)

View File

@@ -35,7 +35,7 @@ func TestVerifyUnsignedAttestation(t *testing.T) {
payload := []byte("payload")
env := &attestation.Envelope{
// no signatures
Signatures: []attestation.Signature{},
Signatures: []*attestation.Signature{},
Payload: base64.StdEncoding.EncodeToString(payload),
PayloadType: intoto.PayloadType,
}

View File

@@ -14,7 +14,7 @@ const (
type VSAPredicate struct {
Verifier VSAVerifier `json:"verifier"`
TimeVerified string `json:"timeVerified"`
ResourceUri string `json:"resourceUri"`
ResourceURI string `json:"resourceUri"`
Policy VSAPolicy `json:"policy"`
InputAttestations []VSAInputAttestation `json:"inputAttestations"`
VerificationResult string `json:"verificationResult"`
@@ -35,7 +35,7 @@ type VSAInputAttestation struct {
}
func ToVSAResourceURI(sub intoto.Subject) (string, error) {
//parse purl
// parse purl
purl, err := packageurl.FromString(sub.Name)
if err != nil {
return "", fmt.Errorf("failed to parse package url: %w", err)

View File

@@ -7,7 +7,7 @@ import (
"regexp"
"github.com/docker/attest/pkg/tuf"
goyaml "gopkg.in/yaml.v3"
"sigs.k8s.io/yaml"
)
const (
@@ -24,14 +24,14 @@ func LoadLocalMappings(configDir string) (*PolicyMappings, error) {
if err != nil {
return nil, fmt.Errorf("failed to read local policy mapping file %s: %w", path, err)
}
err = goyaml.Unmarshal(mappingFile, mappings)
err = yaml.Unmarshal(mappingFile, mappings)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal policy mapping file %s: %w", path, err)
}
return expandMappingFile(mappings)
}
func LoadTufMappings(tufClient tuf.TUFClient, localTargetsDir string) (*PolicyMappings, error) {
func LoadTUFMappings(tufClient tuf.Downloader, localTargetsDir string) (*PolicyMappings, error) {
if tufClient == nil {
return nil, fmt.Errorf("tuf client not set")
}
@@ -42,7 +42,7 @@ func LoadTufMappings(tufClient tuf.TUFClient, localTargetsDir string) (*PolicyMa
}
mappings := &policyMappingsFile{}
err = goyaml.Unmarshal(fileContents, mappings)
err = yaml.Unmarshal(fileContents, mappings)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal policy mapping file %s: %w", filename, err)
}
@@ -52,7 +52,7 @@ func LoadTufMappings(tufClient tuf.TUFClient, localTargetsDir string) (*PolicyMa
func expandMappingFile(mappingFile *policyMappingsFile) (*PolicyMappings, error) {
policies := make(map[string]*PolicyMapping)
for _, policy := range mappingFile.Policies {
policies[policy.Id] = policy
policies[policy.ID] = policy
}
var rules []*PolicyRule
@@ -63,7 +63,7 @@ func expandMappingFile(mappingFile *policyMappingsFile) (*PolicyMappings, error)
}
rules = append(rules, &PolicyRule{
Pattern: r,
PolicyId: rule.PolicyId,
PolicyID: rule.PolicyID,
Replacement: rule.Replacement,
})
}

View File

@@ -1,18 +1,20 @@
package config
import "regexp"
import (
"regexp"
)
type policyMappingsFile struct {
Version string `yaml:"version"`
Kind string `yaml:"kind"`
Policies []*PolicyMapping `yaml:"policies"`
Rules []*policyRuleFile `yaml:"rules"`
Version string `json:"version"`
Kind string `json:"kind"`
Policies []*PolicyMapping `json:"policies"`
Rules []*policyRuleFile `json:"rules"`
}
type policyRuleFile struct {
Pattern string `yaml:"pattern"`
PolicyId string `yaml:"policy-id"`
Replacement string `yaml:"rewrite"`
Pattern string `json:"pattern"`
PolicyID string `json:"policy-id"`
Replacement string `json:"rewrite"`
}
type PolicyMappings struct {
@@ -30,23 +32,23 @@ const (
)
type PolicyMapping struct {
Id string `yaml:"id"`
Description string `yaml:"description"`
Files []PolicyMappingFile `yaml:"files"`
Attestations *AttestationConfig `yaml:"attestations"`
ID string `json:"id"`
Description string `json:"description"`
Files []PolicyMappingFile `json:"files"`
Attestations *AttestationConfig `json:"attestations"`
}
type AttestationConfig struct {
Style AttestationStyle `yaml:"style"`
Repo string `yaml:"repo"`
Style AttestationStyle `json:"style"`
Repo string `json:"repo"`
}
type PolicyMappingFile struct {
Path string `yaml:"path"`
Path string `json:"path"`
}
type PolicyRule struct {
Pattern *regexp.Regexp
PolicyId string
PolicyID string
Replacement string
}

View File

@@ -6,7 +6,6 @@ import (
"path/filepath"
"testing"
"github.com/docker/attest/pkg/mirror"
"github.com/docker/attest/pkg/oci"
"github.com/stretchr/testify/require"
)
@@ -25,7 +24,7 @@ func TestRegistryAuth(t *testing.T) {
}
for _, tc := range testCases {
t.Run(tc.Image, func(t *testing.T) {
err := mirror.PushIndexToRegistry(attIdx.Index, tc.Image)
err := oci.PushIndexToRegistry(attIdx.Index, tc.Image)
require.NoError(t, err)
_, err = oci.IndexFromRemote(tc.Image)
require.NoError(t, err)

View File

@@ -8,18 +8,19 @@ import (
"github.com/docker/attest/internal/embed"
"github.com/docker/attest/pkg/mirror"
"github.com/docker/attest/pkg/oci"
"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
delegatedMetadata []*mirror.Image
targets []*mirror.Image
delegatedTargets []*mirror.Index
}
func ExampleNewTufMirror() {
func ExampleNewTUFMirror() {
home, err := os.UserHomeDir()
if err != nil {
panic(err)
@@ -29,7 +30,7 @@ func ExampleNewTufMirror() {
// configure TUF mirror
metadataURI := "https://docker.github.io/tuf-staging/metadata"
targetsURI := "https://docker.github.io/tuf-staging/targets"
m, err := mirror.NewTufMirror(embed.RootStaging.Data, tufOutputPath, metadataURI, targetsURI, tuf.NewMockVersionChecker())
m, err := mirror.NewTUFMirror(embed.RootStaging.Data, tufOutputPath, metadataURI, targetsURI, tuf.NewMockVersionChecker())
if err != nil {
panic(err)
}
@@ -46,7 +47,7 @@ func ExampleNewTufMirror() {
}
// create targets manifest
targets, err := m.GetTufTargetMirrors()
targets, err := m.GetTUFTargetMirrors()
if err != nil {
panic(err)
}
@@ -80,7 +81,7 @@ func ExampleNewTufMirror() {
func mirrorToRegistry(o *TufMirrorOutput) error {
// push metadata to registry
metadataRepo := "registry-1.docker.io/docker/tuf-metadata:latest"
err := mirror.PushImageToRegistry(o.metadata, metadataRepo)
err := oci.PushImageToRegistry(o.metadata, metadataRepo)
if err != nil {
return err
}
@@ -91,7 +92,7 @@ func mirrorToRegistry(o *TufMirrorOutput) error {
return fmt.Errorf("failed to get repo without tag: %s", metadataRepo)
}
imageName := fmt.Sprintf("%s:%s", repo, metadata.Tag)
err = mirror.PushImageToRegistry(metadata.Image, imageName)
err = oci.PushImageToRegistry(metadata.Image, imageName)
if err != nil {
return err
}
@@ -101,7 +102,7 @@ func mirrorToRegistry(o *TufMirrorOutput) error {
targetsRepo := "registry-1.docker.io/docker/tuf-targets"
for _, target := range o.targets {
imageName := fmt.Sprintf("%s:%s", targetsRepo, target.Tag)
err = mirror.PushImageToRegistry(target.Image, imageName)
err = oci.PushImageToRegistry(target.Image, imageName)
if err != nil {
return err
}
@@ -109,7 +110,7 @@ func mirrorToRegistry(o *TufMirrorOutput) error {
// push delegated targets to registry
for _, target := range o.delegatedTargets {
imageName := fmt.Sprintf("%s:%s", targetsRepo, target.Tag)
err = mirror.PushIndexToRegistry(target.Index, imageName)
err = oci.PushIndexToRegistry(target.Index, imageName)
if err != nil {
return err
}
@@ -119,14 +120,14 @@ func mirrorToRegistry(o *TufMirrorOutput) error {
func mirrorToLocal(o *TufMirrorOutput, outputPath string) error {
// output metadata to local directory
err := mirror.SaveImageAsOCILayout(o.metadata, outputPath)
err := oci.SaveImageAsOCILayout(o.metadata, outputPath)
if err != nil {
return err
}
// output delegated metadata to local directory
for _, metadata := range o.delegatedMetadata {
path := filepath.Join(outputPath, metadata.Tag)
err = mirror.SaveImageAsOCILayout(metadata.Image, path)
err = oci.SaveImageAsOCILayout(metadata.Image, path)
if err != nil {
return err
}
@@ -135,7 +136,7 @@ func mirrorToLocal(o *TufMirrorOutput, outputPath string) error {
// output top-level targets to local directory
for _, target := range o.targets {
path := filepath.Join(outputPath, target.Tag)
err = mirror.SaveImageAsOCILayout(target.Image, path)
err = oci.SaveImageAsOCILayout(target.Image, path)
if err != nil {
return err
}
@@ -143,7 +144,7 @@ func mirrorToLocal(o *TufMirrorOutput, outputPath string) error {
// output delegated targets to local directory
for _, target := range o.delegatedTargets {
path := filepath.Join(outputPath, target.Tag)
err = mirror.SaveIndexAsOCILayout(target.Index, path)
err = oci.SaveIndexAsOCILayout(target.Index, path)
if err != nil {
return err
}

View File

@@ -16,9 +16,9 @@ import (
// TUF root metadata
// -----------------
// GetMetadataManifest returns an image with TUF root metadata as layers
func (m *TufMirror) GetMetadataManifest(metadataURL string) (v1.Image, error) {
metadata, err := m.getTufMetadataMirror(metadataURL)
// GetMetadataManifest returns an image with TUF root metadata as layers.
func (m *TUFMirror) GetMetadataManifest(metadataURL string) (v1.Image, error) {
metadata, err := m.getMetadataMirror(metadataURL)
if err != nil {
return nil, fmt.Errorf("failed to get metadata: %w", err)
}
@@ -29,16 +29,16 @@ func (m *TufMirror) GetMetadataManifest(metadataURL string) (v1.Image, error) {
return manifest, nil
}
// getTufMetadataMirror returns a TufMetadata struct with TUF metadata as map of file names to bytes
func (m *TufMirror) getTufMetadataMirror(metadataURL string) (*TufMetadata, error) {
trustedMetadata := m.TufClient.GetMetadata()
// getMetadataMirror returns a TufMetadata struct with TUF metadata as map of file names to bytes.
func (m *TUFMirror) getMetadataMirror(metadataURL string) (*TUFMetadata, error) {
trustedMetadata := m.TUFClient.GetMetadata()
rootMetadata := map[string][]byte{}
rootVersion := trustedMetadata.Root.Signed.Version
// get the previous versions of root metadata if any
if rootVersion != 1 {
var err error
rootMetadata, err = m.TufClient.GetPriorRoots(metadataURL)
rootMetadata, err = m.TUFClient.GetPriorRoots(metadataURL)
if err != nil {
return nil, fmt.Errorf("failed to get prior root metadata: %w", err)
}
@@ -69,7 +69,7 @@ func (m *TufMirror) getTufMetadataMirror(metadataURL string) (*TufMetadata, erro
snapshotVersion = strconv.FormatInt(trustedMetadata.Snapshot.Signed.Version, 10)
targetsVersion = strconv.FormatInt(trustedMetadata.Targets[metadata.TARGETS].Signed.Version, 10)
}
return &TufMetadata{
return &TUFMetadata{
Root: rootMetadata,
Snapshot: map[string][]byte{nameFromRole(metadata.SNAPSHOT, snapshotVersion): snapshotBytes},
Targets: map[string][]byte{nameFromRole(metadata.TARGETS, targetsVersion): targetsBytes},
@@ -77,12 +77,12 @@ func (m *TufMirror) getTufMetadataMirror(metadataURL string) (*TufMetadata, erro
}, nil
}
// buildMetadataManifest returns an OCI image with TUF metadata as layers with annotations
func (m *TufMirror) buildMetadataManifest(metadata *TufMetadata) (v1.Image, error) {
// buildMetadataManifest returns an OCI image with TUF metadata as layers with annotations.
func (m *TUFMirror) buildMetadataManifest(metadata *TUFMetadata) (v1.Image, error) {
img := empty.Image
img = mutate.MediaType(img, types.OCIManifestSchema1)
img = mutate.ConfigMediaType(img, types.OCIConfigJSON)
for _, role := range TufRoles {
for _, role := range TUFRoles {
layers, err := m.makeRoleLayers(role, metadata)
if err != nil {
return nil, fmt.Errorf("failed to make role layer: %w", err)
@@ -95,8 +95,8 @@ func (m *TufMirror) buildMetadataManifest(metadata *TufMetadata) (v1.Image, erro
return img, nil
}
// makeRoleLayers returns a list of layers for a given TUF role
func (m *TufMirror) makeRoleLayers(role TufRole, tufMetadata *TufMetadata) ([]mutate.Addendum, error) {
// makeRoleLayers returns a list of layers for a given TUF role.
func (m *TUFMirror) makeRoleLayers(role TUFRole, tufMetadata *TUFMetadata) ([]mutate.Addendum, error) {
var layers []mutate.Addendum
ann := map[string]string{tufFileAnnotation: ""}
switch role {
@@ -115,8 +115,8 @@ func (m *TufMirror) makeRoleLayers(role TufRole, tufMetadata *TufMetadata) ([]mu
return layers, nil
}
// annotatedMetaLayers returns a list of layers with annotations for each TUF metadata file
func (m *TufMirror) annotatedMetaLayers(meta map[string][]byte) []mutate.Addendum {
// annotatedMetaLayers returns a list of layers with annotations for each TUF metadata file.
func (m *TUFMirror) annotatedMetaLayers(meta map[string][]byte) []mutate.Addendum {
var layers []mutate.Addendum
for name, data := range meta {
ann := map[string]string{tufFileAnnotation: name}
@@ -129,8 +129,8 @@ func (m *TufMirror) annotatedMetaLayers(meta map[string][]byte) []mutate.Addendu
// TUF delegated targets metadata
// ------------------------------
// GetDelegatedMetadataMirrors returns a list of mirrors (image/tag pairs) for each delegated targets role metadata
func (m *TufMirror) GetDelegatedMetadataMirrors() ([]*MirrorImage, error) {
// GetDelegatedMetadataMirrors returns a list of mirrors (image/tag pairs) for each delegated targets role metadata.
func (m *TUFMirror) GetDelegatedMetadataMirrors() ([]*Image, error) {
// get current delegated targets metadata
delegatedTargets, err := m.getDelegatedTargetsMetadata()
if err != nil {
@@ -143,12 +143,12 @@ func (m *TufMirror) GetDelegatedMetadataMirrors() ([]*MirrorImage, error) {
return mirror, nil
}
// getDelegatedTargetsMetadata returns delegated targets metadata as a list of DelegatedTargetMetadata (role name and data)
func (m *TufMirror) getDelegatedTargetsMetadata() ([]DelegatedTargetMetadata, error) {
// getDelegatedTargetsMetadata returns delegated targets metadata as a list of DelegatedTargetMetadata (role name and data).
func (m *TUFMirror) getDelegatedTargetsMetadata() ([]DelegatedTargetMetadata, error) {
var delegatedTargets []DelegatedTargetMetadata
md := m.TufClient.GetMetadata()
md := m.TUFClient.GetMetadata()
for _, role := range md.Targets[metadata.TARGETS].Signed.Delegations.Roles {
roleMetadata, err := m.TufClient.LoadDelegatedTargets(role.Name, metadata.TARGETS)
roleMetadata, err := m.TUFClient.LoadDelegatedTargets(role.Name, metadata.TARGETS)
if err != nil {
return nil, fmt.Errorf("failed to get delegated role metadata: %w", err)
}
@@ -170,9 +170,9 @@ func (m *TufMirror) getDelegatedTargetsMetadata() ([]DelegatedTargetMetadata, er
return delegatedTargets, nil
}
// buildDelegatedMetadataManifests returns a list of mirrors (image/tag pairs) for each delegated target role metadata
func (m *TufMirror) buildDelegatedMetadataManifests(delegated []DelegatedTargetMetadata) ([]*MirrorImage, error) {
manifests := []*MirrorImage{}
// buildDelegatedMetadataManifests returns a list of mirrors (image/tag pairs) for each delegated target role metadata.
func (m *TUFMirror) buildDelegatedMetadataManifests(delegated []DelegatedTargetMetadata) ([]*Image, error) {
manifests := []*Image{}
for _, role := range delegated {
img := empty.Image
img = mutate.MediaType(img, types.OCIManifestSchema1)
@@ -183,7 +183,7 @@ func (m *TufMirror) buildDelegatedMetadataManifests(delegated []DelegatedTargetM
if err != nil {
return nil, fmt.Errorf("failed to append delegated targets layer to image: %w", err)
}
manifests = append(manifests, &MirrorImage{Image: img, Tag: role.Name})
manifests = append(manifests, &Image{Image: img, Tag: role.Name})
}
return manifests, nil
}

View File

@@ -21,10 +21,10 @@ func TestGetTufMetadataMirror(t *testing.T) {
defer server.Close()
path := test.CreateTempDir(t, "", "tuf_temp")
m, err := NewTufMirror(embed.RootDev.Data, path, server.URL+"/metadata", server.URL+"/targets", tuf.NewMockVersionChecker())
m, err := NewTUFMirror(embed.RootDev.Data, path, server.URL+"/metadata", server.URL+"/targets", tuf.NewMockVersionChecker())
assert.NoError(t, err)
tufMetadata, err := m.getTufMetadataMirror(server.URL + "/metadata")
tufMetadata, err := m.getMetadataMirror(server.URL + "/metadata")
assert.NoError(t, err)
// check that all roles are not empty
@@ -39,7 +39,7 @@ func TestGetMetadataManifest(t *testing.T) {
defer server.Close()
path := test.CreateTempDir(t, "", "tuf_temp")
m, err := NewTufMirror(embed.RootDev.Data, path, server.URL+"/metadata", server.URL+"/targets", tuf.NewMockVersionChecker())
m, err := NewTUFMirror(embed.RootDev.Data, path, server.URL+"/metadata", server.URL+"/targets", tuf.NewMockVersionChecker())
assert.NoError(t, err)
img, err := m.GetMetadataManifest(server.URL + "/metadata")
@@ -78,7 +78,7 @@ func TestGetDelegatedMetadataMirrors(t *testing.T) {
defer server.Close()
path := test.CreateTempDir(t, "", "tuf_temp")
m, err := NewTufMirror(embed.RootDev.Data, path, server.URL+"/metadata", server.URL+"/targets", tuf.NewMockVersionChecker())
m, err := NewTUFMirror(embed.RootDev.Data, path, server.URL+"/metadata", server.URL+"/targets", tuf.NewMockVersionChecker())
assert.NoError(t, err)
delegations, err := m.GetDelegatedMetadataMirrors()

View File

@@ -2,74 +2,18 @@ package mirror
import (
"fmt"
"os"
"github.com/docker/attest/internal/embed"
"github.com/docker/attest/pkg/oci"
"github.com/docker/attest/pkg/tuf"
"github.com/google/go-containerregistry/pkg/name"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/empty"
"github.com/google/go-containerregistry/pkg/v1/layout"
"github.com/google/go-containerregistry/pkg/v1/remote"
)
func NewTufMirror(root []byte, tufPath, metadataURL, targetsURL string, versionChecker tuf.VersionChecker) (*TufMirror, error) {
func NewTUFMirror(root []byte, tufPath, metadataURL, targetsURL string, versionChecker tuf.VersionChecker) (*TUFMirror, error) {
if root == nil {
root = embed.RootDefault.Data
}
tufClient, err := tuf.NewTufClient(root, tufPath, metadataURL, targetsURL, versionChecker)
tufClient, err := tuf.NewClient(root, tufPath, metadataURL, targetsURL, versionChecker)
if err != nil {
return nil, fmt.Errorf("failed to create TUF client: %w", err)
}
return &TufMirror{TufClient: tufClient, tufPath: tufPath, metadataURL: metadataURL, targetsURL: targetsURL}, nil
}
func PushImageToRegistry(image v1.Image, imageName string) error {
ref, err := name.ParseReference(imageName)
if err != nil {
return fmt.Errorf("Failed to parse image name '%s': %w", imageName, err)
}
// Push the image to the registry
return remote.Write(ref, image, oci.MultiKeychainOption())
}
func PushIndexToRegistry(image v1.ImageIndex, imageName string) error {
// Parse the index name
ref, err := name.ParseReference(imageName)
if err != nil {
return fmt.Errorf("Failed to parse image name: %w", err)
}
// Push the index to the registry
return remote.WriteIndex(ref, image, oci.MultiKeychainOption())
}
func SaveImageAsOCILayout(image v1.Image, path string) error {
// Save the image to the local filesystem
err := os.MkdirAll(path, os.ModePerm)
if err != nil {
return fmt.Errorf("failed to create directory: %w", err)
}
index := empty.Index
l, err := layout.Write(path, index)
if err != nil {
return fmt.Errorf("failed to create index: %w", err)
}
return l.AppendImage(image)
}
func SaveIndexAsOCILayout(image v1.ImageIndex, path string) error {
// Save the index to the local filesystem
err := os.MkdirAll(path, os.ModePerm)
if err != nil {
return fmt.Errorf("failed to create directory: %w", err)
}
_, err = layout.Write(path, image)
if err != nil {
return fmt.Errorf("failed to create index: %w", err)
}
return nil
return &TUFMirror{TUFClient: tufClient, tufPath: tufPath, metadataURL: metadataURL, targetsURL: targetsURL}, nil
}

View File

@@ -13,16 +13,16 @@ import (
"github.com/theupdateframework/go-tuf/v2/metadata"
)
// GetTufTargetMirrors returns a list of top-level target files as MirrorImages (image with tag)
func (m *TufMirror) GetTufTargetMirrors() ([]*MirrorImage, error) {
targetMirrors := []*MirrorImage{}
md := m.TufClient.GetMetadata()
// GetTUFTargetMirrors returns a list of top-level target files as MirrorImages (image with tag).
func (m *TUFMirror) GetTUFTargetMirrors() ([]*Image, error) {
targetMirrors := []*Image{}
md := m.TUFClient.GetMetadata()
// for each top-level target file, create an image with the target file as a layer
targets := md.Targets[metadata.TARGETS].Signed.Targets
for _, t := range targets {
// download target file
_, data, err := m.TufClient.DownloadTarget(t.Path, filepath.Join(m.tufPath, "download"))
_, data, err := m.TUFClient.DownloadTarget(t.Path, filepath.Join(m.tufPath, "download"))
if err != nil {
return nil, fmt.Errorf("failed to download target %s: %w", t.Path, err)
}
@@ -42,16 +42,16 @@ func (m *TufMirror) GetTufTargetMirrors() ([]*MirrorImage, error) {
if err != nil {
return nil, fmt.Errorf("failed to append role layer to image: %w", err)
}
targetMirrors = append(targetMirrors, &MirrorImage{Image: img, Tag: name})
targetMirrors = append(targetMirrors, &Image{Image: img, Tag: name})
}
return targetMirrors, nil
}
// GetDelegatedTargetMirrors returns a list of delegated target files as MirrorIndexes (image index with tag)
// each image in the index contains a delegated target file
func (m *TufMirror) GetDelegatedTargetMirrors() ([]*MirrorIndex, error) {
mirror := []*MirrorIndex{}
md := m.TufClient.GetMetadata()
// each image in the index contains a delegated target file.
func (m *TUFMirror) GetDelegatedTargetMirrors() ([]*Index, error) {
mirror := []*Index{}
md := m.TUFClient.GetMetadata()
// for each delegated role, create an image index with target files as images
roles := md.Targets[metadata.TARGETS].Signed.Delegations.Roles
@@ -60,7 +60,7 @@ func (m *TufMirror) GetDelegatedTargetMirrors() ([]*MirrorIndex, error) {
index := v1.ImageIndex(empty.Index)
// get delegated targets metadata for role
roleMeta, err := m.TufClient.LoadDelegatedTargets(role.Name, metadata.TARGETS)
roleMeta, err := m.TUFClient.LoadDelegatedTargets(role.Name, metadata.TARGETS)
if err != nil {
return nil, fmt.Errorf("failed to load delegated targets metadata: %w", err)
}
@@ -68,7 +68,7 @@ func (m *TufMirror) GetDelegatedTargetMirrors() ([]*MirrorIndex, error) {
// for each target file, create an image with the target file as a layer
for _, target := range roleMeta.Signed.Targets {
// download target file
_, data, err := m.TufClient.DownloadTarget(target.Path, filepath.Join(m.tufPath, "download"))
_, data, err := m.TUFClient.DownloadTarget(target.Path, filepath.Join(m.tufPath, "download"))
if err != nil {
return nil, fmt.Errorf("failed to download target %s: %w", target.Path, err)
}
@@ -103,7 +103,7 @@ func (m *TufMirror) GetDelegatedTargetMirrors() ([]*MirrorIndex, error) {
},
})
}
mirror = append(mirror, &MirrorIndex{Index: index, Tag: role.Name})
mirror = append(mirror, &Index{Index: index, Tag: role.Name})
}
return mirror, nil
}

View File

@@ -27,10 +27,10 @@ func TestGetTufTargetsMirror(t *testing.T) {
defer server.Close()
path := test.CreateTempDir(t, "", "tuf_temp")
m, err := NewTufMirror(embed.RootDev.Data, path, server.URL+"/metadata", server.URL+"/targets", tuf.NewMockVersionChecker())
m, err := NewTUFMirror(embed.RootDev.Data, path, server.URL+"/metadata", server.URL+"/targets", tuf.NewMockVersionChecker())
assert.NoError(t, err)
targets, err := m.GetTufTargetMirrors()
targets, err := m.GetTUFTargetMirrors()
assert.NoError(t, err)
assert.Greater(t, len(targets), 0)
@@ -50,8 +50,8 @@ func TestGetTufTargetsMirror(t *testing.T) {
ann, ok := layer.Annotations[tufFileAnnotation]
assert.True(t, ok)
parts := strings.Split(ann, ".")
// <digest>.filename.json
assert.Equal(t, len(parts), 3)
// <digest>.filename.<ext|optional>
assert.GreaterOrEqual(t, len(parts), 2)
}
}
}
@@ -61,10 +61,10 @@ func TestTargetDelegationMetadata(t *testing.T) {
defer server.Close()
path := test.CreateTempDir(t, "", "tuf_temp")
tm, err := NewTufMirror(embed.RootDev.Data, path, server.URL+"/metadata", server.URL+"/targets", tuf.NewMockVersionChecker())
tm, err := NewTUFMirror(embed.RootDev.Data, path, server.URL+"/metadata", server.URL+"/targets", tuf.NewMockVersionChecker())
assert.NoError(t, err)
targets, err := tm.TufClient.LoadDelegatedTargets("test-role", "targets")
targets, err := tm.TUFClient.LoadDelegatedTargets("test-role", "targets")
assert.NoError(t, err)
assert.Greater(t, len(targets.Signed.Targets), 0)
}
@@ -74,7 +74,7 @@ func TestGetDelegatedTargetMirrors(t *testing.T) {
defer server.Close()
path := test.CreateTempDir(t, "", "tuf_temp")
m, err := NewTufMirror(embed.RootDev.Data, path, server.URL+"/metadata", server.URL+"/targets", tuf.NewMockVersionChecker())
m, err := NewTUFMirror(embed.RootDev.Data, path, server.URL+"/metadata", server.URL+"/targets", tuf.NewMockVersionChecker())
assert.NoError(t, err)
mirrors, err := m.GetDelegatedTargetMirrors()
@@ -97,8 +97,8 @@ func TestGetDelegatedTargetMirrors(t *testing.T) {
ann, ok := layer.Annotations[tufFileAnnotation]
assert.True(t, ok)
parts := strings.Split(ann, ".")
// <subdir>/<digest>.filename.json
assert.Equal(t, len(parts), 3)
// <subdir>/<digest>.filename.<ext|optional>
assert.GreaterOrEqual(t, len(parts), 2)
}
}
}

View File

@@ -14,11 +14,11 @@ const (
tufFileAnnotation = "tuf.io/filename"
)
type TufRole string
type TUFRole string
var TufRoles = []TufRole{metadata.ROOT, metadata.SNAPSHOT, metadata.TARGETS, metadata.TIMESTAMP}
var TUFRoles = []TUFRole{metadata.ROOT, metadata.SNAPSHOT, metadata.TARGETS, metadata.TIMESTAMP}
type TufMetadata struct {
type TUFMetadata struct {
Root map[string][]byte
Snapshot map[string][]byte
Targets map[string][]byte
@@ -31,18 +31,18 @@ type DelegatedTargetMetadata struct {
Data []byte
}
type MirrorImage struct {
type Image struct {
Image v1.Image
Tag string
}
type MirrorIndex struct {
type Index struct {
Index v1.ImageIndex
Tag string
}
type TufMirror struct {
TufClient *tuf.TufClient
type TUFMirror struct {
TUFClient *tuf.Client
tufPath string
metadataURL string
targetsURL string

View File

@@ -7,21 +7,21 @@ import (
)
type userAgentTransporter struct {
ua string
rt http.RoundTripper
userAgent string
roundTripper http.RoundTripper
}
type Option = func(*http.Client)
func (u *userAgentTransporter) RoundTrip(req *http.Request) (*http.Response, error) {
req.Header.Set("User-Agent", u.ua)
req.Header.Set("User-Agent", u.userAgent)
return u.rt.RoundTrip(req)
return u.roundTripper.RoundTrip(req)
}
func HttpTransport() http.RoundTripper {
func HTTPTransport() http.RoundTripper {
return &userAgentTransporter{
ua: "Docker-Client",
rt: cleanhttp.DefaultTransport(),
userAgent: "Docker-Client",
roundTripper: cleanhttp.DefaultTransport(),
}
}

View File

@@ -4,23 +4,21 @@ import (
"context"
"encoding/json"
"fmt"
"strings"
"github.com/docker/attest/pkg/attestation"
att "github.com/docker/attest/pkg/attestation"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/layout"
"github.com/pkg/errors"
)
// implementation of AttestationResolver that closes over attestations from an oci layout
type OCILayoutResolver struct {
*AttestationManifest
// implementation of AttestationResolver that closes over attestations from an oci layout.
type LayoutResolver struct {
*attestation.Manifest
*ImageSpec
}
func NewOCILayoutAttestationResolver(src *ImageSpec) (*OCILayoutResolver, error) {
r := &OCILayoutResolver{
func NewOCILayoutAttestationResolver(src *ImageSpec) (*LayoutResolver, error) {
r := &LayoutResolver{
ImageSpec: src,
}
_, err := r.fetchAttestationManifest()
@@ -30,43 +28,36 @@ func NewOCILayoutAttestationResolver(src *ImageSpec) (*OCILayoutResolver, error)
return r, nil
}
func (r *OCILayoutResolver) fetchAttestationManifest() (*AttestationManifest, error) {
if r.AttestationManifest == nil {
func (r *LayoutResolver) fetchAttestationManifest() (*attestation.Manifest, error) {
if r.Manifest == nil {
m, err := attestationManifestFromOCILayout(r.Identifier, r.ImageSpec.Platform)
if err != nil {
return nil, err
}
r.AttestationManifest = m
r.Manifest = m
}
return r.AttestationManifest, nil
return r.Manifest, nil
}
func (r *OCILayoutResolver) Attestations(ctx context.Context, predicateType string) ([]*att.Envelope, error) {
attestationImage := r.AttestationManifest.Image
layers, err := attestationImage.Layers()
if err != nil {
return nil, fmt.Errorf("failed to extract layers from attestation image: %w", err)
}
func (r *LayoutResolver) Attestations(_ context.Context, predicateType string) ([]*att.Envelope, error) {
var envs []*att.Envelope
manifest := r.AttestationManifest.Manifest
for i, l := range manifest.Layers {
if l.Annotations[attestation.InTotoPredicateType] != predicateType {
continue
}
layer := layers[i]
mt, err := layer.MediaType()
dsseMediaType, err := attestation.DSSEMediaType(predicateType)
if err != nil {
return nil, fmt.Errorf("failed to get DSSE media type for predicate '%s': %w", predicateType, err)
}
for _, attestationLayer := range r.Manifest.OriginalLayers {
mt, err := attestationLayer.Layer.MediaType()
if err != nil {
return nil, fmt.Errorf("failed to get layer media type: %w", err)
}
mts := string(mt)
if !strings.HasSuffix(mts, "+dsse") {
if mts != dsseMediaType {
continue
}
var env = new(att.Envelope)
env := new(att.Envelope)
// parse layer blob as json
r, err := layer.Uncompressed()
r, err := attestationLayer.Layer.Uncompressed()
if err != nil {
return nil, fmt.Errorf("failed to get layer contents: %w", err)
}
@@ -80,19 +71,19 @@ func (r *OCILayoutResolver) Attestations(ctx context.Context, predicateType stri
return envs, nil
}
func (r *OCILayoutResolver) ImageName(ctx context.Context) (string, error) {
return r.Name, nil
func (r *LayoutResolver) ImageName(_ context.Context) (string, error) {
return r.SubjectName, nil
}
func (r *OCILayoutResolver) ImageDigest(ctx context.Context) (string, error) {
return r.Digest, nil
func (r *LayoutResolver) ImageDescriptor(_ context.Context) (*v1.Descriptor, error) {
return r.SubjectDescriptor, nil
}
func (r *OCILayoutResolver) ImagePlatform(ctx context.Context) (*v1.Platform, error) {
func (r *LayoutResolver) ImagePlatform(_ context.Context) (*v1.Platform, error) {
return r.ImageSpec.Platform, nil
}
func attestationManifestFromOCILayout(path string, platform *v1.Platform) (*AttestationManifest, error) {
func attestationManifestFromOCILayout(path string, platform *v1.Platform) (*attestation.Manifest, error) {
idx, err := layout.ImageIndexFromPath(path)
if err != nil {
return nil, err
@@ -104,7 +95,6 @@ func attestationManifestFromOCILayout(path string, platform *v1.Platform) (*Atte
}
idxDescriptor := idxm.Manifests[0]
name := idxDescriptor.Annotations["org.opencontainers.image.ref.name"]
idxDigest := idxDescriptor.Digest
mfs, err := idx.ImageIndex(idxDigest)
@@ -115,18 +105,26 @@ func attestationManifestFromOCILayout(path string, platform *v1.Platform) (*Atte
if err != nil {
return nil, fmt.Errorf("failed to extract IndexManifest from ImageIndex: %w", err)
}
var imageDigest string
for _, mf := range mfs2.Manifests {
if mf.Platform.Equals(*platform) {
imageDigest = mf.Digest.String()
var subjectDescriptor *v1.Descriptor
for i := range mfs2.Manifests {
manifest := &mfs2.Manifests[i]
if manifest.Platform != nil {
if manifest.Platform.Equals(*platform) {
subjectDescriptor = manifest
break
}
}
}
for _, mf := range mfs2.Manifests {
if subjectDescriptor == nil {
return nil, fmt.Errorf("platform not found in index")
}
for i := range mfs2.Manifests {
mf := &mfs2.Manifests[i]
if mf.Annotations[att.DockerReferenceType] != attestation.AttestationManifestType {
continue
}
if mf.Annotations[att.DockerReferenceDigest] != imageDigest {
if mf.Annotations[att.DockerReferenceDigest] != subjectDescriptor.Digest.String() {
continue
}
@@ -134,19 +132,17 @@ func attestationManifestFromOCILayout(path string, platform *v1.Platform) (*Atte
if err != nil {
return nil, fmt.Errorf("failed to extract attestation image with digest %s: %w", mf.Digest.String(), err)
}
manifest, err := attestationImage.Manifest()
layers, err := attestation.GetAttestationsFromImage(attestationImage)
if err != nil {
return nil, fmt.Errorf("failed to get manifest: %w", err)
return nil, fmt.Errorf("failed to get attestations from image: %w", err)
}
attest := &AttestationManifest{
Name: name,
Image: attestationImage,
Manifest: manifest,
Descriptor: &mf,
Digest: imageDigest,
Platform: platform,
attest := &attestation.Manifest{
OriginalLayers: layers,
OriginalDescriptor: mf,
SubjectName: idxDescriptor.Annotations["org.opencontainers.image.ref.name"],
SubjectDescriptor: subjectDescriptor,
}
return attest, nil
}
return nil, errors.New("attestation manifest not found")
return nil, fmt.Errorf("attestation manifest not found")
}

68
pkg/oci/layout_test.go Normal file
View File

@@ -0,0 +1,68 @@
package oci_test
import (
"strings"
"testing"
"github.com/docker/attest/internal/test"
"github.com/docker/attest/pkg/attest"
"github.com/docker/attest/pkg/attestation"
"github.com/docker/attest/pkg/oci"
"github.com/docker/attest/pkg/policy"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestAttestationFromOCILayout(t *testing.T) {
ctx, signer := test.Setup(t)
outputLayout := test.CreateTempDir(t, "", "attest-oci-layout")
invalidPlatform := &v1.Platform{
Architecture: "invalid",
OS: "invalid",
}
opts := &attestation.SigningOptions{}
attIdx, err := oci.IndexFromPath(oci.UnsignedTestImage)
require.NoError(t, err)
signedManifests, err := attest.SignStatements(ctx, attIdx.Index, signer, opts)
require.NoError(t, err)
signedIndex := attIdx.Index
signedIndex, err = attestation.UpdateIndexImages(signedIndex, signedManifests)
require.NoError(t, err)
spec, err := oci.ParseImageSpec(oci.LocalPrefix + outputLayout)
require.NoError(t, err)
err = oci.SaveIndex([]*oci.ImageSpec{spec}, signedIndex, outputLayout)
require.NoError(t, err)
testCases := []struct {
name string
platform *v1.Platform
errorStr string
}{
{name: "nominal", platform: spec.Platform},
{name: "invalid platform", platform: invalidPlatform, errorStr: "platform not found in index"},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
spec := &oci.ImageSpec{
Type: oci.OCI,
Identifier: outputLayout,
Platform: tc.platform,
}
resolver, err := policy.CreateImageDetailsResolver(spec)
if tc.errorStr != "" {
require.Error(t, err)
assert.Contains(t, err.Error(), tc.errorStr)
return
}
require.NoError(t, err)
desc, err := resolver.ImageDescriptor(ctx)
require.NoError(t, err)
digest := desc.Digest.String()
assert.True(t, strings.Contains(digest, "sha256:"))
})
}
}

50
pkg/oci/mock.go Normal file
View File

@@ -0,0 +1,50 @@
package oci
import (
"context"
"github.com/docker/attest/pkg/attestation"
v1 "github.com/google/go-containerregistry/pkg/v1"
)
type MockResolver struct {
Envs []*attestation.Envelope
}
func (r MockResolver) Attestations(_ context.Context, _ string) ([]*attestation.Envelope, error) {
return r.Envs, nil
}
func (r MockResolver) ImageName(_ context.Context) (string, error) {
return "library/alpine:latest", nil
}
func (r MockResolver) ImageDescriptor(_ context.Context) (*v1.Descriptor, error) {
digest, err := v1.NewHash("sha256:da8b190665956ea07890a0273e2a9c96bfe291662f08e2860e868eef69c34620")
if err != nil {
return nil, err
}
return &v1.Descriptor{
Digest: digest,
Size: 1234,
MediaType: "application/vnd.oci.image.manifest.v1+json",
}, nil
}
func (r MockResolver) ImagePlatform(_ context.Context) (*v1.Platform, error) {
return ParsePlatform("linux/amd64")
}
type MockRegistryResolver struct {
Subject *v1.Descriptor
ImageNameStr string
*MockResolver
}
func (r *MockRegistryResolver) ImageDescriptor(_ context.Context) (*v1.Descriptor, error) {
return r.Subject, nil
}
func (r *MockRegistryResolver) ImageName(_ context.Context) (string, error) {
return r.ImageNameStr, nil
}

View File

@@ -6,19 +6,19 @@ import (
"fmt"
"strings"
"github.com/containerd/containerd/platforms"
"github.com/containerd/platforms"
"github.com/distribution/reference"
"github.com/docker/attest/pkg/attestation"
att "github.com/docker/attest/pkg/attestation"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/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"
)
// ParsePlatform parses the provided platform string or attempts to obtain
// the platform of the current host system
// the platform of the current host system.
func ParsePlatform(platformStr string) (*v1.Platform, error) {
if platformStr == "" {
cdp := platforms.Normalize(platforms.DefaultSpec())
@@ -30,14 +30,13 @@ func ParsePlatform(platformStr string) (*v1.Platform, error) {
Architecture: cdp.Architecture,
Variant: cdp.Variant,
}, nil
} else {
return v1.ParsePlatform(platformStr)
}
return v1.ParsePlatform(platformStr)
}
func WithOptions(ctx context.Context, platform *v1.Platform) []remote.Option {
// prepare options
options := []remote.Option{MultiKeychainOption(), remote.WithTransport(HttpTransport()), remote.WithContext(ctx)}
options := []remote.Option{MultiKeychainOption(), remote.WithTransport(HTTPTransport()), remote.WithContext(ctx)}
// add in platform into remote Get operation; this might conflict with an explicit digest, but we are trying anyway
if platform != nil {
@@ -46,24 +45,24 @@ func WithOptions(ctx context.Context, platform *v1.Platform) []remote.Option {
return options
}
func ExtractEnvelopes(ia *AttestationManifest, predicateType string) ([]*att.Envelope, error) {
manifest := ia.Manifest
image := ia.Image
func ExtractEnvelopes(manifest *attestation.Manifest, predicateType string) ([]*att.Envelope, error) {
var envs []*att.Envelope
layers, err := image.Layers()
dsseMediaType, err := attestation.DSSEMediaType(predicateType)
if err != nil {
return nil, fmt.Errorf("failed to get layers: %w", err)
return nil, fmt.Errorf("failed to get DSSE media type for predicate '%s': %w", predicateType, err)
}
for i, l := range manifest.Layers {
if (strings.HasPrefix(string(l.MediaType), "application/vnd.in-toto.")) &&
strings.HasSuffix(string(l.MediaType), "+dsse") &&
l.Annotations[att.InTotoPredicateType] == predicateType {
reader, err := layers[i].Uncompressed()
for _, attestationLayer := range manifest.OriginalLayers {
mt, err := attestationLayer.Layer.MediaType()
if err != nil {
return nil, fmt.Errorf("failed to get layer media type: %w", err)
}
if string(mt) == dsseMediaType {
reader, err := attestationLayer.Layer.Uncompressed()
if err != nil {
return nil, fmt.Errorf("failed to get layer contents: %w", err)
}
defer reader.Close()
var env = new(att.Envelope)
env := new(att.Envelope)
err = json.NewDecoder(reader).Decode(&env)
if err != nil {
return nil, fmt.Errorf("failed to decode envelope: %w", err)
@@ -75,24 +74,26 @@ func ExtractEnvelopes(ia *AttestationManifest, predicateType string) ([]*att.Env
return envs, nil
}
func imageDigestForPlatform(ix *v1.IndexManifest, platform *v1.Platform) (string, error) {
for _, m := range ix.Manifests {
func imageDescriptor(ix *v1.IndexManifest, platform *v1.Platform) (*v1.Descriptor, error) {
for i := range ix.Manifests {
m := &ix.Manifests[i]
if (m.MediaType == ocispec.MediaTypeImageManifest || m.MediaType == "application/vnd.docker.distribution.manifest.v2+json") && m.Platform.Equals(*platform) {
return m.Digest.String(), nil
return m, nil
}
}
return "", errors.New(fmt.Sprintf("no image found for platform %v", platform))
return nil, fmt.Errorf("no image found for platform %v", platform)
}
func attestationDigestForDigest(ix *v1.IndexManifest, imageDigest string, attestType string) (string, error) {
for _, m := range ix.Manifests {
for i := range ix.Manifests {
m := &ix.Manifests[i]
if v, ok := m.Annotations[att.DockerReferenceType]; ok && v == attestType {
if d, ok := m.Annotations[att.DockerReferenceDigest]; ok && d == imageDigest {
return m.Digest.String(), nil
}
}
}
return "", errors.New(fmt.Sprintf("no attestation found for image %s", imageDigest))
return "", fmt.Errorf("no attestation found for image %s", imageDigest)
}
func RefToPURL(ref string, platform *v1.Platform) (string, bool, error) {
@@ -147,3 +148,50 @@ func SplitDigest(digest string) (common.DigestSet, error) {
parts[0]: parts[1],
}, nil
}
func ReplaceTagInSpec(src *ImageSpec, digest v1.Hash) (*ImageSpec, error) {
newName, err := replaceTag(src.Identifier, digest)
if err != nil {
return nil, fmt.Errorf("failed to parse repo name: %w", err)
}
return &ImageSpec{
Identifier: newName,
Type: src.Type,
Platform: src.Platform,
}, nil
}
// so that the index tag is replaced with a tag unique to the image digest and doesn't overwrite it.
func replaceTag(image string, digest v1.Hash) (string, error) {
if strings.HasPrefix(image, LocalPrefix) {
return image, nil
}
notag, err := WithoutTag(image)
if err != nil {
return "", nil
}
return fmt.Sprintf("%s:%s-%s.att", notag, digest.Algorithm, digest.Hex), nil
}
func ReplaceDigestInSpec(src *ImageSpec, digest v1.Hash) (*ImageSpec, error) {
newName, err := replaceDigest(src.Identifier, digest)
if err != nil {
return nil, fmt.Errorf("failed to parse repo name: %w", err)
}
return &ImageSpec{
Identifier: newName,
Type: src.Type,
Platform: src.Platform,
}, nil
}
func replaceDigest(image string, digest v1.Hash) (string, error) {
if strings.HasPrefix(image, LocalPrefix) {
return image, nil
}
notag, err := WithoutTag(image)
if err != nil {
return "", nil
}
return fmt.Sprintf("%s@%s:%s", notag, digest.Algorithm, digest.Hex), nil
}

View File

@@ -4,11 +4,14 @@ import (
"path/filepath"
"testing"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/layout"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
var UnsignedTestImage = filepath.Join("..", "..", "test", "testdata", "unsigned-test-image")
func TestRefToPurl(t *testing.T) {
arm, err := ParsePlatform("arm64/linux")
require.NoError(t, err)
@@ -53,10 +56,6 @@ func TestRefToPurl(t *testing.T) {
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)
@@ -75,14 +74,16 @@ func TestImageDigestForPlatform(t *testing.T) {
p, err := ParsePlatform("linux/amd64")
assert.NoError(t, err)
digest, err := imageDigestForPlatform(mfs2, p)
desc, err := imageDescriptor(mfs2, p)
assert.NoError(t, err)
digest := desc.Digest.String()
assert.Equal(t, "sha256:da8b190665956ea07890a0273e2a9c96bfe291662f08e2860e868eef69c34620", digest)
p, err = ParsePlatform("linux/arm64")
assert.NoError(t, err)
digest, err = imageDigestForPlatform(mfs2, p)
desc, err = imageDescriptor(mfs2, p)
assert.NoError(t, err)
digest = desc.Digest.String()
assert.Equal(t, "sha256:7a76cec943853f9f7105b1976afa1bf7cd5bb6afc4e9d5852dd8da7cf81ae86e", digest)
}
@@ -94,10 +95,10 @@ func TestWithoutTag(t *testing.T) {
{name: "image:tag", expected: "index.docker.io/library/image"},
{name: "image", expected: "index.docker.io/library/image"},
{name: "image:sha256-digest.att", expected: "index.docker.io/library/image"},
{name: "docker://image:tag", expected: "docker://index.docker.io/library/image"},
{name: RegistryPrefix + "image:tag", expected: RegistryPrefix + "index.docker.io/library/image"},
{name: "image@sha256:166710df254975d4a6c4c407c315951c22753dcaa829e020a3fd5d18fff70dd2", expected: "index.docker.io/library/image"},
{name: "docker://image@sha256:166710df254975d4a6c4c407c315951c22753dcaa829e020a3fd5d18fff70dd2", expected: "docker://index.docker.io/library/image"},
{name: "docker://127.0.0.1:36555/repo:latest", expected: "docker://127.0.0.1:36555/repo"},
{name: RegistryPrefix + "image@sha256:166710df254975d4a6c4c407c315951c22753dcaa829e020a3fd5d18fff70dd2", expected: RegistryPrefix + "index.docker.io/library/image"},
{name: RegistryPrefix + "127.0.0.1:36555/repo:latest", expected: RegistryPrefix + "127.0.0.1:36555/repo"},
}
for _, c := range tc {
t.Run(c.name, func(t *testing.T) {
@@ -106,3 +107,31 @@ func TestWithoutTag(t *testing.T) {
})
}
}
func TestReplaceTag(t *testing.T) {
tc := []struct {
name string
expected string
}{
{name: "image:tag", expected: "index.docker.io/library/image:sha256-digest.att"},
{name: "image", expected: "index.docker.io/library/image:sha256-digest.att"},
{name: "image:sha256-digest.att", expected: "index.docker.io/library/image:sha256-digest.att"},
{name: RegistryPrefix + "image:tag", expected: RegistryPrefix + "index.docker.io/library/image:sha256-digest.att"},
{name: "image@sha256:166710df254975d4a6c4c407c315951c22753dcaa829e020a3fd5d18fff70dd2", expected: "index.docker.io/library/image:sha256-digest.att"},
{name: LocalPrefix + "foobar", expected: LocalPrefix + "foobar"},
{name: RegistryPrefix + "image@sha256:166710df254975d4a6c4c407c315951c22753dcaa829e020a3fd5d18fff70dd2", expected: RegistryPrefix + "index.docker.io/library/image:sha256-digest.att"},
{name: RegistryPrefix + "127.0.0.1:36555/repo:latest", expected: RegistryPrefix + "127.0.0.1:36555/repo:sha256-digest.att"},
}
digest := v1.Hash{
Algorithm: "sha256",
Hex: "digest",
}
for _, c := range tc {
t.Run(c.name, func(t *testing.T) {
replaced, err := replaceTag(c.name, digest)
require.NoError(t, err)
assert.Equal(t, c.expected, replaced)
})
}
}

142
pkg/oci/output.go Normal file
View File

@@ -0,0 +1,142 @@
package oci
import (
"fmt"
"os"
"github.com/docker/attest/pkg/attestation"
"github.com/google/go-containerregistry/pkg/name"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/empty"
"github.com/google/go-containerregistry/pkg/v1/layout"
"github.com/google/go-containerregistry/pkg/v1/mutate"
"github.com/google/go-containerregistry/pkg/v1/remote"
)
func PushImageToRegistry(image v1.Image, imageName string) error {
ref, err := name.ParseReference(imageName)
if err != nil {
return fmt.Errorf("Failed to parse image name '%s': %w", imageName, err)
}
// Push the image to the registry
return remote.Write(ref, image, MultiKeychainOption())
}
func PushIndexToRegistry(index v1.ImageIndex, imageName string) error {
// Parse the index name
ref, err := name.ParseReference(imageName)
if err != nil {
return fmt.Errorf("Failed to parse image name: %w", err)
}
// Push the index to the registry
return remote.WriteIndex(ref, index, MultiKeychainOption())
}
func SaveImageAsOCILayout(image v1.Image, path string) error {
// Save the image to the local filesystem
err := os.MkdirAll(path, os.ModePerm)
if err != nil {
return fmt.Errorf("failed to create directory: %w", err)
}
index := empty.Index
l, err := layout.Write(path, index)
if err != nil {
return fmt.Errorf("failed to create index: %w", err)
}
return l.AppendImage(image)
}
func SaveIndexAsOCILayout(image v1.ImageIndex, path string) error {
// Save the index to the local filesystem
err := os.MkdirAll(path, os.ModePerm)
if err != nil {
return fmt.Errorf("failed to create directory: %w", err)
}
_, err = layout.Write(path, image)
if err != nil {
return fmt.Errorf("failed to create index: %w", err)
}
return nil
}
func SaveIndex(outputs []*ImageSpec, index v1.ImageIndex, indexName string) error {
// split output by comma and write or push each one
for _, output := range outputs {
if output.Type == OCI {
idx := v1.ImageIndex(empty.Index)
idx = mutate.AppendManifests(idx, mutate.IndexAddendum{
Add: index,
Descriptor: v1.Descriptor{
Annotations: map[string]string{
OCIReferenceTarget: indexName,
},
},
})
err := SaveIndexAsOCILayout(idx, output.Identifier)
if err != nil {
return fmt.Errorf("failed to write signed image: %w", err)
}
} else {
err := PushIndexToRegistry(index, output.Identifier)
if err != nil {
return fmt.Errorf("failed to push signed image: %w", err)
}
}
}
return nil
}
func SaveImage(output *ImageSpec, image v1.Image, imageName string) error {
if output.Type == OCI {
idx := v1.ImageIndex(empty.Index)
idx = mutate.AppendManifests(idx, mutate.IndexAddendum{
Add: image,
Descriptor: v1.Descriptor{
Annotations: map[string]string{
OCIReferenceTarget: imageName,
},
},
})
err := SaveIndexAsOCILayout(idx, output.Identifier)
if err != nil {
return fmt.Errorf("failed to write signed image: %w", err)
}
} else {
err := PushImageToRegistry(image, output.Identifier)
if err != nil {
return fmt.Errorf("failed to push signed image: %w", err)
}
}
return nil
}
func SaveReferrers(manifest *attestation.Manifest, outputs []*ImageSpec) error {
for _, output := range outputs {
// OCI layout output for referrers not supported
if output.Type == OCI {
continue
}
images, err := manifest.BuildReferringArtifacts()
if err != nil {
return fmt.Errorf("failed to build image: %w", err)
}
for _, image := range images {
digest, err := image.Digest()
if err != nil {
return fmt.Errorf("failed to get attestation image digest: %w", err)
}
attOut, err := ReplaceDigestInSpec(output, digest)
if err != nil {
return fmt.Errorf("failed to create attestation image spec: %w", err)
}
err = PushImageToRegistry(image, attOut.Identifier)
if err != nil {
return fmt.Errorf("failed to push image: %w", err)
}
}
}
return nil
}

110
pkg/oci/output_test.go Normal file
View File

@@ -0,0 +1,110 @@
package oci_test
import (
"fmt"
"net/http/httptest"
"net/url"
"path/filepath"
"testing"
"github.com/docker/attest/internal/test"
"github.com/docker/attest/pkg/attest"
"github.com/docker/attest/pkg/attestation"
"github.com/docker/attest/pkg/oci"
"github.com/google/go-containerregistry/pkg/registry"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/empty"
intoto "github.com/in-toto/in-toto-golang/in_toto"
"github.com/stretchr/testify/require"
)
func TestSavingIndex(t *testing.T) {
UnsignedTestImage := filepath.Join("..", "..", "test", "testdata", "unsigned-test-image")
outputLayout := test.CreateTempDir(t, "", "mirror-test")
attIdx, err := oci.IndexFromPath(UnsignedTestImage)
require.NoError(t, err)
server := httptest.NewServer(registry.New())
defer server.Close()
u, err := url.Parse(server.URL)
require.NoError(t, err)
indexName := fmt.Sprintf("%s/repo:root", u.Host)
output, err := oci.ParseImageSpecs(indexName)
require.NoError(t, err)
err = oci.SaveIndex(output, attIdx.Index, indexName)
require.NoError(t, err)
ociOutput, err := oci.ParseImageSpecs(oci.LocalPrefix + outputLayout)
require.NoError(t, err)
err = oci.SaveIndex(ociOutput, attIdx.Index, indexName)
require.NoError(t, err)
}
func TestSavingImage(t *testing.T) {
outputLayout := test.CreateTempDir(t, "", "mirror-test")
img := empty.Image
server := httptest.NewServer(registry.New())
defer server.Close()
u, err := url.Parse(server.URL)
require.NoError(t, err)
indexName := fmt.Sprintf("%s/repo:root", u.Host)
output, err := oci.ParseImageSpec(indexName)
require.NoError(t, err)
err = oci.SaveImage(output, img, indexName)
require.NoError(t, err)
ociOutput, err := oci.ParseImageSpec(oci.LocalPrefix + outputLayout)
require.NoError(t, err)
err = oci.SaveImage(ociOutput, img, indexName)
require.NoError(t, err)
}
func TestSavingReferrers(t *testing.T) {
ctx, signer := test.Setup(t)
opts := &attestation.SigningOptions{}
statement := &intoto.Statement{
StatementHeader: intoto.StatementHeader{
PredicateType: attestation.VSAPredicateType,
},
}
digest, err := v1.NewHash("sha256:da8b190665956ea07890a0273e2a9c96bfe291662f08e2860e868eef69c34620")
require.NoError(t, err)
subject := &v1.Descriptor{
MediaType: "application/vnd.oci.image.manifest.v1+json",
Digest: digest,
}
manifest, err := attest.NewAttestationManifest(subject)
require.NoError(t, err)
err = manifest.AddAttestation(ctx, signer, statement, opts)
require.NoError(t, err)
server := httptest.NewServer(registry.New(registry.WithReferrersSupport(true)))
defer server.Close()
u, err := url.Parse(server.URL)
require.NoError(t, err)
indexName := fmt.Sprintf("%s/repo:root", u.Host)
output, err := oci.ParseImageSpecs(indexName)
require.NoError(t, err)
err = oci.SaveReferrers(manifest, output)
require.NoError(t, err)
reg := &oci.MockRegistryResolver{
Subject: subject,
MockResolver: &oci.MockResolver{},
ImageNameStr: indexName,
}
require.NoError(t, err)
refResolver, err := oci.NewReferrersAttestationResolver(reg)
require.NoError(t, err)
attestations, err := refResolver.Attestations(ctx, attestation.VSAPredicateType)
require.NoError(t, err)
require.Len(t, attestations, 1)
}

View File

@@ -3,23 +3,22 @@ package oci
import (
"context"
"fmt"
"strings"
"github.com/docker/attest/pkg/attestation"
att "github.com/docker/attest/pkg/attestation"
"github.com/google/go-containerregistry/pkg/name"
"github.com/google/go-containerregistry/pkg/v1/remote"
"github.com/pkg/errors"
)
type ReferrersResolver struct {
digest string
referrersRepo string
manifests []*AttestationManifest
*RegistryImageDetailsResolver
ImageDetailsResolver
}
func NewReferrersAttestationResolver(src *RegistryImageDetailsResolver, options ...func(*ReferrersResolver) error) (*ReferrersResolver, error) {
func NewReferrersAttestationResolver(src ImageDetailsResolver, options ...func(*ReferrersResolver) error) (*ReferrersResolver, error) {
res := &ReferrersResolver{
RegistryImageDetailsResolver: src,
ImageDetailsResolver: src,
}
for _, opt := range options {
err := opt(res)
@@ -37,80 +36,87 @@ func WithReferrersRepo(repo string) func(*ReferrersResolver) error {
}
}
func (r *ReferrersResolver) resolveAttestations(ctx context.Context) error {
if r.manifests == nil {
subjectRef, err := name.ParseReference(r.Identifier)
if err != nil {
return fmt.Errorf("failed to parse reference: %w", err)
}
subjectDigest, err := r.ImageDigest(ctx)
if err != nil {
return fmt.Errorf("failed to get digest: %w", err)
}
var referrersSubjectRef name.Digest
if r.referrersRepo != "" {
referrersSubjectRef, err = name.NewDigest(fmt.Sprintf("%s@%s", r.referrersRepo, subjectDigest))
if err != nil {
return fmt.Errorf("failed to create referrers reference: %w", err)
}
} else {
referrersSubjectRef = subjectRef.Context().Digest(subjectDigest)
}
referrersIndex, err := remote.Referrers(referrersSubjectRef)
if err != nil {
return fmt.Errorf("failed to get referrers: %w", err)
}
referrersIndexManifest, err := referrersIndex.IndexManifest()
if err != nil {
return fmt.Errorf("failed to get index manifest: %w", err)
}
if len(referrersIndexManifest.Manifests) == 0 {
return errors.New("no referrers found")
}
aManifests := make([]*AttestationManifest, 0)
for _, m := range referrersIndexManifest.Manifests {
remoteRef := referrersSubjectRef.Context().Digest(m.Digest.String())
attestationImage, err := remote.Image(remoteRef)
if err != nil {
return fmt.Errorf("failed to get referred image: %w", err)
}
manifest, err := attestationImage.Manifest()
if err != nil {
return fmt.Errorf("failed to get manifest: %w", err)
}
if manifest.Annotations[att.DockerReferenceType] != att.AttestationManifestType {
continue
}
if manifest.Annotations[att.DockerReferenceDigest] != subjectDigest {
continue
}
attest := &AttestationManifest{
Name: r.Identifier,
Image: attestationImage,
Manifest: manifest,
Descriptor: &m,
Digest: subjectDigest,
Platform: r.Platform,
}
aManifests = append(aManifests, attest)
}
if len(aManifests) == 0 {
return errors.New("no attestation manifests found")
}
r.manifests = aManifests
func (r *ReferrersResolver) resolveAttestations(ctx context.Context, predicateType string) ([]*attestation.Manifest, error) {
dsseMediaType, err := attestation.DSSEMediaType(predicateType)
if err != nil {
return nil, fmt.Errorf("failed to get DSSE media type for predicate '%s': %w", predicateType, err)
}
return nil
imageName, err := r.ImageName(ctx)
if err != nil {
return nil, fmt.Errorf("failed to get image name: %w", err)
}
subjectRef, err := name.ParseReference(imageName)
if err != nil {
return nil, fmt.Errorf("failed to parse reference: %w", err)
}
desc, err := r.ImageDescriptor(ctx)
if err != nil {
return nil, fmt.Errorf("failed to get descriptor: %w", err)
}
subjectDigest := desc.Digest.String()
if err != nil {
return nil, fmt.Errorf("failed to get digest: %w", err)
}
var referrersSubjectRef name.Digest
if r.referrersRepo != "" {
referrersSubjectRef, err = name.NewDigest(fmt.Sprintf("%s@%s", strings.TrimPrefix(r.referrersRepo, RegistryPrefix), subjectDigest))
if err != nil {
return nil, fmt.Errorf("failed to create referrers reference: %w", err)
}
} else {
referrersSubjectRef = subjectRef.Context().Digest(subjectDigest)
}
options := WithOptions(ctx, nil)
options = append(options, remote.WithFilter("artifactType", dsseMediaType))
referrersIndex, err := remote.Referrers(referrersSubjectRef, options...)
if err != nil {
return nil, fmt.Errorf("failed to get referrers: %w", err)
}
referrersIndexManifest, err := referrersIndex.IndexManifest()
if err != nil {
return nil, fmt.Errorf("failed to get index manifest: %w", err)
}
aManifests := make([]*attestation.Manifest, 0)
for i := range referrersIndexManifest.Manifests {
m := referrersIndexManifest.Manifests[i]
remoteRef := referrersSubjectRef.Context().Digest(m.Digest.String())
options = WithOptions(ctx, nil)
attestationImage, err := remote.Image(remoteRef, options...)
if err != nil {
return nil, fmt.Errorf("failed to get referred image: %w", err)
}
layers, err := attestation.GetAttestationsFromImage(attestationImage)
if err != nil {
return nil, fmt.Errorf("failed to get attestations from image: %w", err)
}
if len(layers) != 1 {
return nil, fmt.Errorf("expected exactly one layer, got %d", len(layers))
}
mt, err := layers[0].Layer.MediaType()
if err != nil {
return nil, fmt.Errorf("failed to get layer media type: %w", err)
}
if string(mt) != dsseMediaType {
return nil, fmt.Errorf("expected layer media type %s, got %s", dsseMediaType, mt)
}
attest := &attestation.Manifest{
SubjectName: imageName,
OriginalLayers: layers,
OriginalDescriptor: &m,
SubjectDescriptor: desc,
}
aManifests = append(aManifests, attest)
}
return aManifests, nil
}
func (r *ReferrersResolver) Attestations(ctx context.Context, predicateType string) ([]*att.Envelope, error) {
err := r.resolveAttestations(ctx)
manifests, err := r.resolveAttestations(ctx, predicateType)
if err != nil {
return nil, fmt.Errorf("failed to resolve attestations: %w", err)
}
var envs []*att.Envelope
for _, attest := range r.manifests {
for _, attest := range manifests {
es, err := ExtractEnvelopes(attest, predicateType)
if err != nil {
return nil, fmt.Errorf("failed to extract envelopes: %w", err)

View File

@@ -2,9 +2,9 @@ package oci
import (
"context"
"encoding/json"
"fmt"
"github.com/docker/attest/pkg/attestation"
att "github.com/docker/attest/pkg/attestation"
"github.com/google/go-containerregistry/pkg/name"
v1 "github.com/google/go-containerregistry/pkg/v1"
@@ -13,12 +13,12 @@ import (
type RegistryResolver struct {
*RegistryImageDetailsResolver
*AttestationManifest
*attestation.Manifest
}
type RegistryImageDetailsResolver struct {
*ImageSpec
digest string
descriptor *v1.Descriptor
}
func NewRegistryImageDetailsResolver(src *ImageSpec) (*RegistryImageDetailsResolver, error) {
@@ -33,46 +33,58 @@ func NewRegistryAttestationResolver(src *RegistryImageDetailsResolver) (*Registr
}, nil
}
func (r *RegistryImageDetailsResolver) ImageName(ctx context.Context) (string, error) {
func (r *RegistryImageDetailsResolver) ImageName(_ context.Context) (string, error) {
return r.Identifier, nil
}
func (r *RegistryImageDetailsResolver) ImagePlatform(ctx context.Context) (*v1.Platform, error) {
func (r *RegistryImageDetailsResolver) ImagePlatform(_ context.Context) (*v1.Platform, error) {
return r.Platform, nil
}
func (r *RegistryImageDetailsResolver) ImageDigest(ctx context.Context) (string, error) {
if r.digest == "" {
func (r *RegistryImageDetailsResolver) ImageDescriptor(ctx context.Context) (*v1.Descriptor, error) {
if r.descriptor == nil {
subjectRef, err := name.ParseReference(r.Identifier)
if err != nil {
return "", fmt.Errorf("failed to parse reference: %w", err)
return nil, fmt.Errorf("failed to parse reference: %w", err)
}
options := WithOptions(ctx, r.Platform)
desc, err := remote.Image(subjectRef, options...)
image, err := remote.Image(subjectRef, options...)
if err != nil {
return "", fmt.Errorf("failed to get image manifest: %w", err)
return nil, fmt.Errorf("failed to get image manifest: %w", err)
}
subjectDigest, err := desc.Digest()
digest, err := image.Digest()
if err != nil {
return "", fmt.Errorf("failed to get image digest: %w", err)
return nil, fmt.Errorf("failed to get image digest: %w", err)
}
size, err := image.Size()
if err != nil {
return nil, fmt.Errorf("failed to get image size: %w", err)
}
mediaType, err := image.MediaType()
if err != nil {
return nil, fmt.Errorf("failed to get image media type: %w", err)
}
r.descriptor = &v1.Descriptor{
Digest: digest,
Size: size,
MediaType: mediaType,
}
r.digest = subjectDigest.String()
}
return r.digest, nil
return r.descriptor, nil
}
func (r *RegistryResolver) Attestations(ctx context.Context, predicateType string) ([]*att.Envelope, error) {
if r.AttestationManifest == nil {
if r.Manifest == nil {
attest, err := FetchAttestationManifest(ctx, r.Identifier, r.ImageSpec.Platform)
if err != nil {
return nil, err
}
r.AttestationManifest = attest
r.Manifest = attest
}
return ExtractEnvelopes(r.AttestationManifest, predicateType)
return ExtractEnvelopes(r.Manifest, predicateType)
}
func FetchAttestationManifest(ctx context.Context, image string, platform *v1.Platform) (*AttestationManifest, error) {
func FetchAttestationManifest(ctx context.Context, image string, platform *v1.Platform) (*attestation.Manifest, error) {
// we want to get to the image index, so ignoring platform for now
options := WithOptions(ctx, nil)
ref, err := name.ParseReference(image)
@@ -87,10 +99,12 @@ func FetchAttestationManifest(ctx context.Context, image string, platform *v1.Pl
if err != nil {
return nil, fmt.Errorf("failed to get index manifest: %w", err)
}
digest, err := imageDigestForPlatform(indexManifest, platform)
subjectDescriptor, err := imageDescriptor(indexManifest, platform)
if err != nil {
return nil, fmt.Errorf("failed to obtain image for platform: %w", err)
}
digest := subjectDescriptor.Digest.String()
ref, err = name.ParseReference(fmt.Sprintf("%s@%s", ref.Context().Name(), digest))
if err != nil {
return nil, fmt.Errorf("failed to parse attestation reference: %w", err)
@@ -108,22 +122,20 @@ func FetchAttestationManifest(ctx context.Context, image string, platform *v1.Pl
if err != nil {
return nil, fmt.Errorf("failed to get attestation: %w", err)
}
manifest := new(v1.Manifest)
err = json.Unmarshal(remoteDescriptor.Manifest, manifest)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal attestation: %w", err)
}
attestationImage, err := remoteDescriptor.Image()
if err != nil {
return nil, fmt.Errorf("failed to get attestation image: %w", err)
}
attest := &AttestationManifest{
Name: image,
Image: attestationImage,
Manifest: manifest,
Descriptor: &remoteDescriptor.Descriptor,
Digest: digest,
Platform: platform,
layers, err := attestation.GetAttestationsFromImage(attestationImage)
if err != nil {
return nil, fmt.Errorf("failed to get attestations from image: %w", err)
}
attest := &attestation.Manifest{
OriginalLayers: layers,
OriginalDescriptor: &remoteDescriptor.Descriptor,
SubjectName: image,
SubjectDescriptor: subjectDescriptor,
}
return attest, nil
}

View File

@@ -10,7 +10,6 @@ import (
"github.com/docker/attest/internal/test"
"github.com/docker/attest/pkg/attest"
"github.com/docker/attest/pkg/attestation"
"github.com/docker/attest/pkg/mirror"
"github.com/docker/attest/pkg/oci"
"github.com/docker/attest/pkg/policy"
"github.com/google/go-containerregistry/pkg/registry"
@@ -25,21 +24,18 @@ func TestRegistry(t *testing.T) {
u, err := url.Parse(server.URL)
require.NoError(t, err)
opts := &attestation.SigningOptions{
Replace: true,
SkipSubject: true,
}
opts := &attestation.SigningOptions{}
attIdx, err := oci.IndexFromPath(oci.UnsignedTestImage)
require.NoError(t, err)
signedManifests, err := attest.SignStatements(ctx, attIdx.Index, signer, opts)
require.NoError(t, err)
signedIndex := attIdx.Index
signedIndex, err = attestation.AddImagesToIndex(signedIndex, signedManifests)
signedIndex, err = attestation.UpdateIndexImages(signedIndex, signedManifests)
require.NoError(t, err)
indexName := fmt.Sprintf("%s/repo:root", u.Host)
require.NoError(t, err)
err = mirror.PushIndexToRegistry(signedIndex, indexName)
err = oci.PushIndexToRegistry(signedIndex, indexName)
require.NoError(t, err)
spec, err := oci.ParseImageSpec(indexName)
@@ -47,7 +43,8 @@ func TestRegistry(t *testing.T) {
resolver, err := policy.CreateImageDetailsResolver(spec)
require.NoError(t, err)
digest, err := resolver.ImageDigest(ctx)
desc, err := resolver.ImageDescriptor(ctx)
require.NoError(t, err)
digest := desc.Digest.String()
assert.True(t, strings.Contains(digest, "sha256:"))
}

View File

@@ -7,21 +7,6 @@ import (
v1 "github.com/google/go-containerregistry/pkg/v1"
)
type AttestationManifests struct {
Manifests []*AttestationManifest
}
type AttestationManifest struct {
// attestation image details
Image v1.Image
Manifest *v1.Manifest
Descriptor *v1.Descriptor
// details of subect image
Name string
Digest string
Platform *v1.Platform
}
type AttestationResolver interface {
ImageDetailsResolver
Attestations(ctx context.Context, mediaType string) ([]*att.Envelope, error)
@@ -30,25 +15,5 @@ type AttestationResolver interface {
type ImageDetailsResolver interface {
ImageName(ctx context.Context) (string, error)
ImagePlatform(ctx context.Context) (*v1.Platform, error)
ImageDigest(ctx context.Context) (string, error)
}
type MockResolver struct {
Envs []*att.Envelope
}
func (r MockResolver) Attestations(ctx context.Context, mediaType string) ([]*att.Envelope, error) {
return r.Envs, nil
}
func (r MockResolver) ImageName(ctx context.Context) (string, error) {
return "library/alpine:latest", nil
}
func (r MockResolver) ImageDigest(ctx context.Context) (string, error) {
return "sha256:test-digest", nil
}
func (r MockResolver) ImagePlatform(ctx context.Context) (*v1.Platform, error) {
return ParsePlatform("linux/amd64")
ImageDescriptor(ctx context.Context) (*v1.Descriptor, error)
}

View File

@@ -11,18 +11,20 @@ import (
)
const (
OciReferenceTarget = "org.opencontainers.image.ref.name"
OCIReferenceTarget = "org.opencontainers.image.ref.name"
LocalPrefix = "oci://"
RegistryPrefix = "docker://"
OCI SourceType = "OCI"
Docker SourceType = "Docker"
)
type SourceType string
type NamedIndex struct {
Index v1.ImageIndex
Name string
}
type (
SourceType string
NamedIndex struct {
Index v1.ImageIndex
Name string
}
)
type AttestationOptions struct {
NoReferrers bool
@@ -50,7 +52,7 @@ func IndexFromPath(path string) (*NamedIndex, error) {
if err != nil {
return nil, fmt.Errorf("failed to get digest: %w", err)
}
imageName := idxm.Manifests[0].Annotations[OciReferenceTarget]
imageName := idxm.Manifests[0].Annotations[OCIReferenceTarget]
idxDigest := idxm.Manifests[0].Digest
idx, err := wrapperIdx.ImageIndex(idxDigest)
@@ -83,9 +85,8 @@ func IndexFromRemote(image string) (*NamedIndex, error) {
func LoadIndex(input *ImageSpec) (*NamedIndex, error) {
if input.Type == OCI {
return IndexFromPath(input.Identifier)
} else {
return IndexFromRemote(input.Identifier)
}
return IndexFromRemote(input.Identifier)
}
func (i *ImageSpec) ForPlatforms(platform string) ([]*ImageSpec, error) {

View File

@@ -11,20 +11,20 @@ type policyEvaluatorCtxKeyType struct{}
var PolicyEvaluatorCtxKey policyEvaluatorCtxKeyType
// sets PolicyEvaluator in context
func WithPolicyEvaluator(ctx context.Context, pe PolicyEvaluator) context.Context {
// sets PolicyEvaluator in context.
func WithPolicyEvaluator(ctx context.Context, pe Evaluator) context.Context {
return context.WithValue(ctx, PolicyEvaluatorCtxKey, pe)
}
// gets PolicyEvaluator from context, defaults to Rego PolicyEvaluator if not set
func GetPolicyEvaluator(ctx context.Context) (PolicyEvaluator, error) {
t, ok := ctx.Value(PolicyEvaluatorCtxKey).(PolicyEvaluator)
// gets PolicyEvaluator from context, defaults to Rego PolicyEvaluator if not set.
func GetPolicyEvaluator(ctx context.Context) (Evaluator, error) {
t, ok := ctx.Value(PolicyEvaluatorCtxKey).(Evaluator)
if !ok {
return nil, fmt.Errorf("no policy evaluator client set on context (set one with policy.WithPolicyEvaluator)")
}
return t, nil
}
type PolicyEvaluator interface {
Evaluate(ctx context.Context, resolver oci.AttestationResolver, pctx *Policy, input *PolicyInput) (*Result, error)
type Evaluator interface {
Evaluate(ctx context.Context, resolver oci.AttestationResolver, pctx *Policy, input *Input) (*Result, error)
}

View File

@@ -7,19 +7,19 @@ import (
)
type MockPolicyEvaluator struct {
EvaluateFunc func(ctx context.Context, resolver oci.AttestationResolver, pctx *Policy, input *PolicyInput) (*Result, error)
EvaluateFunc func(ctx context.Context, resolver oci.AttestationResolver, pctx *Policy, input *Input) (*Result, error)
}
func (pe *MockPolicyEvaluator) Evaluate(ctx context.Context, resolver oci.AttestationResolver, pctx *Policy, input *PolicyInput) (*Result, error) {
func (pe *MockPolicyEvaluator) Evaluate(ctx context.Context, resolver oci.AttestationResolver, pctx *Policy, input *Input) (*Result, error) {
if pe.EvaluateFunc != nil {
return pe.EvaluateFunc(ctx, resolver, pctx, input)
}
return AllowedResult(), nil
}
func GetMockPolicy() PolicyEvaluator {
func GetMockPolicy() Evaluator {
return &MockPolicyEvaluator{
EvaluateFunc: func(ctx context.Context, resolver oci.AttestationResolver, pctx *Policy, input *PolicyInput) (*Result, error) {
EvaluateFunc: func(_ context.Context, _ oci.AttestationResolver, _ *Policy, _ *Input) (*Result, error) {
return AllowedResult(), nil
},
}

View File

@@ -12,11 +12,11 @@ import (
"github.com/docker/attest/pkg/oci"
)
func resolveLocalPolicy(opts *PolicyOptions, mapping *config.PolicyMapping, imageName string, matchedName string) (*Policy, error) {
func resolveLocalPolicy(opts *Options, mapping *config.PolicyMapping, imageName string, matchedName string) (*Policy, error) {
if opts.LocalPolicyDir == "" {
return nil, fmt.Errorf("local policy dir not set")
}
files := make([]*PolicyFile, 0, len(mapping.Files))
files := make([]*File, 0, len(mapping.Files))
for _, f := range mapping.Files {
filename := f.Path
filePath := path.Join(opts.LocalPolicyDir, filename)
@@ -24,7 +24,7 @@ func resolveLocalPolicy(opts *PolicyOptions, mapping *config.PolicyMapping, imag
if err != nil {
return nil, fmt.Errorf("failed to read policy file %s: %w", filename, err)
}
files = append(files, &PolicyFile{
files = append(files, &File{
Path: filename,
Content: fileContents,
})
@@ -39,15 +39,15 @@ func resolveLocalPolicy(opts *PolicyOptions, mapping *config.PolicyMapping, imag
return policy, nil
}
func resolveTufPolicy(opts *PolicyOptions, mapping *config.PolicyMapping, imageName string, matchedName string) (*Policy, error) {
files := make([]*PolicyFile, 0, len(mapping.Files))
func resolveTUFPolicy(opts *Options, mapping *config.PolicyMapping, imageName string, matchedName string) (*Policy, error) {
files := make([]*File, 0, len(mapping.Files))
for _, f := range mapping.Files {
filename := f.Path
_, fileContents, err := opts.TufClient.DownloadTarget(filename, filepath.Join(opts.LocalTargetsDir, filename))
_, fileContents, err := opts.TUFClient.DownloadTarget(filename, filepath.Join(opts.LocalTargetsDir, filename))
if err != nil {
return nil, fmt.Errorf("failed to download policy file %s: %w", filename, err)
}
files = append(files, &PolicyFile{
files = append(files, &File{
Path: filename,
Content: fileContents,
})
@@ -88,12 +88,12 @@ func findPolicyMatchImpl(imageName string, mappings *config.PolicyMappings, matc
for _, rule := range mappings.Rules {
if rule.Pattern.MatchString(imageName) {
switch {
case rule.PolicyId == "" && rule.Replacement == "":
case rule.PolicyID == "" && rule.Replacement == "":
return nil, fmt.Errorf("rule %s has neither policy-id nor rewrite", rule.Pattern)
case rule.PolicyId != "" && rule.Replacement != "":
case rule.PolicyID != "" && rule.Replacement != "":
return nil, fmt.Errorf("rule %s has both policy-id and rewrite", rule.Pattern)
case rule.PolicyId != "":
policy := mappings.Policies[rule.PolicyId]
case rule.PolicyID != "":
policy := mappings.Policies[rule.PolicyID]
if policy != nil {
return &policyMatch{
matchType: matchTypePolicy,
@@ -120,35 +120,35 @@ func findPolicyMatchImpl(imageName string, mappings *config.PolicyMappings, matc
return &policyMatch{matchType: matchTypeNoMatch, matchedName: imageName}, nil
}
func resolvePolicyById(opts *PolicyOptions) (*Policy, error) {
if opts.PolicyId != "" {
func resolvePolicyByID(opts *Options) (*Policy, error) {
if opts.PolicyID != "" {
localMappings, err := config.LoadLocalMappings(opts.LocalPolicyDir)
if err != nil {
return nil, fmt.Errorf("failed to load local policy mappings: %w", err)
}
if localMappings != nil {
policy := localMappings.Policies[opts.PolicyId]
policy := localMappings.Policies[opts.PolicyID]
if policy != nil {
return resolveLocalPolicy(opts, policy, "", "")
}
}
// must check tuf
tufMappings, err := config.LoadTufMappings(opts.TufClient, opts.LocalTargetsDir)
tufMappings, err := config.LoadTUFMappings(opts.TUFClient, opts.LocalTargetsDir)
if err != nil {
return nil, fmt.Errorf("failed to load tuf policy mappings by id: %w", err)
}
policy := tufMappings.Policies[opts.PolicyId]
policy := tufMappings.Policies[opts.PolicyID]
if policy != nil {
return resolveTufPolicy(opts, policy, "", "")
return resolveTUFPolicy(opts, policy, "", "")
}
return nil, fmt.Errorf("policy with id %s not found", opts.PolicyId)
return nil, fmt.Errorf("policy with id %s not found", opts.PolicyID)
}
return nil, nil
}
func ResolvePolicy(ctx context.Context, detailsResolver oci.ImageDetailsResolver, opts *PolicyOptions) (*Policy, error) {
p, err := resolvePolicyById(opts)
func ResolvePolicy(ctx context.Context, detailsResolver oci.ImageDetailsResolver, opts *Options) (*Policy, error) {
p, err := resolvePolicyByID(opts)
if err != nil {
return nil, fmt.Errorf("failed to resolve policy by id: %w", err)
}
@@ -175,7 +175,7 @@ func ResolvePolicy(ctx context.Context, detailsResolver oci.ImageDetailsResolver
return resolveLocalPolicy(opts, match.policy, imageName, match.matchedName)
}
// must check tuf
tufMappings, err := config.LoadTufMappings(opts.TufClient, opts.LocalTargetsDir)
tufMappings, err := config.LoadTUFMappings(opts.TUFClient, opts.LocalTargetsDir)
if err != nil {
return nil, fmt.Errorf("failed to load tuf policy mappings as fallback: %w", err)
}
@@ -183,8 +183,8 @@ func ResolvePolicy(ctx context.Context, detailsResolver oci.ImageDetailsResolver
// it's a mirror of a tuf policy
if match.matchType == matchTypeMatchNoPolicy {
for _, mapping := range tufMappings.Policies {
if mapping.Id == match.rule.PolicyId {
return resolveTufPolicy(opts, mapping, imageName, match.matchedName)
if mapping.ID == match.rule.PolicyID {
return resolveTUFPolicy(opts, mapping, imageName, match.matchedName)
}
}
}
@@ -195,7 +195,7 @@ func ResolvePolicy(ctx context.Context, detailsResolver oci.ImageDetailsResolver
return nil, err
}
if match.matchType == matchTypePolicy {
return resolveTufPolicy(opts, match.policy, imageName, match.matchedName)
return resolveTUFPolicy(opts, match.policy, imageName, match.matchedName)
}
return nil, nil
}
@@ -219,20 +219,20 @@ func CreateImageDetailsResolver(imageSource *oci.ImageSpec) (oci.ImageDetailsRes
}
func CreateAttestationResolver(resolver oci.ImageDetailsResolver, mapping *config.PolicyMapping) (oci.AttestationResolver, error) {
switch resolver := resolver.(type) {
case *oci.RegistryImageDetailsResolver:
if mapping.Attestations != nil && mapping.Attestations.Style == config.AttestationStyleAttached {
return oci.NewRegistryAttestationResolver(resolver)
} else {
if mapping.Attestations != nil && mapping.Attestations.Repo != "" {
return oci.NewReferrersAttestationResolver(resolver, oci.WithReferrersRepo(mapping.Attestations.Repo))
} else {
return oci.NewReferrersAttestationResolver(resolver)
if mapping.Attestations != nil {
if mapping.Attestations.Style == config.AttestationStyleAttached {
switch resolver := resolver.(type) {
case *oci.RegistryImageDetailsResolver:
return oci.NewRegistryAttestationResolver(resolver)
case *oci.LayoutResolver:
return resolver, nil
default:
return nil, fmt.Errorf("unsupported image details resolver type: %T", resolver)
}
}
case *oci.OCILayoutResolver:
return resolver, nil
default:
return nil, fmt.Errorf("unsupported image details resolver type: %T", resolver)
if mapping.Attestations.Repo != "" {
return oci.NewReferrersAttestationResolver(resolver, oci.WithReferrersRepo(mapping.Attestations.Repo))
}
}
return oci.NewReferrersAttestationResolver(resolver)
}

View File

@@ -112,7 +112,7 @@ func TestFindPolicyMatch(t *testing.T) {
assert.Equal(t, tc.expectedMatchType, match.matchType)
if match.matchType == matchTypePolicy {
if assert.NotNil(t, match.policy) {
assert.Equal(t, tc.expectedPolicyID, match.policy.Id)
assert.Equal(t, tc.expectedPolicyID, match.policy.ID)
}
}
assert.Equal(t, tc.expectedImageName, match.matchedName)

View File

@@ -22,7 +22,7 @@ func loadAttestation(t *testing.T, path string) *attestation.Envelope {
t.Fatal(err)
}
var env = new(attestation.Envelope)
env := new(attestation.Envelope)
err = json.Unmarshal(ex, env)
if err != nil {
t.Fatal(err)
@@ -47,13 +47,13 @@ func TestRegoEvaluator_Evaluate(t *testing.T) {
expectSuccess bool
isCanonical bool
resolver oci.AttestationResolver
policy *policy.PolicyOptions
policyId string
policy *policy.Options
policyID string
errorStr string
}{
{repo: "testdata/mock-tuf-allow", expectSuccess: true, isCanonical: false, resolver: defaultResolver},
{repo: "testdata/mock-tuf-allow", expectSuccess: true, isCanonical: false, resolver: defaultResolver, policyId: "docker-official-images"},
{repo: "testdata/mock-tuf-allow", expectSuccess: false, isCanonical: false, resolver: defaultResolver, policyId: "non-existent-policy-id", errorStr: errorStr},
{repo: "testdata/mock-tuf-allow", expectSuccess: true, isCanonical: false, resolver: defaultResolver, policyID: "docker-official-images"},
{repo: "testdata/mock-tuf-allow", expectSuccess: false, isCanonical: false, resolver: defaultResolver, policyID: "non-existent-policy-id", errorStr: errorStr},
{repo: "testdata/mock-tuf-deny", expectSuccess: false, isCanonical: false, resolver: defaultResolver},
{repo: "testdata/mock-tuf-verify-sig", expectSuccess: true, isCanonical: false, resolver: defaultResolver},
{repo: "testdata/mock-tuf-wrong-key", expectSuccess: false, isCanonical: false, resolver: defaultResolver},
@@ -63,18 +63,18 @@ func TestRegoEvaluator_Evaluate(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.repo, func(t *testing.T) {
input := &policy.PolicyInput{
input := &policy.Input{
Digest: "sha256:test-digest",
Purl: "test-purl",
PURL: "test-purl",
IsCanonical: tc.isCanonical,
}
tufClient := tuf.NewMockTufClient(tc.repo, test.CreateTempDir(t, "", "tuf-dest"))
if tc.policy == nil {
tc.policy = &policy.PolicyOptions{
TufClient: tufClient,
tc.policy = &policy.Options{
TUFClient: tufClient,
LocalTargetsDir: test.CreateTempDir(t, "", "tuf-targets"),
PolicyId: tc.policyId,
PolicyID: tc.policyID,
}
}
imageName, err := tc.resolver.ImageName(ctx)
@@ -103,7 +103,6 @@ func TestRegoEvaluator_Evaluate(t *testing.T) {
}
})
}
}
func TestLoadingMappings(t *testing.T) {
@@ -111,8 +110,69 @@ func TestLoadingMappings(t *testing.T) {
require.NoError(t, err)
assert.Equal(t, len(policyMappings.Rules), 3)
for _, mirror := range policyMappings.Rules {
if mirror.PolicyId != "" {
assert.Equal(t, "docker-official-images", mirror.PolicyId)
if mirror.PolicyID != "" {
assert.Equal(t, "docker-official-images", mirror.PolicyID)
}
}
}
func TestCreateAttestationResolver(t *testing.T) {
mockResolver := oci.MockResolver{
Envs: []*attestation.Envelope{},
}
layoutResolver := &oci.LayoutResolver{}
registryResolver := &oci.RegistryImageDetailsResolver{}
nilRepoReferrers := &config.PolicyMapping{
Attestations: &config.AttestationConfig{
Style: config.AttestationStyleReferrers,
},
}
referrers := &config.PolicyMapping{
Attestations: &config.AttestationConfig{
Repo: "localhost:5000/repo",
Style: config.AttestationStyleReferrers,
},
}
attached := &config.PolicyMapping{
Attestations: &config.AttestationConfig{
Style: config.AttestationStyleAttached,
},
}
testCases := []struct {
name string
resolver oci.ImageDetailsResolver
mapping *config.PolicyMapping
errorStr string
}{
{name: "referrers", resolver: layoutResolver, mapping: referrers},
{name: "referrers (no mapped repo)", resolver: layoutResolver, mapping: nilRepoReferrers},
{name: "referrers (no mapping)", resolver: layoutResolver, mapping: &config.PolicyMapping{Attestations: nil}},
{name: "attached (registry)", resolver: registryResolver, mapping: attached},
{name: "attached (layout)", resolver: layoutResolver, mapping: attached},
{name: "attached (unsupported)", resolver: mockResolver, mapping: attached, errorStr: "unsupported image details resolver type"},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
resolver, err := policy.CreateAttestationResolver(tc.resolver, tc.mapping)
if tc.errorStr == "" {
require.NoError(t, err)
} else {
assert.Contains(t, err.Error(), tc.errorStr)
}
if tc.mapping.Attestations == nil {
return
}
switch resolver.(type) {
case *oci.ReferrersResolver:
assert.Equal(t, tc.mapping.Attestations.Style, config.AttestationStyleReferrers)
case *oci.RegistryResolver:
assert.Equal(t, tc.mapping.Attestations.Style, config.AttestationStyleAttached)
case *oci.LayoutResolver:
assert.Equal(t, tc.mapping.Attestations.Style, config.AttestationStyleAttached)
}
})
}
}

View File

@@ -30,13 +30,13 @@ const (
resultBinding = "result"
)
func NewRegoEvaluator(debug bool) PolicyEvaluator {
func NewRegoEvaluator(debug bool) Evaluator {
return &regoEvaluator{
debug: debug,
}
}
func (re *regoEvaluator) Evaluate(ctx context.Context, resolver oci.AttestationResolver, pctx *Policy, input *PolicyInput) (*Result, error) {
func (re *regoEvaluator) Evaluate(ctx context.Context, resolver oci.AttestationResolver, pctx *Policy, input *Input) (*Result, error) {
var regoOpts []func(*rego.Rego)
// Create a new in-memory store
@@ -113,7 +113,7 @@ func (re *regoEvaluator) Evaluate(ctx context.Context, resolver oci.AttestationR
}
func jsonGenerator[T any]() func(t *ast.Term, ec *rego.EvalContext) (any, error) {
return func(t *ast.Term, ec *rego.EvalContext) (any, error) {
return func(t *ast.Term, _ *rego.EvalContext) (any, error) {
// TODO: this is horrible - we're converting the AST to JSON and then back to AST, then using ast.As to convert it to a struct
// We can't use ast.As directly because it fails if the AST contains a set
json, err := ast.JSON(t.Value)
@@ -140,6 +140,7 @@ var verifyDecl = &ast.Builtin{
Decl: types.NewFunction(types.Args(dynamicObj, dynamicObj), dynamicObj),
Nondeterministic: true,
}
var attestDecl = &ast.Builtin{
Name: "attest.fetch",
Decl: types.NewFunction(types.Args(types.S), dynamicObj),
@@ -163,9 +164,9 @@ func handleErrors1(f func(rCtx rego.BuiltinContext, a *ast.Term) (*ast.Term, err
}
}
func handleErrors2(f func(rCtx rego.BuiltinContext, a, b *ast.Term) (*ast.Term, error)) rego.Builtin2 {
func handleErrors2(f func(rCtx *rego.BuiltinContext, a, b *ast.Term) (*ast.Term, error)) rego.Builtin2 {
return func(rCtx rego.BuiltinContext, a, b *ast.Term) (*ast.Term, error) {
return wrapFunctionResult(f(rCtx, a, b))
return wrapFunctionResult(f(&rCtx, a, b))
}
}
@@ -180,7 +181,7 @@ func RegoFunctions(resolver oci.AttestationResolver) []*tester.Builtin {
Memoize: true,
Nondeterministic: verifyDecl.Nondeterministic,
},
handleErrors2(verifyIntotoEnvelope)),
handleErrors2(verifyInTotoEnvelope)),
},
{
Decl: attestDecl,
@@ -191,12 +192,12 @@ func RegoFunctions(resolver oci.AttestationResolver) []*tester.Builtin {
Memoize: true,
Nondeterministic: attestDecl.Nondeterministic,
},
handleErrors1(fetchIntotoAttestations(resolver))),
handleErrors1(fetchInTotoAttestations(resolver))),
},
}
}
func fetchIntotoAttestations(resolver oci.AttestationResolver) rego.Builtin1 {
func fetchInTotoAttestations(resolver oci.AttestationResolver) rego.Builtin1 {
return func(rCtx rego.BuiltinContext, predicateTypeTerm *ast.Term) (*ast.Term, error) {
predicateTypeStr, ok := predicateTypeTerm.Value.(ast.String)
if !ok {
@@ -226,7 +227,7 @@ func fetchIntotoAttestations(resolver oci.AttestationResolver) rego.Builtin1 {
}
}
func verifyIntotoEnvelope(rCtx rego.BuiltinContext, envTerm, optsTerm *ast.Term) (*ast.Term, error) {
func verifyInTotoEnvelope(rCtx *rego.BuiltinContext, envTerm, optsTerm *ast.Term) (*ast.Term, error) {
env := new(att.Envelope)
opts := new(att.VerifyOptions)
err := ast.As(envTerm.Value, env)

View File

@@ -26,29 +26,29 @@ type Result struct {
Summary Summary `json:"summary"`
}
type PolicyOptions struct {
TufClient tuf.TUFClient
type Options struct {
TUFClient tuf.Downloader
LocalTargetsDir string
LocalPolicyDir string
PolicyId string
PolicyID string
ReferrersRepo string
AttestationStyle config.AttestationStyle
}
type Policy struct {
InputFiles []*PolicyFile
InputFiles []*File
Query string
Mapping *config.PolicyMapping
ResolvedName string
}
type PolicyInput struct {
type Input struct {
Digest string `json:"digest"`
Purl string `json:"purl"`
PURL string `json:"purl"`
IsCanonical bool `json:"isCanonical"`
}
type PolicyFile struct {
type File struct {
Path string
Content []byte
}

View File

@@ -9,18 +9,18 @@ import (
awssigner "github.com/sigstore/sigstore/pkg/signature/kms/aws"
)
// using AWS KMS
func GetAWSSigner(ctx context.Context, keyArn string, region string) (dsse.SignerVerifier, error) {
keypath := fmt.Sprintf("awskms:///%s", keyArn)
sv, err := awssigner.LoadSignerVerifier(ctx, keypath, config.WithRegion(region))
// using AWS KMS.
func GetAWSSigner(ctx context.Context, keyARN string, region string) (dsse.SignerVerifier, error) {
keyPath := fmt.Sprintf("awskms:///%s", keyARN)
sv, err := awssigner.LoadSignerVerifier(ctx, keyPath, config.WithRegion(region))
if err != nil {
return nil, fmt.Errorf("error loading aws signer verifier: %w", err)
}
cs, _, err := sv.CryptoSigner(context.Background(), func(err error) {})
cs, _, err := sv.CryptoSigner(context.Background(), func(_ error) {})
if err != nil {
return nil, fmt.Errorf("error getting aws crypto signer: %w", err)
}
signer := &ECDSA256_SignerVerifier{
signer := &ECDSA256SignerVerifier{
Signer: cs,
}
return signer, nil

View File

@@ -14,12 +14,12 @@ import (
"github.com/secure-systems-lab/go-securesystemslib/dsse"
)
type ECDSA256_SignerVerifier struct {
type ECDSA256SignerVerifier struct {
crypto.Signer
}
// implement keyid function
func (s *ECDSA256_SignerVerifier) KeyID() (string, error) {
// implement keyid function.
func (s *ECDSA256SignerVerifier) KeyID() (string, error) {
keyid, err := KeyID(s.Signer.Public())
if err != nil {
return "", fmt.Errorf("error getting keyid: %w", err)
@@ -27,15 +27,15 @@ func (s *ECDSA256_SignerVerifier) KeyID() (string, error) {
return keyid, nil
}
func (s *ECDSA256_SignerVerifier) Public() crypto.PublicKey {
func (s *ECDSA256SignerVerifier) Public() crypto.PublicKey {
return s.Signer.Public()
}
func (s *ECDSA256_SignerVerifier) Sign(ctx context.Context, data []byte) ([]byte, error) {
func (s *ECDSA256SignerVerifier) Sign(_ context.Context, data []byte) ([]byte, error) {
return s.Signer.Sign(rand.Reader, data, crypto.SHA256)
}
func (s *ECDSA256_SignerVerifier) Verify(ctx context.Context, data []byte, sig []byte) error {
func (s *ECDSA256SignerVerifier) Verify(_ context.Context, data []byte, sig []byte) error {
pub, ok := s.Signer.Public().(*ecdsa.PublicKey)
if !ok {
return fmt.Errorf("public key is not ecdsa")
@@ -52,7 +52,7 @@ func LoadKeyPair(priv []byte) (dsse.SignerVerifier, error) {
if err != nil {
return nil, err
}
return &ECDSA256_SignerVerifier{
return &ECDSA256SignerVerifier{
Signer: privateKey,
}, nil
}
@@ -78,7 +78,7 @@ func GenKeyPair() (dsse.SignerVerifier, error) {
if err != nil {
return nil, err
}
return &ECDSA256_SignerVerifier{
return &ECDSA256SignerVerifier{
Signer: signer,
}, nil
}

View File

@@ -10,18 +10,18 @@ import (
)
// using GCP KMS
// reference should be in the format projects/[PROJECT_ID]/locations/[LOCATION]/keyRings/[KEY_RING]/cryptoKeys/[KEY]/cryptoKeyVersions/[VERSION]
// reference should be in the format projects/[PROJECT_ID]/locations/[LOCATION]/keyRings/[KEY_RING]/cryptoKeys/[KEY]/cryptoKeyVersions/[VERSION].
func GetGCPSigner(ctx context.Context, reference string, opts ...option.ClientOption) (dsse.SignerVerifier, error) {
reference = fmt.Sprintf("gcpkms://%s", reference)
sv, err := gcpsigner.LoadSignerVerifier(ctx, reference, opts...)
if err != nil {
return nil, fmt.Errorf("error loading gcp signer verifier: %w", err)
}
cs, _, err := sv.CryptoSigner(ctx, func(err error) {})
cs, _, err := sv.CryptoSigner(ctx, func(_ error) {})
if err != nil {
return nil, fmt.Errorf("error getting gcp crypto signer: %w", err)
}
signer := &ECDSA256_SignerVerifier{
signer := &ECDSA256SignerVerifier{
Signer: cs,
}
return signer, nil

View File

@@ -22,7 +22,7 @@ k2s4SO3XbQ2GG2alm289SUUpmBAuVxvT8muYQ8HC/QzixzyTACTXsBDjQg==
func TestGCPKMS_Signer(t *testing.T) {
// create a new signer
ctx := context.Background()
ctx := context.TODO()
ref := "projects/attest-kms-test/locations/us-west1/keyRings/attest-kms-test/cryptoKeys/test-signing-key/cryptoKeyVersions/1"
signer, err := GetGCPSigner(ctx, ref)
require.NoError(t, err)
@@ -37,7 +37,7 @@ func TestGCPKMS_Signer(t *testing.T) {
keyId, err := signer.KeyID()
require.NoError(t, err)
assert.NotEmpty(t, keyId)
publicKey, err := Parse([]byte(publicKeyPEM))
publicKey, err := ParsePublicKey([]byte(publicKeyPEM))
require.NoError(t, err)
// verify payload ecdsa signature
ok := ecdsa.VerifyASN1(publicKey, hash, sig)

View File

@@ -11,7 +11,7 @@ import (
func KeyID(pubKey crypto.PublicKey) (string, error) {
pub, err := x509.MarshalPKIXPublicKey(pubKey)
if err != nil {
return "", fmt.Errorf("error marshalling public key: %w", err)
return "", fmt.Errorf("error marshaling public key: %w", err)
}
return util.SHA256Hex(pub), nil
}

View File

@@ -9,7 +9,7 @@ import (
const pemType = "PUBLIC KEY"
func Parse(pubkeyBytes []byte) (*ecdsa.PublicKey, error) {
func ParsePublicKey(pubkeyBytes []byte) (*ecdsa.PublicKey, error) {
p, _ := pem.Decode(pubkeyBytes)
if p == nil {
return nil, fmt.Errorf("pubkey file does not contain any PEM data")
@@ -29,7 +29,7 @@ func Parse(pubkeyBytes []byte) (*ecdsa.PublicKey, error) {
return ecdsaPubKey, nil
}
func ToPEM(ecdsaPubKey *ecdsa.PublicKey) ([]byte, error) {
func ConvertToPEM(ecdsaPubKey *ecdsa.PublicKey) ([]byte, error) {
pubKeyBytes, err := x509.MarshalPKIXPublicKey(ecdsaPubKey)
if err != nil {
return nil, fmt.Errorf("error failed to marshal public key: %w", err)

View File

@@ -10,7 +10,7 @@ import (
)
const (
USE_MOCK_TL = true
UseMockTL = true
TestEntry = `{"body":"eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiI5Zjg2ZDA4MTg4NGM3ZDY1OWEyZmVhYTBjNTVhZDAxNWEzYmY0ZjFiMmIwYjgyMmNkMTVkNmMxNWIwZjAwYTA4In19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FUUNJQUlyVUZGUzBIYmNzZjc5L08yajVXdHl2R2Vvd1NVSXpZcDlBM2IwWnREVUFpQVQxZU42ZjFyVmVWa011REFlN3dxWkJ2bE5LY2VsajNVVDNmaWhyQjZSY2c9PSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVSlZla05DSzJGQlJFRm5SVU5CWjBWQ1RVRnZSME5EY1VkVFRUUTVRa0ZOUTAxQk9IaEVWRUZNUW1kT1ZrSkJUVlJDU0ZKc1l6TlJkMGhvWTA0S1RXcE5lRTFxU1ROTlZHdDVUWHBWTlZkb1kwNU5hbEY0VFdwSk1rMVVhM2xOZWxVMVYycEJVRTFSTUhkRGQxbEVWbEZSUkVWM1VqQmFXRTR3VFVacmR3cEZkMWxJUzI5YVNYcHFNRU5CVVZsSlMyOWFTWHBxTUVSQlVXTkVVV2RCUlVRMFZpdFNSV2g0SzJGeFYwZzNlV3hOVFVSSVlXaE9UVzVOVEZOUFNsQXZDamxyUVcwNWJIQXJNMjF4V1ZSQmFGVlNjbUUyVDBRMVVYZzRXbUprSzJWMVVIbFFhemw1SzNjdloxZEhSRUk1ZW00dlNXd3hTMDVIVFVWUmQwUm5XVVFLVmxJd1VFRlJTQzlDUVZGRVFXZGxRVTFDVFVkQk1WVmtTbEZSVFUxQmIwZERRM05IUVZGVlJrSjNUVVJOUVhkSFFURlZaRVYzUlVJdmQxRkRUVUZCZHdwRWQxbEVWbEl3VWtKQlozZENiMGxGWkVkV2VtUkVRVXRDWjJkeGFHdHFUMUJSVVVSQlowNUtRVVJDUjBGcFJVRTNOMjFFTDFSbVJtRlJVemxrWlhRMENqbFhaRk41YURKT1VTOUZiMVJtYVVGdFFtaHVWblpEVTNSUVowTkpVVU1yZDNSdllpOU9iMUp4T0c5cU4wZDNibTVKYUZKVGRDOVJNbmtyVXpoUkwzSUthRkpVYW5GaE9HZExRVDA5Q2kwdExTMHRSVTVFSUVORlVsUkpSa2xEUVZSRkxTMHRMUzBLIn19fX0=","integratedTime":1703705039,"logID":"c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d","logIndex":59674396,"verification":{"inclusionProof":{"checkpoint":"rekor.sigstore.dev - 2605736670972794746\n55510966\nJCi1O53Xmdi9lXnui4Q5SQ+MJSMnWr1Bxn+Q2Qf22tU=\nTimestamp: 1703705040158839214\n\n— rekor.sigstore.dev wNI9ajBFAiAXgtjFDVqCSgiSP04TQzELrz4+EyBwyYVL2EEULTCy0AIhAI9peLU76ZUD1tvU8qvzBJBo77IYD1rc+A1MPc35AeVK\n","hashes":["fb77ee213b48f4b18dc81c6e634c570abf99b257713561f174f2e0f4c039af67","6cb113bbefadecbbb8b89b1c08232438a6125071790b6a062cff8c1ccfdcb91e","6fbe1424e264e4590ca502d671b7a036c87f7a90d1f57534b98eb781144160bf","077b606720a6478200f6c3ed08a68e9b01b1cae192cb120888ddcc95521601bd","b6f8e8bc21ae0cde82b92422a4b4f37b28a43185821e468a4e65b6c79ed8f5b7","89332533fac54e9bc68c7353c42f6ebb9fe38039f67910332ff95082072068d4","0814d6f707a75fb3334bab14ab5466bd8b9a64ae7be7cd4d53a428c64932bc66","e883e826f10329c63a4a2ed21156037a050df43b9d74079296beac6968ed4150","d79230703257b7e4a8a61b032b6980d1a0bdbc7ae96ca838b525b3751785fe48","2f4a77e5288462cd3b75084d37f1502dcbe0943d18dd95cb247fc1ebbabc0aad","38562c253d3536d0d00e3547c880b6b0251a25ac69605b50c9eaa1a27186cc7a","9dea192350ff8b3c0f5ccda38261cb38ebd61869281c3928912332d1144e0a04","2c4d25ba59aa573ab2c79c2d3cd9e1d74789b10632432724d63112ce50b44874","98c486feb5d87092a78a46c4b5be04868654900affc2e86ffb20074dc73a883a","6969c49bd73f19bf28a5eaeabd331ddd60502defb2cd3d96e17b741c80adec6c"],"logIndex":55510965,"rootHash":"2428b53b9dd799d8bd9579ee8b8439490f8c2523275abd41c67f90d907f6dad5","treeSize":55510966},"signedEntryTimestamp":"MEUCIQCG9PRI8PcvtJyE9pbcculZipze6NEWR1Nk8EYocto3BwIgYu5gqgjW80HMjSjUxUNJLp0wlVTesnJCeByUBySc59w="}}`
)
@@ -26,10 +26,10 @@ func GetMockTL() TL {
}
return &MockTL{
UploadLogEntryFunc: func(ctx context.Context, subject string, payload []byte, signature []byte, signer dsse.SignerVerifier) ([]byte, error) {
UploadLogEntryFunc: func(_ context.Context, _ string, _ []byte, _ []byte, _ dsse.SignerVerifier) ([]byte, error) {
return []byte(TestEntry), nil
},
VerifyLogEntryFunc: func(ctx context.Context, entryBytes []byte) (time.Time, error) {
VerifyLogEntryFunc: func(_ context.Context, entryBytes []byte) (time.Time, error) {
// return the integrated time in the log entry without any checking
le, err := unmarshalEntry(entryBytes)
if err != nil {
@@ -40,7 +40,7 @@ func GetMockTL() TL {
}
return time.Unix(*le.IntegratedTime, 0), nil
},
VerifyEntryPayloadFunc: func(entryBytes, payload, pkToken []byte) error {
VerifyEntryPayloadFunc: func(_, _, _ []byte) error {
return nil
},
UnmarshalEntryFunc: func(entry []byte) (any, error) {

View File

@@ -1,6 +1,7 @@
package tlog
import (
"bytes"
"context"
"crypto/rand"
"crypto/sha256"
@@ -31,23 +32,23 @@ const (
type tlCtxKeyType struct{}
var TlCtxKey tlCtxKeyType
var TLCtxKey tlCtxKeyType
// sets TL in context
// sets TL in context.
func WithTL(ctx context.Context, tl TL) context.Context {
return context.WithValue(ctx, TlCtxKey, tl)
return context.WithValue(ctx, TLCtxKey, tl)
}
// gets TL from context, defaults to Rekor TL if not set
// gets TL from context, defaults to Rekor TL if not set.
func GetTL(ctx context.Context) TL {
t, ok := ctx.Value(TlCtxKey).(TL)
t, ok := ctx.Value(TLCtxKey).(TL)
if !ok {
t = &RekorTL{}
}
return t
}
type TlPayload struct {
type TLPayload struct {
Algorithm string
Hash string
Signature string
@@ -98,7 +99,7 @@ func (tl *MockTL) UnmarshalEntry(entryBytes []byte) (any, error) {
type RekorTL struct{}
// UploadLogEntry submits a PK token signature to the transparency log
// UploadLogEntry submits a PK token signature to the transparency log.
func (tl *RekorTL) UploadLogEntry(ctx context.Context, subject string, payload, signature []byte, signer dsse.SignerVerifier) ([]byte, error) {
// generate self-signed x509 cert
pubCert, err := CreateX509Cert(subject, signer)
@@ -121,12 +122,12 @@ func (tl *RekorTL) UploadLogEntry(ctx context.Context, subject string, payload,
}
entryBytes, err := entry.MarshalBinary()
if err != nil {
return nil, fmt.Errorf("error marshalling TL entry: %w", err)
return nil, fmt.Errorf("error marshaling TL entry: %w", err)
}
return entryBytes, nil
}
// VerifyLogEntry verifies a transparency log entry
// VerifyLogEntry verifies a transparency log entry.
func (tl *RekorTL) VerifyLogEntry(ctx context.Context, entryBytes []byte) (time.Time, error) {
zeroTime := time.Time{}
entry, err := tl.UnmarshalEntry(entryBytes)
@@ -157,12 +158,12 @@ func (tl *RekorTL) VerifyLogEntry(ctx context.Context, entryBytes []byte) (time.
return integratedTime, nil
}
// CreateX509Cert generates a self-signed x509 cert for TL submission
// CreateX509Cert generates a self-signed x509 cert for TL submission.
func CreateX509Cert(subject string, signer dsse.SignerVerifier) ([]byte, error) {
// encode ephemeral public key
ecPub, err := x509.MarshalPKIXPublicKey(signer.Public())
if err != nil {
return nil, fmt.Errorf("error marshalling public key: %w", err)
return nil, fmt.Errorf("error marshaling public key: %w", err)
}
template := x509.Certificate{
@@ -180,7 +181,7 @@ func CreateX509Cert(subject string, signer dsse.SignerVerifier) ([]byte, error)
// dsse.SignerVerifier doesn't implement cypto.Signer exactly
csigner, ok := signer.(*signerverifier.ECDSA256_SignerVerifier)
csigner, ok := signer.(*signerverifier.ECDSA256SignerVerifier)
if !ok {
return nil, fmt.Errorf("expected signer to be of type *signerverifier.ECDSA_SignerVerifier, got %T", signer)
}
@@ -193,7 +194,7 @@ func CreateX509Cert(subject string, signer dsse.SignerVerifier) ([]byte, error)
return pem.EncodeToMemory(certBlock), nil
}
// VerifyEntryPayload checks that the TL entry payload matches envelope payload
// VerifyEntryPayload checks that the TL entry payload matches envelope payload.
func (tl *RekorTL) VerifyEntryPayload(entryBytes, payload, publicKey []byte) error {
entry, err := tl.UnmarshalEntry(entryBytes)
if err != nil {
@@ -228,7 +229,7 @@ func (tl *RekorTL) VerifyEntryPayload(entryBytes, payload, publicKey []byte) err
if err != nil {
return fmt.Errorf("failed to parse certificate: %w", err)
}
if string(result.RawSubjectPublicKeyInfo) != string(publicKey) {
if !bytes.Equal(result.RawSubjectPublicKeyInfo, publicKey) {
return fmt.Errorf("error payload and tl entry public key mismatch")
}
return nil
@@ -243,9 +244,9 @@ func (tl *RekorTL) UnmarshalEntry(entry []byte) (any, error) {
return le, nil
}
func extractHashedRekord(Body string) (*TlPayload, error) {
sig := new(TlPayload)
pe, err := models.UnmarshalProposedEntry(base64.NewDecoder(base64.StdEncoding, strings.NewReader(Body)), runtime.JSONConsumer())
func extractHashedRekord(body string) (*TLPayload, error) {
sig := new(TLPayload)
pe, err := models.UnmarshalProposedEntry(base64.NewDecoder(base64.StdEncoding, strings.NewReader(body)), runtime.JSONConsumer())
if err != nil {
return nil, err
}

View File

@@ -14,7 +14,7 @@ import (
)
const (
// test artifacts
// test artifacts.
TestPayload = "test"
TestPublicKey = "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAED4V+REhx+aqWH7ylMMDHahNMnMLS\nOJP/9kAm9lp+3mqYTAhURra6OD5Qx8Zbd+euPyPk9y+w/gWGDB9zn/Il1A==\n-----END PUBLIC KEY-----"
)
@@ -53,15 +53,15 @@ func TestUploadAndVerifyLogEntry(t *testing.T) {
assert.NoError(t, err)
var tl TL
if USE_MOCK_TL {
if UseMockTL {
tl = &MockTL{
UploadLogEntryFunc: func(ctx context.Context, subject string, payload []byte, signature []byte, signer dsse.SignerVerifier) ([]byte, error) {
UploadLogEntryFunc: func(_ context.Context, _ string, _ []byte, _ []byte, _ dsse.SignerVerifier) ([]byte, error) {
return []byte(TestEntry), nil
},
VerifyLogEntryFunc: func(ctx context.Context, entryBytes []byte) (time.Time, error) {
VerifyLogEntryFunc: func(_ context.Context, _ []byte) (time.Time, error) {
return time.Time{}, nil
},
VerifyEntryPayloadFunc: func(entryBytes, payload, publicKey []byte) error {
VerifyEntryPayloadFunc: func(_, _, _ []byte) error {
return nil
},
}

View File

@@ -9,7 +9,7 @@ import (
"github.com/theupdateframework/go-tuf/v2/metadata"
)
func ExampleNewTufClient_registry() {
func ExampleNewClient_registry() {
// create a tuf client
home, err := os.UserHomeDir()
if err != nil {
@@ -21,7 +21,7 @@ func ExampleNewTufClient_registry() {
metadataURI := "registry-1.docker.io/docker/tuf-metadata:latest"
targetsURI := "registry-1.docker.io/docker/tuf-targets"
registryClient, err := tuf.NewTufClient(embed.RootStaging.Data, tufOutputPath, metadataURI, targetsURI, tuf.NewMockVersionChecker())
registryClient, err := tuf.NewClient(embed.RootStaging.Data, tufOutputPath, metadataURI, targetsURI, tuf.NewMockVersionChecker())
if err != nil {
panic(err)
}

View File

@@ -6,25 +6,25 @@ import (
"path/filepath"
)
type mockTufClient struct {
type MockTufClient struct {
srcPath string
dstPath string
}
func NewMockTufClient(srcPath string, dstPath string) *mockTufClient {
func NewMockTufClient(srcPath string, dstPath string) *MockTufClient {
if srcPath == "" {
panic("srcPath must be set")
}
if dstPath == "" {
panic("dstPath must be set")
}
return &mockTufClient{
return &MockTufClient{
srcPath: srcPath,
dstPath: dstPath,
}
}
func (dc *mockTufClient) DownloadTarget(target string, filePath string) (actualFilePath string, data []byte, err error) {
func (dc *MockTufClient) DownloadTarget(target string, filePath string) (actualFilePath string, data []byte, err error) {
src, err := os.Open(filepath.Join(dc.srcPath, target))
if err != nil {
return "", nil, err
@@ -38,7 +38,7 @@ func (dc *mockTufClient) DownloadTarget(target string, filePath string) (actualF
dstFilePath = filePath
}
err = os.MkdirAll(filepath.Dir(dstFilePath), 0755)
err = os.MkdirAll(filepath.Dir(dstFilePath), os.ModePerm)
if err != nil {
return "", nil, err
}
@@ -59,14 +59,14 @@ func (dc *mockTufClient) DownloadTarget(target string, filePath string) (actualF
return dstFilePath, b, nil
}
type mockVersionChecker struct {
type MockVersionChecker struct {
err error
}
func NewMockVersionChecker() *mockVersionChecker {
return &mockVersionChecker{}
func NewMockVersionChecker() *MockVersionChecker {
return &MockVersionChecker{}
}
func (vc *mockVersionChecker) CheckVersion(client TUFClient) error {
func (vc *MockVersionChecker) CheckVersion(_ Downloader) error {
return vc.err
}

View File

@@ -19,14 +19,14 @@ import (
)
const (
TufFileNameAnnotation = "tuf.io/filename"
TUFFileNameAnnotation = "tuf.io/filename"
)
type TufRole string
type Role string
var TufRoles = []TufRole{metadata.ROOT, metadata.SNAPSHOT, metadata.TARGETS, metadata.TIMESTAMP}
var Roles = []Role{metadata.ROOT, metadata.SNAPSHOT, metadata.TARGETS, metadata.TIMESTAMP}
// RegistryFetcher implements Fetcher
// RegistryFetcher implements Fetcher.
type RegistryFetcher struct {
httpUserAgent string
metadataRepo string
@@ -46,13 +46,13 @@ func NewImageCache() *ImageCache {
}
}
// Get image from cache
// Get image from cache.
func (c *ImageCache) Get(imgRef string) ([]byte, bool) {
img, found := c.cache[imgRef]
return img, found
}
// Add image to cache
// Add image to cache.
func (c *ImageCache) Put(imgRef string, img []byte) {
c.cache[imgRef] = img
}
@@ -113,7 +113,7 @@ func (d *RegistryFetcher) DownloadFile(urlPath string, maxLength int64, timeout
}
}
// getManifest returns the manifest for an image or index
// getManifest returns the manifest for an image or index.
func (d *RegistryFetcher) getManifest(ref string) ([]byte, error) {
// Pull image manifest
var err error
@@ -135,7 +135,7 @@ func (d *RegistryFetcher) getManifest(ref string) ([]byte, error) {
return mf, nil
}
// pullFileLayer pulls a layer for an image or index and returns its data
// pullFileLayer pulls a layer for an image or index and returns its data.
func (d *RegistryFetcher) pullFileLayer(ref string, maxLength int64) ([]byte, error) {
var data []byte
var found bool
@@ -159,7 +159,7 @@ func (d *RegistryFetcher) pullFileLayer(ref string, maxLength int64) ([]byte, er
return data, nil
}
// getDataFromLayer returns the data from a layer in an image
// getDataFromLayer returns the data from a layer in an image.
func getDataFromLayer(fileLayer v1.Layer, maxLength int64) ([]byte, error) {
length, err := fileLayer.Size()
if err != nil {
@@ -185,7 +185,7 @@ func getDataFromLayer(fileLayer v1.Layer, maxLength int64) ([]byte, error) {
return data, nil
}
// parseImgRef maintains the Fetcher interface by parsing a URL path to an image reference and file name
// parseImgRef maintains the Fetcher interface by parsing a URL path to an image reference and file name.
func (d *RegistryFetcher) parseImgRef(urlPath string) (imgRef, fileName string, err error) {
// Check if repo is target or metadata
if strings.Contains(urlPath, d.targetsRepo) {
@@ -208,12 +208,11 @@ func (d *RegistryFetcher) parseImgRef(urlPath string) (imgRef, fileName string,
return fmt.Sprintf("%s:%s", d.metadataRepo, role), fileName, nil
}
return fmt.Sprintf("%s:%s", d.metadataRepo, d.metadataTag), fileName, nil
} else {
return "", "", fmt.Errorf("urlPath: %s must be in metadata or targets repo", urlPath)
}
return "", "", fmt.Errorf("urlPath: %s must be in metadata or targets repo", urlPath)
}
// findFileInManifest searches the image or index manifest for a file with the given name and returns its digest
// findFileInManifest searches the image or index manifest for a file with the given name and returns its digest.
func (d *RegistryFetcher) findFileInManifest(mf []byte, name string) (*v1.Hash, error) {
var index bool
@@ -226,20 +225,21 @@ func (d *RegistryFetcher) findFileInManifest(mf []byte, name string) (*v1.Hash,
// determine image or index manifest
var layers []Layer
if l.MediaType == string(types.OCIImageIndex) {
switch l.MediaType {
case string(types.OCIImageIndex):
layers = l.Manifests
index = true
} else if l.MediaType == string(types.OCIManifestSchema1) {
case string(types.OCIManifestSchema1):
layers = l.Layers
index = false
} else {
default:
return nil, fmt.Errorf("invalid manifest media type: %s", l.MediaType)
}
// find annotation with file name
var digest string
for _, layer := range layers {
if layer.Annotations[TufFileNameAnnotation] == name {
if layer.Annotations[TUFFileNameAnnotation] == name {
digest = layer.Digest
break
}
@@ -267,7 +267,7 @@ func (d *RegistryFetcher) findFileInManifest(mf []byte, name string) (*v1.Hash,
return hash, nil
}
// transportWithTimeout returns a http.RoundTripper with a specified timeout
// transportWithTimeout returns a http.RoundTripper with a specified timeout.
func transportWithTimeout(timeout time.Duration) http.RoundTripper {
// transport is based on go-containerregistry remote.DefaultTransport
// with modifications to include a specified timeout
@@ -286,9 +286,9 @@ func transportWithTimeout(timeout time.Duration) http.RoundTripper {
}
}
// isDelegatedRole returns true if the role is a delegated role
// isDelegatedRole returns true if the role is a delegated role.
func isDelegatedRole(role string) bool {
for _, r := range TufRoles {
for _, r := range Roles {
if role == string(r) {
return false // role is not a delegated role
}
@@ -296,7 +296,7 @@ func isDelegatedRole(role string) bool {
return true // role is a delegated role
}
// roleFromConsistentName returns the role name from a consistent snapshot file name
// roleFromConsistentName returns the role name from a consistent snapshot file name.
func roleFromConsistentName(filename string) string {
name := strings.TrimSuffix(filename, ".json")
role := strings.Split(name, ".")

View File

@@ -22,7 +22,6 @@ import (
"github.com/google/go-containerregistry/pkg/v1/static"
"github.com/google/go-containerregistry/pkg/v1/types"
"github.com/stretchr/testify/assert"
"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/modules/registry"
"github.com/theupdateframework/go-tuf/v2/metadata"
"github.com/theupdateframework/go-tuf/v2/metadata/config"
@@ -31,6 +30,8 @@ import (
const (
tufTargetMediaType = "application/vnd.tuf.target"
testRole = "test-role"
tufMetadataRepo = "tuf-metadata"
)
func TestRegistryFetcher(t *testing.T) {
@@ -41,13 +42,13 @@ func TestRegistryFetcher(t *testing.T) {
t.Fatalf("failed to terminate container: %s", err) // nolint:gocritic
}
}()
LoadRegistryTestData(t, regAddr, OciTufTestDataPath)
LoadRegistryTestData(t, regAddr, OCITUFTestDataPath)
metadataRepo := regAddr.Host + "/tuf-metadata"
metadataImgTag := "latest"
metadataImgTag := LatestTag
targetsRepo := regAddr.Host + "/tuf-targets"
targetFile := "test.txt"
delegatedRole := "test-role"
delegatedRole := testRole
dir := CreateTempDir(t, "", "tuf_temp")
delegatedDir := CreateTempDir(t, dir, delegatedRole)
delegatedTargetFile := fmt.Sprintf("%s/%s", delegatedRole, targetFile)
@@ -127,11 +128,11 @@ func TestFindFileInManifest(t *testing.T) {
img = mutate.ConfigMediaType(img, types.OCIConfigJSON)
// add test layer
name := strings.Join([]string{hash.Hex, file}, ".")
ann := map[string]string{TufFileNameAnnotation: name}
ann := map[string]string{TUFFileNameAnnotation: name}
layer := mutate.Addendum{Layer: static.NewLayer(data, tufTargetMediaType), Annotations: ann}
img, err := mutate.Append(img, layer)
assert.NoError(t, err)
image_manifest, err := img.RawManifest()
imageManifest, err := img.RawManifest()
assert.NoError(t, err)
// make test index manifest
@@ -142,11 +143,11 @@ func TestFindFileInManifest(t *testing.T) {
Add: img,
Descriptor: v1.Descriptor{
Annotations: map[string]string{
TufFileNameAnnotation: name,
TUFFileNameAnnotation: name,
},
},
})
index_manifest, err := idx.RawManifest()
indexManifest, err := idx.RawManifest()
assert.NoError(t, err)
// cache image layer
targetsRepo := "test/tuf-targets"
@@ -156,7 +157,7 @@ func TestFindFileInManifest(t *testing.T) {
}
imgHash, err := img.Digest()
assert.NoError(t, err)
d.cache.Put(fmt.Sprintf("%s@%s", targetsRepo, imgHash.String()), image_manifest)
d.cache.Put(fmt.Sprintf("%s@%s", targetsRepo, imgHash.String()), imageManifest)
testCases := []struct {
name string
@@ -164,9 +165,9 @@ func TestFindFileInManifest(t *testing.T) {
file string
expected string
}{
{"consistent filename image", image_manifest, fmt.Sprintf("%s.%s", hash.Hex, file), hash.Hex},
{"filename image", image_manifest, file, ""},
{"consistent filename index", index_manifest, fmt.Sprintf("%s.%s", hash.Hex, file), hash.Hex},
{"consistent filename image", imageManifest, fmt.Sprintf("%s.%s", hash.Hex, file), hash.Hex},
{"filename image", imageManifest, file, ""},
{"consistent filename index", indexManifest, fmt.Sprintf("%s.%s", hash.Hex, file), hash.Hex},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
@@ -183,9 +184,9 @@ func TestFindFileInManifest(t *testing.T) {
func TestParseImgRef(t *testing.T) {
metadataRepo := "test/tuf-metadata"
metadataTag := "latest"
metadataTag := LatestTag
targetsRepo := "test/tuf-targets"
delegatedRole := "test-role"
delegatedRole := testRole
testCases := []struct {
name string
ref string
@@ -201,7 +202,7 @@ func TestParseImgRef(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
d := &RegistryFetcher{
metadataRepo: metadataRepo,
metadataTag: "latest",
metadataTag: LatestTag,
targetsRepo: targetsRepo,
}
imgRef, file, err := d.parseImgRef(tc.ref)
@@ -247,7 +248,7 @@ func TestPullFileLayer(t *testing.T) {
}()
// make test layer
repo := "tuf-metadata"
repo := tufMetadataRepo
data := []byte("test")
testLayer := static.NewLayer(data, tufTargetMediaType)
hash, err := testLayer.Digest()
@@ -304,7 +305,7 @@ func TestGetManifest(t *testing.T) {
}()
// make test manifest
repo := "tuf-metadata"
repo := tufMetadataRepo
img := empty.Image
img = mutate.MediaType(img, types.OCIManifestSchema1)
img = mutate.ConfigMediaType(img, types.OCIConfigJSON)
@@ -340,9 +341,9 @@ func TestGetManifest(t *testing.T) {
}
}
// RunTestRegistry starts a registry testcontainer for TUF on OCI testdata
// RunTestRegistry starts a registry testcontainer for TUF on OCI testdata.
func RunTestRegistry(t *testing.T) (*registry.RegistryContainer, *url.URL) {
registryContainer, err := registry.RunContainer(context.Background(), testcontainers.WithImage("registry:2.8.3"))
registryContainer, err := registry.Run(context.Background(), "registry:2.8.3")
if err != nil {
t.Fatalf("failed to start container: %s", err)
}
@@ -360,22 +361,21 @@ func RunTestRegistry(t *testing.T) (*registry.RegistryContainer, *url.URL) {
return registryContainer, addr
}
// LoadRegistryTestData pushes TUF metadata and targets to an OCI registry
// LoadRegistryTestData pushes TUF metadata and targets to an OCI registry.
func LoadRegistryTestData(t *testing.T, registry *url.URL, path string) {
// push tuf metadata and targets to local registry
METADATA_REPO := "tuf-metadata"
METADATA_TAG := "latest"
TARGETS_REPO := "tuf-targets"
DELEGATED_ROLE := "test-role"
MetadataRepo := tufMetadataRepo
TargetsRepo := "tuf-targets"
DelegatedRole := testRole
// push top-level metadata -> metadata:latest
err := LoadMetadata(filepath.Join(path, "metadata"), registry.Host, METADATA_REPO, METADATA_TAG)
err := LoadMetadata(filepath.Join(path, "metadata"), registry.Host, MetadataRepo, LatestTag)
if err != nil {
t.Fatal(err)
}
// push delegated metadata -> metadata:<DELEGATED_ROLE>
err = LoadMetadata(filepath.Join(path, "metadata", DELEGATED_ROLE), registry.Host, METADATA_REPO, DELEGATED_ROLE)
err = LoadMetadata(filepath.Join(path, "metadata", DelegatedRole), registry.Host, MetadataRepo, DelegatedRole)
if err != nil {
t.Fatal(err)
}
@@ -393,7 +393,7 @@ func LoadRegistryTestData(t *testing.T, registry *url.URL, path string) {
if err != nil {
t.Fatal(err)
}
ref, err := name.ParseReference(fmt.Sprintf("%s/%s:%s", registry.Host, TARGETS_REPO, dir.Name()))
ref, err := name.ParseReference(fmt.Sprintf("%s/%s:%s", registry.Host, TargetsRepo, dir.Name()))
if err != nil {
t.Fatal(err)
}
@@ -401,7 +401,8 @@ func LoadRegistryTestData(t *testing.T, registry *url.URL, path string) {
if err != nil {
t.Fatal(err)
}
if len(mf.Manifests) == 1 {
switch len(mf.Manifests) {
case 1:
// top-level target
img, err := tIdx.Image(mf.Manifests[0].Digest)
if err != nil {
@@ -411,19 +412,19 @@ func LoadRegistryTestData(t *testing.T, registry *url.URL, path string) {
if err != nil {
t.Fatal(err)
}
} else if len(mf.Manifests) > 1 {
case 2:
// delegated target
err = remote.WriteIndex(ref, tIdx, oci.MultiKeychainOption())
if err != nil {
t.Fatal(err)
}
} else {
default:
t.Fatal("no manifests found")
}
}
}
// LoadMetadata loads TUF metadata from a local path and pushes to a registry
// LoadMetadata loads TUF metadata from a local path and pushes to a registry.
func LoadMetadata(path, host, repo, tag string) error {
mIdx, err := layout.ImageIndexFromPath(path)
if err != nil {

View File

@@ -20,56 +20,56 @@ import (
"github.com/theupdateframework/go-tuf/v2/metadata/updater"
)
type TufSource string
type Source string
const (
HttpSource TufSource = "http"
OciSource TufSource = "oci"
HTTPSource Source = "http"
OCISource Source = "oci"
LatestTag string = "latest"
)
var (
DockerTufRootProd = embed.RootProd
DockerTufRootStaging = embed.RootStaging
DockerTufRootDev = embed.RootDev
DockerTufRootDefault = embed.RootDefault
DockerTUFRootProd = embed.RootProd
DockerTUFRootStaging = embed.RootStaging
DockerTUFRootDev = embed.RootDev
DockerTUFRootDefault = embed.RootDefault
)
type TUFClient interface {
type Downloader interface {
DownloadTarget(target, filePath string) (actualFilePath string, data []byte, err error)
}
type TufClient struct {
type Client struct {
updater *updater.Updater
cfg *config.UpdaterConfig
}
// NewTufClient creates a new TUF client
func NewTufClient(initialRoot []byte, tufPath, metadataSource, targetsSource string, versionChecker VersionChecker) (*TufClient, error) {
var tufSource TufSource
// NewClient creates a new TUF client.
func NewClient(initialRoot []byte, tufPath, metadataSource, targetsSource string, versionChecker VersionChecker) (*Client, error) {
var tufSource Source
if strings.HasPrefix(metadataSource, "https://") || strings.HasPrefix(metadataSource, "http://") {
tufSource = HttpSource
tufSource = HTTPSource
} else {
tufSource = OciSource
tufSource = OCISource
}
tufRootDigest := util.SHA256Hex(initialRoot)
// create a directory for each initial root.json
metadataPath := filepath.Join(tufPath, tufRootDigest)
err := os.MkdirAll(metadataPath, 0755)
err := os.MkdirAll(metadataPath, os.ModePerm)
if err != nil {
return nil, fmt.Errorf("failed to create directory '%s': %w", metadataPath, err)
}
rootFile := filepath.Join(metadataPath, "root.json")
var rootBytes []byte
rootBytes, err = os.ReadFile(rootFile)
if err != nil {
if !errors.Is(err, fs.ErrNotExist) {
return nil, fmt.Errorf("failed to read root.json: %w", err)
}
// write the root.json file to the metadata directory
err = os.WriteFile(rootFile, initialRoot, 0644)
err = os.WriteFile(rootFile, initialRoot, 0o666) // #nosec G306
if err != nil {
return nil, fmt.Errorf("Failed to write root.json %w", err)
}
@@ -85,11 +85,11 @@ func NewTufClient(initialRoot []byte, tufPath, metadataSource, targetsSource str
cfg.LocalTargetsDir = filepath.Join(metadataPath, "download")
cfg.RemoteTargetsURL = targetsSource
if tufSource == OciSource {
if tufSource == OCISource {
metadataRepo, metadataTag, found := strings.Cut(metadataSource, ":")
if !found {
fmt.Printf("metadata tag not found in URL, using latest\n")
metadataTag = "latest"
metadataTag = LatestTag
}
cfg.Fetcher = NewRegistryFetcher(metadataRepo, metadataTag, targetsSource)
}
@@ -106,7 +106,7 @@ func NewTufClient(initialRoot []byte, tufPath, metadataSource, targetsSource str
return nil, fmt.Errorf("failed to refresh trusted metadata: %w", err)
}
client := &TufClient{
client := &Client{
updater: up,
cfg: cfg,
}
@@ -122,7 +122,7 @@ func NewTufClient(initialRoot []byte, tufPath, metadataSource, targetsSource str
// DownloadTarget downloads the target file using Updater. The Updater gets the target
// information, verifies if the target is already cached, and if it is not cached,
// downloads the target file.
func (t *TufClient) DownloadTarget(target string, filePath string) (actualFilePath string, data []byte, err error) {
func (t *Client) DownloadTarget(target string, filePath string) (actualFilePath string, data []byte, err error) {
// search if the desired target is available
targetInfo, err := t.updater.GetTargetInfo(target)
if err != nil {
@@ -131,7 +131,7 @@ func (t *TufClient) DownloadTarget(target string, filePath string) (actualFilePa
// check if filePath exists and create the directory if it doesn't
if _, err := os.Stat(filepath.Dir(filePath)); os.IsNotExist(err) {
err = os.MkdirAll(filepath.Dir(filePath), 0755)
err = os.MkdirAll(filepath.Dir(filePath), os.ModePerm)
if err != nil {
return "", nil, fmt.Errorf("failed to create target download directory '%s': %w", filepath.Dir(filePath), err)
}
@@ -155,15 +155,15 @@ func (t *TufClient) DownloadTarget(target string, filePath string) (actualFilePa
return actualFilePath, data, err
}
func (t *TufClient) GetMetadata() trustedmetadata.TrustedMetadata {
func (t *Client) GetMetadata() trustedmetadata.TrustedMetadata {
return t.updater.GetTrustedMetadataSet()
}
func (t *TufClient) MaxRootLength() int64 {
func (t *Client) MaxRootLength() int64 {
return t.cfg.RootMaxLength
}
func (t *TufClient) GetPriorRoots(metadataURL string) (map[string][]byte, error) {
func (t *Client) GetPriorRoots(metadataURL string) (map[string][]byte, error) {
rootMetadata := map[string][]byte{}
trustedMetadata := t.GetMetadata()
client := fetcher.DefaultFetcher{}
@@ -177,12 +177,12 @@ func (t *TufClient) GetPriorRoots(metadataURL string) (map[string][]byte, error)
return rootMetadata, nil
}
func (t *TufClient) SetRemoteTargetsURL(url string) {
func (t *Client) SetRemoteTargetsURL(url string) {
t.cfg.RemoteTargetsURL = url
}
// Derived from updater.loadTargets() in theupdateframework/go-tuf
func (t *TufClient) LoadDelegatedTargets(roleName, parentName string) (*metadata.Metadata[metadata.TargetsType], error) {
// Derived from updater.loadTargets() in theupdateframework/go-tuf.
func (t *Client) LoadDelegatedTargets(roleName, parentName string) (*metadata.Metadata[metadata.TargetsType], error) {
// extract the targets meta from the trusted snapshot metadata
meta := t.updater.GetTrustedMetadataSet()
metaInfo := meta.Snapshot.Signed.Meta[fmt.Sprintf("%s.json", roleName)]
@@ -209,8 +209,8 @@ func (t *TufClient) LoadDelegatedTargets(roleName, parentName string) (*metadata
return delegatedTargets, nil
}
// downloadMetadata download a metadata file and return it as bytes
func (t *TufClient) downloadMetadata(roleName string, length int64, version string) ([]byte, error) {
// downloadMetadata download a metadata file and return it as bytes.
func (t *Client) downloadMetadata(roleName string, length int64, version string) ([]byte, error) {
urlPath := ensureTrailingSlash(t.cfg.RemoteMetadataURL)
// build urlPath
if version == "" {
@@ -221,7 +221,7 @@ func (t *TufClient) downloadMetadata(roleName string, length int64, version stri
return t.cfg.Fetcher.DownloadFile(urlPath, length, time.Second*15)
}
// ensureTrailingSlash ensures url ends with a slash
// ensureTrailingSlash ensures url ends with a slash.
func ensureTrailingSlash(url string) string {
if updater.IsWindowsPath(url) {
slash := string(filepath.Separator)
@@ -236,7 +236,7 @@ func ensureTrailingSlash(url string) string {
return url + "/"
}
// GetEmbeddedTufRoot returns the embedded TUF root based on the given root name
func GetEmbeddedTufRoot(root string) (*embed.EmbeddedRoot, error) {
// GetEmbeddedRoot returns the embedded TUF root based on the given root name.
func GetEmbeddedRoot(root string) (*embed.EmbeddedRoot, error) {
return embed.GetRootFromName(root)
}

View File

@@ -15,8 +15,8 @@ import (
)
var (
HttpTufTestDataPath = filepath.Join("..", "..", "test", "testdata", "tuf", "test-repo")
OciTufTestDataPath = filepath.Join("..", "..", "test", "testdata", "tuf", "test-repo-oci")
HTTPTUFTestDataPath = filepath.Join("..", "..", "test", "testdata", "tuf", "test-repo")
OCITUFTestDataPath = filepath.Join("..", "..", "test", "testdata", "tuf", "test-repo-oci")
)
func CreateTempDir(t *testing.T, dir, pattern string) string {
@@ -35,12 +35,12 @@ func CreateTempDir(t *testing.T, dir, pattern string) string {
return tempDir
}
// NewTufClient creates a new TUF client
// NewTufClient creates a new TUF client.
func TestRootInit(t *testing.T) {
tufPath := CreateTempDir(t, "", "tuf_temp")
// Start a test HTTP server to serve data from /test/testdata/tuf/test-repo/ paths
server := httptest.NewServer(http.FileServer(http.Dir(HttpTufTestDataPath)))
server := httptest.NewServer(http.FileServer(http.Dir(HTTPTUFTestDataPath)))
defer server.Close()
// run local registry
@@ -50,10 +50,10 @@ func TestRootInit(t *testing.T) {
t.Fatalf("failed to terminate container: %s", err) // nolint:gocritic
}
}()
LoadRegistryTestData(t, regAddr, OciTufTestDataPath)
LoadRegistryTestData(t, regAddr, OCITUFTestDataPath)
alwaysGoodVersionChecker := &mockVersionChecker{err: nil}
alwaysBadVersionChecker := &mockVersionChecker{err: assert.AnError}
alwaysGoodVersionChecker := &MockVersionChecker{err: nil}
alwaysBadVersionChecker := &MockVersionChecker{err: assert.AnError}
testCases := []struct {
name string
@@ -65,17 +65,17 @@ func TestRootInit(t *testing.T) {
}
for _, tc := range testCases {
_, err := NewTufClient(embed.RootDev.Data, tufPath, tc.metadataSource, tc.targetsSource, alwaysGoodVersionChecker)
_, err := NewClient(embed.RootDev.Data, tufPath, tc.metadataSource, tc.targetsSource, alwaysGoodVersionChecker)
assert.NoErrorf(t, err, "Failed to create TUF client: %v", err)
// recreation should work with same root
_, err = NewTufClient(embed.RootDev.Data, tufPath, tc.metadataSource, tc.targetsSource, alwaysGoodVersionChecker)
_, err = NewClient(embed.RootDev.Data, tufPath, tc.metadataSource, tc.targetsSource, alwaysGoodVersionChecker)
assert.NoErrorf(t, err, "Failed to recreate TUF client: %v", err)
_, err = NewTufClient([]byte("broken"), tufPath, tc.metadataSource, tc.targetsSource, alwaysGoodVersionChecker)
_, err = NewClient([]byte("broken"), tufPath, tc.metadataSource, tc.targetsSource, alwaysGoodVersionChecker)
assert.Errorf(t, err, "Expected error recreating TUF client with broken root: %v", err)
_, err = NewTufClient(embed.RootDev.Data, tufPath, tc.metadataSource, tc.targetsSource, alwaysBadVersionChecker)
_, err = NewClient(embed.RootDev.Data, tufPath, tc.metadataSource, tc.targetsSource, alwaysBadVersionChecker)
assert.Errorf(t, err, "Expected error creating TUF client with bad attest version: %v", err)
}
}
@@ -83,11 +83,11 @@ func TestRootInit(t *testing.T) {
func TestDownloadTarget(t *testing.T) {
tufPath := CreateTempDir(t, "", "tuf_temp")
targetFile := "test.txt"
delegatedRole := "test-role"
delegatedRole := testRole
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)))
server := httptest.NewServer(http.FileServer(http.Dir(HTTPTUFTestDataPath)))
defer server.Close()
// run local registry
@@ -97,9 +97,9 @@ func TestDownloadTarget(t *testing.T) {
t.Fatalf("failed to terminate container: %s", err) // nolint:gocritic
}
}()
LoadRegistryTestData(t, regAddr, OciTufTestDataPath)
LoadRegistryTestData(t, regAddr, OCITUFTestDataPath)
alwaysGoodVersionChecker := &mockVersionChecker{err: nil}
alwaysGoodVersionChecker := &MockVersionChecker{err: nil}
testCases := []struct {
name string
@@ -111,7 +111,7 @@ func TestDownloadTarget(t *testing.T) {
}
for _, tc := range testCases {
tufClient, err := NewTufClient(embed.RootDev.Data, tufPath, tc.metadataSource, tc.targetsSource, alwaysGoodVersionChecker)
tufClient, err := NewClient(embed.RootDev.Data, tufPath, tc.metadataSource, tc.targetsSource, alwaysGoodVersionChecker)
assert.NoErrorf(t, err, "Failed to create TUF client: %v", err)
// get trusted tuf metadata
@@ -135,22 +135,22 @@ func TestDownloadTarget(t *testing.T) {
}
func TestGetEmbeddedTufRootBytes(t *testing.T) {
dev, err := GetEmbeddedTufRoot("dev")
dev, err := GetEmbeddedRoot("dev")
assert.NoError(t, err)
staging, err := GetEmbeddedTufRoot("staging")
staging, err := GetEmbeddedRoot("staging")
assert.NoError(t, err)
assert.NotEqual(t, dev.Data, staging.Data)
prod, err := GetEmbeddedTufRoot("prod")
prod, err := GetEmbeddedRoot("prod")
assert.NoError(t, err)
assert.NotEqual(t, dev.Data, prod.Data)
assert.NotEqual(t, staging.Data, prod.Data)
def, err := GetEmbeddedTufRoot("")
def, err := GetEmbeddedRoot("")
assert.NoError(t, err)
assert.Equal(t, def.Data, prod.Data)
_, err = GetEmbeddedTufRoot("invalid")
_, err = GetEmbeddedRoot("invalid")
assert.Error(t, err)
}

View File

@@ -12,7 +12,7 @@ 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
CheckVersion(tufClient Downloader) error
}
type InvalidVersionError struct {
@@ -32,13 +32,13 @@ func (e *InvalidVersionError) Error() string {
return fmt.Sprintf("%s version %s does not satisfy constraints %s: %s", ThisModulePath, e.AttestVersion, e.VersionConstraint, errsStr.String())
}
func NewVersionChecker() *versionChecker {
return &versionChecker{}
func NewDefaultVersionChecker() *DefaultVersionChecker {
return &DefaultVersionChecker{}
}
type versionChecker struct{}
type DefaultVersionChecker struct{}
func (vc *versionChecker) CheckVersion(client TUFClient) error {
func (vc *DefaultVersionChecker) CheckVersion(client Downloader) error {
var attestMod *debug.Module
bi, ok := debug.ReadBuildInfo()
if !ok {

View File

@@ -0,0 +1 @@
{"signatures":[{"keyid":"76d0a7e1ff8617ce99627d0fa5c9809f2c0f0d52e0bf65c7b84c031608d25221","sig":"3065023079fce0ddea385d0e5b6eed0da688946f417d1c1bf6397edaa44279bf948d6de41daf5e0852069900f363175abd95959b023100d2b950cb3f39cc4df8140d2ec3c60d81d2811827fbc61034786cd877586f6ab5f9ba03ad95d7de58e9241917d79687a9"},{"keyid":"beac53949c4cf075824edede7d41715941f524db247d1b455a2389d7490ecd72","sig":""}],"signed":{"_type":"root","consistent_snapshot":true,"expires":"2034-06-12T17:21:13Z","keys":{"76d0a7e1ff8617ce99627d0fa5c9809f2c0f0d52e0bf65c7b84c031608d25221":{"keytype":"ecdsa","keyval":{"public":"-----BEGIN PUBLIC KEY-----\nMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE3+asmp2GD6UijwWvMezwVG/BwFLuQa3o\nT6eRxFvkILGpVDbZ92ZYWidHl9LZ/eJUjhIjuVEkNVKoenw5KjKl8veP3MthZrQA\nSkYytOIwkidZo9Rk2dczbDcFSJvLGsmd\n-----END PUBLIC KEY-----\n"},"scheme":"ecdsa-sha2-nistp384","x-tuf-on-ci-keyowner":"@mrjoelkamp"},"bdd1703ecbde8812614b112a6551d58de3ad681048fd90fca2a3e491edd8afe5":{"keytype":"ecdsa","keyval":{"public":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEgDpP6O0sEt2R+l84WlfmqPBsFSby\nxJsJ6YmeUVgDk/wk9++8IAR6YBYewaKye56gMnIYjTFbyOI8WomA2NQFBw==\n-----END PUBLIC KEY-----\n"},"scheme":"ecdsa-sha2-nistp256","x-tuf-on-ci-online-uri":"awskms:arn:aws:kms:us-east-1:175142243308:key/fbd8dab6-5677-4b57-87e6-8369c45b3b61"},"beac53949c4cf075824edede7d41715941f524db247d1b455a2389d7490ecd72":{"keytype":"ecdsa","keyval":{"public":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEERet/8hs3WHIXyOXNzhLpTOz6DBx\n7zzHnenJgV/TB0dRMAx6j9UVRvlEkh5OcYuktNeqnLpHce1rLjLjpiRPVg==\n-----END PUBLIC KEY-----\n"},"scheme":"ecdsa-sha2-nistp256","x-tuf-on-ci-keyowner":"@jonnystoten"}},"roles":{"root":{"keyids":["76d0a7e1ff8617ce99627d0fa5c9809f2c0f0d52e0bf65c7b84c031608d25221","beac53949c4cf075824edede7d41715941f524db247d1b455a2389d7490ecd72"],"threshold":1},"snapshot":{"keyids":["bdd1703ecbde8812614b112a6551d58de3ad681048fd90fca2a3e491edd8afe5"],"threshold":1,"x-tuf-on-ci-expiry-period":3650,"x-tuf-on-ci-signing-period":60},"targets":{"keyids":["76d0a7e1ff8617ce99627d0fa5c9809f2c0f0d52e0bf65c7b84c031608d25221","beac53949c4cf075824edede7d41715941f524db247d1b455a2389d7490ecd72"],"threshold":1},"timestamp":{"keyids":["bdd1703ecbde8812614b112a6551d58de3ad681048fd90fca2a3e491edd8afe5"],"threshold":1,"x-tuf-on-ci-expiry-period":3650,"x-tuf-on-ci-signing-period":60}},"spec_version":"1.0.31","version":2,"x-tuf-on-ci-expiry-period":3650,"x-tuf-on-ci-signing-period":60}}

View File

@@ -0,0 +1 @@
{"signatures":[{"keyid":"bdd1703ecbde8812614b112a6551d58de3ad681048fd90fca2a3e491edd8afe5","sig":"304502204019c08b30b7525b95c4010e5c1420c5618c18d5b0719fb1d9392ef93322ca4e022100924ec18242ba21edcc2c7ad92ee13a38a6f4a8e1315c588eb9eb2d0bce0a1a80"}],"signed":{"_type":"timestamp","expires":"2034-06-23T12:47:16Z","meta":{"snapshot.json":{"version":7}},"spec_version":"1.0.31","version":7}}

View File

@@ -1 +0,0 @@
{"signatures":[{"keyid":"198f00ff96ea7cbfa7eac480cc9bfc43ce13bb434b901011ab777856533997d3","sig":"3044022039b56cd2e3597df74e57d200a652ba020cdc9a8cd050bd65b5f8e2640d50691d02205e073e4b6fc260acc64327a331e4440601af5b1cbff594ea91cf7b70d5828fb1"}],"signed":{"_type":"snapshot","expires":"2034-04-03T15:59:47Z","meta":{"targets.json":{"version":5},"test-role.json":{"version":3}},"spec_version":"1.0.31","version":6}}

View File

@@ -1 +0,0 @@
{"signatures":[{"keyid":"198f00ff96ea7cbfa7eac480cc9bfc43ce13bb434b901011ab777856533997d3","sig":"3045022011f2afa9b448fcbbac983c11fc3e264e95d5d7a9c9527b09d83a316ee762635f022100d05197a78ccc7a713ebdb0bccb44844f67a7c5208af8d346e201064b7ce11055"}],"signed":{"_type":"timestamp","expires":"2034-04-03T15:59:47Z","meta":{"snapshot.json":{"version":6}},"spec_version":"1.0.31","version":6}}

View File

@@ -1,42 +1,42 @@
{
"signatures": [
{
"keyid": "b7474a42f2588fa92ed4a2ebea6047a7b1b2f7351f1cfe0912732c0d0fb0fc09",
"sig": "3064023037bbb03c3472b140572a7d5a2895bd80e74435bbcb7053949731f81b104c6d05a0876590cd6a2e94d7ed619426a2f6fa02303adc8c9006fa5506fdd7ea87d2960074a537ad8bf2459f2863e806b47682cbb2f9b01b7502eaf5437a1a68fdaaeac114"
"keyid": "76d0a7e1ff8617ce99627d0fa5c9809f2c0f0d52e0bf65c7b84c031608d25221",
"sig": "3065023000f7d0a866576e94eaabc173b9233d4c8fcfa495527088f9022dff5a553f7a457da1015a6d0fc714f84848ec627387360231009fa70b2eebbe15241a2ec9b96a094ebd28661e30b8c3d1eab8d694df2b340bda511c489393630c9a9dacde42c99e9fa1"
}
],
"signed": {
"_type": "root",
"consistent_snapshot": true,
"expires": "2034-04-02T17:00:22Z",
"expires": "2034-05-29T20:14:11Z",
"keys": {
"198f00ff96ea7cbfa7eac480cc9bfc43ce13bb434b901011ab777856533997d3": {
"keytype": "ecdsa",
"keyval": {
"public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEgDpP6O0sEt2R+l84WlfmqPBsFSby\nxJsJ6YmeUVgDk/wk9++8IAR6YBYewaKye56gMnIYjTFbyOI8WomA2NQFBw==\n-----END PUBLIC KEY-----\n"
},
"scheme": "ecdsa-sha2-nistp256",
"x-tuf-on-ci-online-uri": "awskms:arn:aws:kms:us-east-1:175142243308:key/fbd8dab6-5677-4b57-87e6-8369c45b3b61"
},
"b7474a42f2588fa92ed4a2ebea6047a7b1b2f7351f1cfe0912732c0d0fb0fc09": {
"76d0a7e1ff8617ce99627d0fa5c9809f2c0f0d52e0bf65c7b84c031608d25221": {
"keytype": "ecdsa",
"keyval": {
"public": "-----BEGIN PUBLIC KEY-----\nMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE3+asmp2GD6UijwWvMezwVG/BwFLuQa3o\nT6eRxFvkILGpVDbZ92ZYWidHl9LZ/eJUjhIjuVEkNVKoenw5KjKl8veP3MthZrQA\nSkYytOIwkidZo9Rk2dczbDcFSJvLGsmd\n-----END PUBLIC KEY-----\n"
},
"scheme": "ecdsa-sha2-nistp384",
"x-tuf-on-ci-keyowner": "@mrjoelkamp"
},
"bdd1703ecbde8812614b112a6551d58de3ad681048fd90fca2a3e491edd8afe5": {
"keytype": "ecdsa",
"keyval": {
"public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEgDpP6O0sEt2R+l84WlfmqPBsFSby\nxJsJ6YmeUVgDk/wk9++8IAR6YBYewaKye56gMnIYjTFbyOI8WomA2NQFBw==\n-----END PUBLIC KEY-----\n"
},
"scheme": "ecdsa-sha2-nistp256",
"x-tuf-on-ci-online-uri": "awskms:arn:aws:kms:us-east-1:175142243308:key/fbd8dab6-5677-4b57-87e6-8369c45b3b61"
}
},
"roles": {
"root": {
"keyids": [
"b7474a42f2588fa92ed4a2ebea6047a7b1b2f7351f1cfe0912732c0d0fb0fc09"
"76d0a7e1ff8617ce99627d0fa5c9809f2c0f0d52e0bf65c7b84c031608d25221"
],
"threshold": 1
},
"snapshot": {
"keyids": [
"198f00ff96ea7cbfa7eac480cc9bfc43ce13bb434b901011ab777856533997d3"
"bdd1703ecbde8812614b112a6551d58de3ad681048fd90fca2a3e491edd8afe5"
],
"threshold": 1,
"x-tuf-on-ci-expiry-period": 3650,
@@ -44,13 +44,13 @@
},
"targets": {
"keyids": [
"b7474a42f2588fa92ed4a2ebea6047a7b1b2f7351f1cfe0912732c0d0fb0fc09"
"76d0a7e1ff8617ce99627d0fa5c9809f2c0f0d52e0bf65c7b84c031608d25221"
],
"threshold": 1
},
"timestamp": {
"keyids": [
"198f00ff96ea7cbfa7eac480cc9bfc43ce13bb434b901011ab777856533997d3"
"bdd1703ecbde8812614b112a6551d58de3ad681048fd90fca2a3e491edd8afe5"
],
"threshold": 1,
"x-tuf-on-ci-expiry-period": 3650,

View File

@@ -0,0 +1 @@
{"signatures":[{"keyid":"bdd1703ecbde8812614b112a6551d58de3ad681048fd90fca2a3e491edd8afe5","sig":"3045022018e31a2e743b21054939262706520be10375829fb93dec7f3042e48ed8eb9cec0221008c2765ee9e49d49c12a6b9a5124c984d414b8d86452cdbcc2fc2f2ca10a11e67"}],"signed":{"_type":"snapshot","expires":"2034-06-23T12:47:16Z","meta":{"targets.json":{"version":8},"test-role.json":{"version":2}},"spec_version":"1.0.31","version":7}}

View File

@@ -0,0 +1 @@
{"architecture":"","created":"0001-01-01T00:00:00Z","history":[{"created":"0001-01-01T00:00:00Z"},{"created":"0001-01-01T00:00:00Z"},{"created":"0001-01-01T00:00:00Z"},{"created":"0001-01-01T00:00:00Z"},{"created":"0001-01-01T00:00:00Z"}],"os":"","rootfs":{"type":"layers","diff_ids":["sha256:5a9f60b64b708d05e4e4da0354529fc7fe5015807b79f0bf7b136207bf952bd7","sha256:1e6d780fc1967ff3d2d65c01b3614536a1562de0f0e5981718df82f61dc0c670","sha256:5caaed86d85583b60586eff2da6ecff41a35d0ec5b8a603330db791249f7d497","sha256:ddc840cc61ca4a5cf9b79d683fc81144977f2d95f1734ebf247b3f9da4d644fb","sha256:1f83502e00bf791ad0b4308fed7ba4a2cb099665069585f21f819fb35be140d8"]},"config":{}}

View File

@@ -1 +0,0 @@
{"schemaVersion":2,"mediaType":"application/vnd.oci.image.manifest.v1+json","config":{"mediaType":"application/vnd.oci.image.config.v1+json","size":669,"digest":"sha256:ad4cacc170229608305ffccd8d09eeb59578fcb72ae394763cf7ef492175b1ee"},"layers":[{"mediaType":"application/vnd.tuf.metadata+json","size":2607,"digest":"sha256:a2e026ce65c198ee68a7ed2df6978ed0287bb38342f6ddb7bf934a456f1d6f87","annotations":{"tuf.io/filename":"2.root.json"}},{"mediaType":"application/vnd.tuf.metadata+json","size":2200,"digest":"sha256:61a98e1e86ae279e59415d927e38beae430d7e6d2bd6207054179429ea9b6763","annotations":{"tuf.io/filename":"1.root.json"}},{"mediaType":"application/vnd.tuf.metadata+json","size":410,"digest":"sha256:1fd0d9781f02486718fcbd7724db0e4c4ba47b649930cec22a3e7e6b6077ba38","annotations":{"tuf.io/filename":"6.snapshot.json"}},{"mediaType":"application/vnd.tuf.metadata+json","size":1683,"digest":"sha256:ea7713eb649ca1a33d79ebdccda9f7f066595b1b2c6e37e52dbfd250f5287260","annotations":{"tuf.io/filename":"5.targets.json"}},{"mediaType":"application/vnd.tuf.metadata+json","size":383,"digest":"sha256:4c1054844dba3241525cbd71ff9e58becca652fb1ce4a0e6ea55a01c4ec41950","annotations":{"tuf.io/filename":"timestamp.json"}}]}

View File

@@ -1 +0,0 @@
{"signatures":[{"keyid":"b7474a42f2588fa92ed4a2ebea6047a7b1b2f7351f1cfe0912732c0d0fb0fc09","sig":"3066023100e99acc5f74777ebf40376b60f0216e8fe1829c1a49a5f6a6899126c15de1df7a56533baf493b2b53159c50843a289102023100b6a006b24da62ea0b743fbe38e1497ff485bf3a0833894985fc27a0305ad0693eeb968a7b52723ed3c49af8bef2027b6"},{"keyid":"81cf5a78d6ea2cd904256b9d814b340289b765e6f75ec4397e4ebb7586cab664","sig":"30440220136debcc2f60dd1d63c9c2704f9b13c2cb2f5d2df58ea93f07f7c10f54f36742022059d7f8c6620e33506c6f1766394a32f86c9b008328f6398831ba7ebcf4ce0838"}],"signed":{"_type":"root","consistent_snapshot":true,"expires":"2034-04-03T08:45:50Z","keys":{"198f00ff96ea7cbfa7eac480cc9bfc43ce13bb434b901011ab777856533997d3":{"keytype":"ecdsa","keyval":{"public":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEgDpP6O0sEt2R+l84WlfmqPBsFSby\nxJsJ6YmeUVgDk/wk9++8IAR6YBYewaKye56gMnIYjTFbyOI8WomA2NQFBw==\n-----END PUBLIC KEY-----\n"},"scheme":"ecdsa-sha2-nistp256","x-tuf-on-ci-online-uri":"awskms:arn:aws:kms:us-east-1:175142243308:key/fbd8dab6-5677-4b57-87e6-8369c45b3b61"},"81cf5a78d6ea2cd904256b9d814b340289b765e6f75ec4397e4ebb7586cab664":{"keytype":"ecdsa","keyval":{"public":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEWmhpAfB7Q53UNluMhpkDxXXup4E0\n2Hh4PSgHC1Yh6brGl6Akq9a4io55LtZTk5mnCTqxuB+rc5cI/yaNUeWEqQ==\n-----END PUBLIC KEY-----\n"},"scheme":"ecdsa-sha2-nistp256","x-tuf-on-ci-keyowner":"@kipz"},"b7474a42f2588fa92ed4a2ebea6047a7b1b2f7351f1cfe0912732c0d0fb0fc09":{"keytype":"ecdsa","keyval":{"public":"-----BEGIN PUBLIC KEY-----\nMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE3+asmp2GD6UijwWvMezwVG/BwFLuQa3o\nT6eRxFvkILGpVDbZ92ZYWidHl9LZ/eJUjhIjuVEkNVKoenw5KjKl8veP3MthZrQA\nSkYytOIwkidZo9Rk2dczbDcFSJvLGsmd\n-----END PUBLIC KEY-----\n"},"scheme":"ecdsa-sha2-nistp384","x-tuf-on-ci-keyowner":"@mrjoelkamp"}},"roles":{"root":{"keyids":["b7474a42f2588fa92ed4a2ebea6047a7b1b2f7351f1cfe0912732c0d0fb0fc09","81cf5a78d6ea2cd904256b9d814b340289b765e6f75ec4397e4ebb7586cab664"],"threshold":1},"snapshot":{"keyids":["198f00ff96ea7cbfa7eac480cc9bfc43ce13bb434b901011ab777856533997d3"],"threshold":1,"x-tuf-on-ci-expiry-period":3650,"x-tuf-on-ci-signing-period":60},"targets":{"keyids":["b7474a42f2588fa92ed4a2ebea6047a7b1b2f7351f1cfe0912732c0d0fb0fc09","81cf5a78d6ea2cd904256b9d814b340289b765e6f75ec4397e4ebb7586cab664"],"threshold":1},"timestamp":{"keyids":["198f00ff96ea7cbfa7eac480cc9bfc43ce13bb434b901011ab777856533997d3"],"threshold":1,"x-tuf-on-ci-expiry-period":3650,"x-tuf-on-ci-signing-period":60}},"spec_version":"1.0.31","version":2,"x-tuf-on-ci-expiry-period":3650,"x-tuf-on-ci-signing-period":60}}

View File

@@ -1 +0,0 @@
{"schemaVersion":2,"mediaType":"application/vnd.oci.image.manifest.v1+json","config":{"mediaType":"application/vnd.oci.image.config.v1+json","size":669,"digest":"sha256:c927b30f17fa8c64e3c20b8f92b7e348733f9c1281b5b7e6b6d669a8a74230a7"},"layers":[{"mediaType":"application/vnd.tuf.metadata+json","size":2200,"digest":"sha256:61a98e1e86ae279e59415d927e38beae430d7e6d2bd6207054179429ea9b6763","annotations":{"tuf.io/filename":"1.root.json"}},{"mediaType":"application/vnd.tuf.metadata+json","size":2607,"digest":"sha256:a2e026ce65c198ee68a7ed2df6978ed0287bb38342f6ddb7bf934a456f1d6f87","annotations":{"tuf.io/filename":"2.root.json"}},{"mediaType":"application/vnd.tuf.metadata+json","size":410,"digest":"sha256:1fd0d9781f02486718fcbd7724db0e4c4ba47b649930cec22a3e7e6b6077ba38","annotations":{"tuf.io/filename":"6.snapshot.json"}},{"mediaType":"application/vnd.tuf.metadata+json","size":1683,"digest":"sha256:ea7713eb649ca1a33d79ebdccda9f7f066595b1b2c6e37e52dbfd250f5287260","annotations":{"tuf.io/filename":"5.targets.json"}},{"mediaType":"application/vnd.tuf.metadata+json","size":383,"digest":"sha256:4c1054844dba3241525cbd71ff9e58becca652fb1ce4a0e6ea55a01c4ec41950","annotations":{"tuf.io/filename":"timestamp.json"}}]}

View File

@@ -1 +0,0 @@
{"architecture":"","created":"0001-01-01T00:00:00Z","history":[{"created":"0001-01-01T00:00:00Z"},{"created":"0001-01-01T00:00:00Z"},{"created":"0001-01-01T00:00:00Z"},{"created":"0001-01-01T00:00:00Z"},{"created":"0001-01-01T00:00:00Z"}],"os":"","rootfs":{"type":"layers","diff_ids":["sha256:a2e026ce65c198ee68a7ed2df6978ed0287bb38342f6ddb7bf934a456f1d6f87","sha256:61a98e1e86ae279e59415d927e38beae430d7e6d2bd6207054179429ea9b6763","sha256:1fd0d9781f02486718fcbd7724db0e4c4ba47b649930cec22a3e7e6b6077ba38","sha256:ea7713eb649ca1a33d79ebdccda9f7f066595b1b2c6e37e52dbfd250f5287260","sha256:4c1054844dba3241525cbd71ff9e58becca652fb1ce4a0e6ea55a01c4ec41950"]},"config":{}}

View File

@@ -1 +0,0 @@
{"architecture":"","created":"0001-01-01T00:00:00Z","history":[{"created":"0001-01-01T00:00:00Z"},{"created":"0001-01-01T00:00:00Z"},{"created":"0001-01-01T00:00:00Z"},{"created":"0001-01-01T00:00:00Z"},{"created":"0001-01-01T00:00:00Z"}],"os":"","rootfs":{"type":"layers","diff_ids":["sha256:61a98e1e86ae279e59415d927e38beae430d7e6d2bd6207054179429ea9b6763","sha256:a2e026ce65c198ee68a7ed2df6978ed0287bb38342f6ddb7bf934a456f1d6f87","sha256:1fd0d9781f02486718fcbd7724db0e4c4ba47b649930cec22a3e7e6b6077ba38","sha256:ea7713eb649ca1a33d79ebdccda9f7f066595b1b2c6e37e52dbfd250f5287260","sha256:4c1054844dba3241525cbd71ff9e58becca652fb1ce4a0e6ea55a01c4ec41950"]},"config":{}}

View File

@@ -0,0 +1 @@
{"signatures":[{"keyid":"76d0a7e1ff8617ce99627d0fa5c9809f2c0f0d52e0bf65c7b84c031608d25221","sig":""},{"keyid":"beac53949c4cf075824edede7d41715941f524db247d1b455a2389d7490ecd72","sig":"304602210086552ad4ffddd7e60f2b80d095b4dfad9d2836cfce5d6b12dfb2aec0786240df02210097807190a1f64c615798b74068e8c9f19a29f495566bc1f16d296c7edd9343b3"}],"signed":{"_type":"targets","delegations":{"keys":{"76d0a7e1ff8617ce99627d0fa5c9809f2c0f0d52e0bf65c7b84c031608d25221":{"keytype":"ecdsa","keyval":{"public":"-----BEGIN PUBLIC KEY-----\nMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE3+asmp2GD6UijwWvMezwVG/BwFLuQa3o\nT6eRxFvkILGpVDbZ92ZYWidHl9LZ/eJUjhIjuVEkNVKoenw5KjKl8veP3MthZrQA\nSkYytOIwkidZo9Rk2dczbDcFSJvLGsmd\n-----END PUBLIC KEY-----\n"},"scheme":"ecdsa-sha2-nistp384","x-tuf-on-ci-keyowner":"@mrjoelkamp"}},"roles":[{"keyids":["76d0a7e1ff8617ce99627d0fa5c9809f2c0f0d52e0bf65c7b84c031608d25221"],"name":"test-role","paths":["test-role/*","test-role/*/*","test-role/*/*/*","test-role/*/*/*/*"],"terminating":true,"threshold":1}]},"expires":"2034-06-23T12:42:15Z","spec_version":"1.0.31","targets":{"always-fail.rego":{"hashes":{"sha256":"e8a5b75ac27a28056d2155ff63acc1ffd76c30ed8558011c54708f4832f073ac"},"length":364},"jonnystoten2.rego":{"hashes":{"sha256":"bc46e8c31646f166a9efbd14fef154dd84cf07efc95c96be3a201c84470dcbc1"},"length":5857},"mapping.yaml":{"hashes":{"sha256":"baad1a9d61afa5d6f8717f576b57b9749e5549da4b826746fd73a5a914ac5be1"},"length":272},"test.txt":{"hashes":{"sha256":"02119a076ec3878c736c3a95e20794f5a8d5bce3d7ecc264681bb7334ca2e24b"},"length":31},"version-constraints":{"hashes":{"sha256":"bd6394a08afc1edfe5512fc14e63025a337e25ca0013c1068ec879742fc3a3c3"},"length":12}},"version":8,"x-tuf-on-ci-expiry-period":3650,"x-tuf-on-ci-signing-period":60}}

View File

@@ -0,0 +1 @@
{"schemaVersion":2,"mediaType":"application/vnd.oci.image.manifest.v1+json","config":{"mediaType":"application/vnd.oci.image.config.v1+json","size":669,"digest":"sha256:742736cf58eef752676e9254241b3143779ad66e10707f980b6a477cdc23ad59"},"layers":[{"mediaType":"application/vnd.tuf.metadata+json","size":2202,"digest":"sha256:5a9f60b64b708d05e4e4da0354529fc7fe5015807b79f0bf7b136207bf952bd7","annotations":{"tuf.io/filename":"1.root.json"}},{"mediaType":"application/vnd.tuf.metadata+json","size":2472,"digest":"sha256:1e6d780fc1967ff3d2d65c01b3614536a1562de0f0e5981718df82f61dc0c670","annotations":{"tuf.io/filename":"2.root.json"}},{"mediaType":"application/vnd.tuf.metadata+json","size":412,"digest":"sha256:5caaed86d85583b60586eff2da6ecff41a35d0ec5b8a603330db791249f7d497","annotations":{"tuf.io/filename":"7.snapshot.json"}},{"mediaType":"application/vnd.tuf.metadata+json","size":1746,"digest":"sha256:ddc840cc61ca4a5cf9b79d683fc81144977f2d95f1734ebf247b3f9da4d644fb","annotations":{"tuf.io/filename":"8.targets.json"}},{"mediaType":"application/vnd.tuf.metadata+json","size":383,"digest":"sha256:1f83502e00bf791ad0b4308fed7ba4a2cb099665069585f21f819fb35be140d8","annotations":{"tuf.io/filename":"timestamp.json"}}]}

View File

@@ -1 +0,0 @@
{"signatures":[{"keyid":"b7474a42f2588fa92ed4a2ebea6047a7b1b2f7351f1cfe0912732c0d0fb0fc09","sig":""},{"keyid":"81cf5a78d6ea2cd904256b9d814b340289b765e6f75ec4397e4ebb7586cab664","sig":"3046022100f892a496c9bd96082e3b06d5eae85429355876b8eb455aa04b53ab9051911d90022100a3e89c29b15bccfc2877278c0fb2d3b34500da6351e245ad0b3f8c0ae6b67eff"}],"signed":{"_type":"targets","delegations":{"keys":{"81cf5a78d6ea2cd904256b9d814b340289b765e6f75ec4397e4ebb7586cab664":{"keytype":"ecdsa","keyval":{"public":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEWmhpAfB7Q53UNluMhpkDxXXup4E0\n2Hh4PSgHC1Yh6brGl6Akq9a4io55LtZTk5mnCTqxuB+rc5cI/yaNUeWEqQ==\n-----END PUBLIC KEY-----\n"},"scheme":"ecdsa-sha2-nistp256","x-tuf-on-ci-keyowner":"@kipz"},"b7474a42f2588fa92ed4a2ebea6047a7b1b2f7351f1cfe0912732c0d0fb0fc09":{"keytype":"ecdsa","keyval":{"public":"-----BEGIN PUBLIC KEY-----\nMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE3+asmp2GD6UijwWvMezwVG/BwFLuQa3o\nT6eRxFvkILGpVDbZ92ZYWidHl9LZ/eJUjhIjuVEkNVKoenw5KjKl8veP3MthZrQA\nSkYytOIwkidZo9Rk2dczbDcFSJvLGsmd\n-----END PUBLIC KEY-----\n"},"scheme":"ecdsa-sha2-nistp384","x-tuf-on-ci-keyowner":"@mrjoelkamp"}},"roles":[{"keyids":["b7474a42f2588fa92ed4a2ebea6047a7b1b2f7351f1cfe0912732c0d0fb0fc09","81cf5a78d6ea2cd904256b9d814b340289b765e6f75ec4397e4ebb7586cab664"],"name":"test-role","paths":["test-role/*","test-role/*/*","test-role/*/*/*","test-role/*/*/*/*"],"terminating":true,"threshold":1}]},"expires":"2034-04-03T15:28:29Z","spec_version":"1.0.31","targets":{"test.txt":{"hashes":{"sha256":"02119a076ec3878c736c3a95e20794f5a8d5bce3d7ecc264681bb7334ca2e24b"},"length":31}},"version":5,"x-tuf-on-ci-expiry-period":3650,"x-tuf-on-ci-signing-period":60}}

View File

@@ -5,7 +5,7 @@
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"size": 1220,
"digest": "sha256:a744a2f1e62ae4ce410822b5e3f5508dbaf6a76768a9d23741828172bab1dc97"
"digest": "sha256:e744131b8e5deec56c893bb4de662fdefa3b82fb8c66a9fa4a039ea543afa5e1"
}
]
}

View File

@@ -1 +0,0 @@
{"signatures":[{"keyid":"b7474a42f2588fa92ed4a2ebea6047a7b1b2f7351f1cfe0912732c0d0fb0fc09","sig":""},{"keyid":"81cf5a78d6ea2cd904256b9d814b340289b765e6f75ec4397e4ebb7586cab664","sig":"3044022015b6ebe9d30895e3be20e707a6738e38460197d90cae3dc37527ddb7c437868602207f85f3d4e068bef4c51a749f5d166cc7fe2cb9483999ea197e72395081c3aa61"}],"signed":{"_type":"targets","expires":"2034-04-03T15:39:02Z","spec_version":"1.0.31","targets":{"test-role/dir1/dir2/dir3/myfile.txt":{"hashes":{"sha256":"ea230621c53e0bb858ea5526125414f8957fb29c08350528d50a162c620f36b1"},"length":10},"test-role/test.txt":{"hashes":{"sha256":"d1bb6181284970ae43fbbc88b5e72f9a5942ebac20588aa0c4bf78ba621e1ee2"},"length":32}},"version":3,"x-tuf-on-ci-expiry-period":3650,"x-tuf-on-ci-signing-period":60}}

View File

@@ -0,0 +1 @@
{"schemaVersion":2,"mediaType":"application/vnd.oci.image.manifest.v1+json","config":{"mediaType":"application/vnd.oci.image.config.v1+json","size":233,"digest":"sha256:84fd82cab3086626411db7936836bca343f3f2cb7a9b41846cbc42d6ff64da98"},"layers":[{"mediaType":"application/vnd.tuf.metadata+json","size":742,"digest":"sha256:ad7b6cdc3c7c0af0f8f05459471074adb6353ff72e65e2ec2629fafcce1603b1","annotations":{"tuf.io/filename":"2.test-role.json"}}]}

View File

@@ -1 +0,0 @@
{"schemaVersion":2,"mediaType":"application/vnd.oci.image.manifest.v1+json","config":{"mediaType":"application/vnd.oci.image.config.v1+json","size":233,"digest":"sha256:9edf24c022c2cd6796e87f49ec6a6ea2fad3e7c939c32a8219aaa4726792457c"},"layers":[{"mediaType":"application/vnd.tuf.metadata+json","size":764,"digest":"sha256:2b2d4fba192ec164e05e6d90399c5cf4a45e4fe2ddebb9066c55aa2bcf0a73d3","annotations":{"tuf.io/filename":"3.test-role.json"}}]}

View File

@@ -1 +1 @@
{"architecture":"","created":"0001-01-01T00:00:00Z","history":[{"created":"0001-01-01T00:00:00Z"}],"os":"","rootfs":{"type":"layers","diff_ids":["sha256:2b2d4fba192ec164e05e6d90399c5cf4a45e4fe2ddebb9066c55aa2bcf0a73d3"]},"config":{}}
{"architecture":"","created":"0001-01-01T00:00:00Z","history":[{"created":"0001-01-01T00:00:00Z"}],"os":"","rootfs":{"type":"layers","diff_ids":["sha256:ad7b6cdc3c7c0af0f8f05459471074adb6353ff72e65e2ec2629fafcce1603b1"]},"config":{}}

View File

@@ -0,0 +1 @@
{"signatures":[{"keyid":"76d0a7e1ff8617ce99627d0fa5c9809f2c0f0d52e0bf65c7b84c031608d25221","sig":"3065023100c37572d6e0608e0501026d99238ee37d26856d93074227410b0748e56775f8369cf7c44553b73d8a30aa94a388148ca602305b46acbb0e8818657725024a39d02589538845ad9fa0c2b6eb18f431f560096045fd825586dce81688c9574b11b975da"}],"signed":{"_type":"targets","expires":"2034-05-29T20:25:01Z","spec_version":"1.0.31","targets":{"test-role/dir1/dir2/dir3/test.txt":{"hashes":{"sha256":"bb8fcf06f6c067dcbcb394d7d9ced788316fc02b715fe679097281108a4bd465"},"length":46},"test-role/test.txt":{"hashes":{"sha256":"d1bb6181284970ae43fbbc88b5e72f9a5942ebac20588aa0c4bf78ba621e1ee2"},"length":32}},"version":2,"x-tuf-on-ci-expiry-period":3650,"x-tuf-on-ci-signing-period":60}}

View File

@@ -5,7 +5,7 @@
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"size": 444,
"digest": "sha256:7c8d8f5dfca62068e3a4b18bb41cf85dad23ec9cdc7d7d2e10bc37b86ebffff5"
"digest": "sha256:6536fc6f6e006b674a97c23b28c01e97153533777a48c3de9ff06a20a200dcbc"
}
]
}

View File

@@ -0,0 +1 @@
{"schemaVersion":2,"mediaType":"application/vnd.oci.image.manifest.v1+json","config":{"mediaType":"application/vnd.oci.image.config.v1+json","size":233,"digest":"sha256:518931eb24f93aa58c711c77e59d63171462133141ba9c6f8b6bc99a8daaab4d"},"layers":[{"mediaType":"application/vnd.tuf.target","size":272,"digest":"sha256:baad1a9d61afa5d6f8717f576b57b9749e5549da4b826746fd73a5a914ac5be1","annotations":{"tuf.io/filename":"baad1a9d61afa5d6f8717f576b57b9749e5549da4b826746fd73a5a914ac5be1.mapping.yaml"}}]}

View File

@@ -1 +1 @@
{"architecture":"","created":"0001-01-01T00:00:00Z","history":[{"created":"0001-01-01T00:00:00Z"}],"os":"","rootfs":{"type":"layers","diff_ids":["sha256:ea230621c53e0bb858ea5526125414f8957fb29c08350528d50a162c620f36b1"]},"config":{}}
{"architecture":"","created":"0001-01-01T00:00:00Z","history":[{"created":"0001-01-01T00:00:00Z"}],"os":"","rootfs":{"type":"layers","diff_ids":["sha256:baad1a9d61afa5d6f8717f576b57b9749e5549da4b826746fd73a5a914ac5be1"]},"config":{}}

View File

@@ -0,0 +1,12 @@
version: v1
kind: policy-mapping
policies:
- origin:
domain: docker.io
prefix: jonnystoten2/
id: jonnystoten2
description: jonnystoten2 personal images for testing
attestations:
style: "referrers"
files:
- path: jonnystoten2.rego

Some files were not shown because too many files have changed in this diff Show More