docs: update examples in README.md
This commit is contained in:
125
README.md
125
README.md
@@ -3,128 +3,13 @@ library to create, verify, and evaluate policy for attestations on container ima
|
||||
|
||||
# usage
|
||||
## verifying attestations
|
||||
1. create a TUF client
|
||||
* using OCI registry for TUF
|
||||
```go
|
||||
tufOutputPath = "/.docker/tuf"
|
||||
metadataURI = "docker/tuf-metadata:latest"
|
||||
targetsURI = "docker/tuf-targets"
|
||||
tufClient, err := tuf.NewTufClient(embed.DefaultRoot, tufOutputPath, metadataURI, targetsURI)
|
||||
```
|
||||
* using HTTPS for TUF
|
||||
```go
|
||||
tufOutputPath = "/.docker/tuf"
|
||||
metadataURI = "https://docker.github.io/tuf/metadata"
|
||||
targetsURI = "https://docker.github.io/tuf/targets"
|
||||
tufClient, err := tuf.NewTufClient(embed.DefaultRoot, tufOutputPath, metadataURI, targetsURI)
|
||||
```
|
||||
|
||||
1. configure an attestation resolver
|
||||
* using OCI registry
|
||||
```go
|
||||
var resolver oci.AttestationResolver
|
||||
resolver = &oci.RegistryResolver{
|
||||
Image: image, // path to image index in OCI registry containing image attestations (e.g. docker/nginx:latest)
|
||||
Platform: platform, // platform of subject image (image that attestations are being verified against)
|
||||
}
|
||||
```
|
||||
* using local OCI layout
|
||||
```go
|
||||
var resolver oci.AttestationResolver
|
||||
resolver = &oci.OCILayoutResolver{
|
||||
Path: path, // file path to OCI layout containing image attestations (e.g. /myimage)
|
||||
Platform: platform, // platform of subject image (image that attestations are being verified against)
|
||||
}
|
||||
```
|
||||
|
||||
1. configure policy options
|
||||
```go
|
||||
opts := &policy.PolicyOptions{
|
||||
TufClient: tufClient,
|
||||
LocalTargetsDir: "/.docker/policy", // location to store policy files downloaded from TUF
|
||||
LocalPolicyDir: "", // overrides TUF policy for local policy files
|
||||
}
|
||||
```
|
||||
|
||||
1. verify attestations
|
||||
```go
|
||||
policy, err := attest.Verify(ctx, opts, resolver)
|
||||
if err != nil {
|
||||
return false // failed policy or attestation signature verification
|
||||
}
|
||||
if policy {
|
||||
return true // passed policy
|
||||
}
|
||||
return true // no policy for image
|
||||
```
|
||||
See example [example_verify.go](./pkg/attest/example_verify.go)
|
||||
|
||||
## signing attestations
|
||||
1. generate an image with intoto Statements (optional)
|
||||
```sh
|
||||
docker buildx build <PATH TO DOCKERFILE> --sbom true --provenance true --output type=oci,tar=false,name=<REPO>:<TAG>,dest=<OUTPUT DIR>
|
||||
```
|
||||
See example [example_sign.go](./pkg/attest/example_sign.go)
|
||||
|
||||
1. confgiure a `dsse.SignerVerifier`
|
||||
```go
|
||||
var signer dsse.SignerVerifier
|
||||
signer, err = signerverifier.GetAWSSigner(cmd.Context(), aws_arn, aws_region)
|
||||
```
|
||||
## mirroring TUF repositories to OCI
|
||||
See example [example_mirror.go](./pkg/mirror/example_mirror.go)
|
||||
|
||||
1. configure signing options
|
||||
```go
|
||||
opts := &attest.SigningOptions{
|
||||
Replace: true, // replace unsigned intoto statements with signed intoto attestations, otherwise leave in place
|
||||
}
|
||||
```
|
||||
* add [Verification Summary Attestation (VSA)](https://slsa.dev/spec/v1.0/verification_summary) for all intoto attestations (optional)
|
||||
```go
|
||||
opts.VSAOptions = &attestation.VSAOptions{
|
||||
BuildLevel: "SLSA_BUILD_LEVEL_" + slsaBuildLevel,
|
||||
PolicyURI: slsaPolicyUri,
|
||||
VerifierID: slsaVerifierId,
|
||||
}
|
||||
```
|
||||
1. load attestations
|
||||
* oci registry
|
||||
```go
|
||||
ref := "docker/attest:latest"
|
||||
att, err := oci.AttestationIndexFromRemote(ref)
|
||||
```
|
||||
* local filepath
|
||||
```go
|
||||
path := "/test-image"
|
||||
att, err := oci.AttestationIndexFromPath(path)
|
||||
```
|
||||
|
||||
1. sign attestations
|
||||
```go
|
||||
signedImageIndex, err := attest.Sign(ctx, att, signer, opts)
|
||||
```
|
||||
`attest.Sign()` iterates over attestation manifests in the image index and signs all intoto statements (optionally generates a VSA), returning a mutated ImageIndex with all intoto statements signed as attestations.
|
||||
|
||||
1. save output (optional)
|
||||
* push to oci registry
|
||||
```go
|
||||
err = mirror.PushToRegistry(signedImageIndex, ref)
|
||||
```
|
||||
* save to local filesystem
|
||||
```go
|
||||
idx := v1.ImageIndex(empty.Index)
|
||||
idx = mutate.AppendManifests(idx, mutate.IndexAddendum{
|
||||
Add: signedImageIndex,
|
||||
Descriptor: v1.Descriptor{
|
||||
Annotations: map[string]string{
|
||||
oci.OciReferenceTarget: att.Name,
|
||||
},
|
||||
},
|
||||
})
|
||||
err = mirror.SaveAsOCILayout(idx, path)
|
||||
```
|
||||
|
||||
## mirroring TUF repositories
|
||||
TODO: write content for this outline
|
||||
### mirroring TUF metadata to OCI
|
||||
#### delegated metadata
|
||||
### mirroring TUF targets to OCI
|
||||
#### delegated targets
|
||||
### using `go-tuf` OCI registry client
|
||||
See example [example_registry](./pkg/tuf/example_registry.go)
|
||||
|
||||
78
pkg/attest/example_sign.go
Normal file
78
pkg/attest/example_sign.go
Normal file
@@ -0,0 +1,78 @@
|
||||
package attest
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"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"
|
||||
"github.com/google/go-containerregistry/pkg/v1/empty"
|
||||
"github.com/google/go-containerregistry/pkg/v1/mutate"
|
||||
)
|
||||
|
||||
func ExampleSign_remote() {
|
||||
// configure signerverifier
|
||||
// local signer (unsafe for production)
|
||||
signer, err := signerverifier.GenKeyPair()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// example using AWS KMS signer
|
||||
// aws_arn := "arn:aws:kms:us-west-2:123456789012:key/12345678-1234-1234-1234-123456789012"
|
||||
// aws_region := "us-west-2"
|
||||
// signer, err := signerverifier.GetAWSSigner(cmd.Context(), aws_arn, aws_region)
|
||||
|
||||
// configure signing options
|
||||
opts := &SigningOptions{
|
||||
Replace: true, // replace unsigned intoto statements with signed intoto attestations, otherwise leave in place
|
||||
}
|
||||
|
||||
// configure VSA options (optional)
|
||||
slsaBuildLevel := "3"
|
||||
slsaPolicyUri := "https://docker.com/attest/policy"
|
||||
slsaVerifierId := "https://docker.com"
|
||||
opts.VSAOptions = &attestation.VSAOptions{
|
||||
BuildLevel: "SLSA_BUILD_LEVEL_" + slsaBuildLevel,
|
||||
PolicyURI: slsaPolicyUri,
|
||||
VerifierID: slsaVerifierId,
|
||||
}
|
||||
|
||||
// load image index with unsigned attestation-manifests
|
||||
ref := "docker/image-signer-verifier:latest"
|
||||
att, err := oci.AttestationIndexFromRemote(ref)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// example for local image index
|
||||
// path := "/myimage"
|
||||
// att, err := oci.AttestationIndexFromLocal(path)
|
||||
|
||||
// sign attestations
|
||||
signedImageIndex, err := Sign(context.Background(), att.Index, signer, opts)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// push image index with signed attestation-manifests
|
||||
err = mirror.PushToRegistry(signedImageIndex, ref)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// output image index to filesystem (optional)
|
||||
path := "/myimage"
|
||||
idx := v1.ImageIndex(empty.Index)
|
||||
idx = mutate.AppendManifests(idx, mutate.IndexAddendum{
|
||||
Add: signedImageIndex,
|
||||
Descriptor: v1.Descriptor{
|
||||
Annotations: map[string]string{
|
||||
oci.OciReferenceTarget: att.Name,
|
||||
},
|
||||
},
|
||||
})
|
||||
err = mirror.SaveAsOCILayout(idx, path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
69
pkg/attest/example_verify.go
Normal file
69
pkg/attest/example_verify.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package attest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/docker/attest/internal/embed"
|
||||
"github.com/docker/attest/pkg/oci"
|
||||
"github.com/docker/attest/pkg/policy"
|
||||
"github.com/docker/attest/pkg/tuf"
|
||||
)
|
||||
|
||||
func createTufClient(outputPath string) (*tuf.TufClient, error) {
|
||||
// using oci tuf metadata and targets
|
||||
metadataURI := "regsitry-1.docker.io/docker/tuf-metadata:latest"
|
||||
targetsURI := "regsitry-1.docker.io/docker/tuf-targets"
|
||||
// example using http tuf metadata and targets
|
||||
// metadataURI := "https://docker.github.io/tuf-staging/metadata"
|
||||
// targetsURI := "https://docker.github.io/tuf-staging/targets"
|
||||
|
||||
return tuf.NewTufClient(embed.DefaultRoot, outputPath, metadataURI, targetsURI)
|
||||
}
|
||||
|
||||
func ExampleVerify_remote() {
|
||||
// create a tuf client
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
tufOutputPath := filepath.Join(home, ".docker", "tuf")
|
||||
tufClient, err := createTufClient(tufOutputPath)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// create a resolver for remote attestations
|
||||
image := "regsitry-1.docker.io/notary:server"
|
||||
platform := "linux/amd64"
|
||||
resolver := &oci.RegistryResolver{
|
||||
Image: image, // path to image index in OCI registry containing image attestations
|
||||
Platform: platform, // platform of subject image (image that attestations are being verified against)
|
||||
}
|
||||
// example using a local resolver
|
||||
// path := "/myimage"
|
||||
// platform := "linux/amd64"
|
||||
// resolver := &oci.OCILayoutResolver{
|
||||
// Path: path, // file path to OCI layout containing image attestations
|
||||
// Platform: platform, // platform of subject image (image that attestations are being verified against)
|
||||
// }
|
||||
|
||||
// configure policy options
|
||||
opts := &policy.PolicyOptions{
|
||||
TufClient: tufClient,
|
||||
LocalTargetsDir: filepath.Join(home, ".docker", "policy"), // location to store policy files downloaded from TUF
|
||||
LocalPolicyDir: "", // overrides TUF policy for local policy files if set
|
||||
}
|
||||
|
||||
// verify attestations
|
||||
policy, err := Verify(context.Background(), opts, resolver)
|
||||
if err != nil {
|
||||
panic(err) // failed policy or attestation signature verification
|
||||
}
|
||||
if policy {
|
||||
print("policy passed: %v\n", policy)
|
||||
return // passed policy
|
||||
}
|
||||
// no policy for image
|
||||
}
|
||||
150
pkg/mirror/example_mirror.go
Normal file
150
pkg/mirror/example_mirror.go
Normal file
@@ -0,0 +1,150 @@
|
||||
package mirror
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/attest/internal/embed"
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
)
|
||||
|
||||
type TufMirrorOutput struct {
|
||||
metadata *v1.Image
|
||||
delegatedMetadata []*MirrorImage
|
||||
targets []*MirrorImage
|
||||
delegatedTargets []*MirrorIndex
|
||||
}
|
||||
|
||||
func ExampleMirror() {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
tufOutputPath := filepath.Join(home, ".docker", "tuf")
|
||||
|
||||
// configure TUF mirror
|
||||
metadataURI := "https://docker.github.io/tuf-staging/metadata"
|
||||
targetsURI := "https://docker.github.io/tuf-staging/targets"
|
||||
m, err := NewTufMirror(embed.DefaultRoot, tufOutputPath, metadataURI, targetsURI)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// create metadata manifest
|
||||
metadataManifest, err := m.GetMetadataManifest(metadataURI)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// create delegated targets metadata manifests
|
||||
delegatedMetadata, err := m.GetDelegatedMetadataMirrors()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// create targets manifest
|
||||
targets, err := m.GetTufTargetMirrors()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// create delegated targets manifests
|
||||
delegatedTargets, err := m.GetDelegatedTargetMirrors()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
mirrorOutput := &TufMirrorOutput{
|
||||
metadata: metadataManifest,
|
||||
delegatedMetadata: delegatedMetadata,
|
||||
targets: targets,
|
||||
delegatedTargets: delegatedTargets,
|
||||
}
|
||||
|
||||
// push metadata and targets to registry (optional)
|
||||
err = mirrorToRegistry(mirrorOutput)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// save metadata and targets to local directory (optional)
|
||||
mirrorOutputPath := filepath.Join(home, ".docker", "tuf", "mirror")
|
||||
err = mirrorToLocal(mirrorOutput, mirrorOutputPath)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func mirrorToRegistry(o *TufMirrorOutput) error {
|
||||
// push metadata to registry
|
||||
metadataRepo := "registry-1.docker.io/docker/tuf-metadata:latest"
|
||||
err := PushToRegistry(o.metadata, metadataRepo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// push delegated metadata to registry
|
||||
for _, metadata := range o.delegatedMetadata {
|
||||
repo, _, ok := strings.Cut(metadataRepo, ":")
|
||||
if !ok {
|
||||
return fmt.Errorf("failed to get repo without tag: %s", metadataRepo)
|
||||
}
|
||||
imageName := fmt.Sprintf("%s:%s", repo, metadata.Tag)
|
||||
err = PushToRegistry(metadata.Image, imageName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// push top-level targets to registry
|
||||
targetsRepo := "registry-1.docker.io/docker/tuf-targets"
|
||||
for _, target := range o.targets {
|
||||
imageName := fmt.Sprintf("%s:%s", targetsRepo, target.Tag)
|
||||
err = PushToRegistry(target.Image, imageName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// push delegated targets to registry
|
||||
for _, target := range o.delegatedTargets {
|
||||
imageName := fmt.Sprintf("%s:%s", targetsRepo, target.Tag)
|
||||
err = PushToRegistry(target.Index, imageName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func mirrorToLocal(o *TufMirrorOutput, outputPath string) error {
|
||||
// output metadata to local directory
|
||||
err := SaveAsOCILayout(o.metadata, outputPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// output delegated metadata to local directory
|
||||
for _, metadata := range o.delegatedMetadata {
|
||||
path := filepath.Join(outputPath, metadata.Tag)
|
||||
err = SaveAsOCILayout(metadata.Image, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// output top-level targets to local directory
|
||||
for _, target := range o.targets {
|
||||
path := filepath.Join(outputPath, target.Tag)
|
||||
err = SaveAsOCILayout(target.Image, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// output delegated targets to local directory
|
||||
for _, target := range o.delegatedTargets {
|
||||
path := filepath.Join(outputPath, target.Tag)
|
||||
err = SaveAsOCILayout(target.Index, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
43
pkg/tuf/example_registry.go
Normal file
43
pkg/tuf/example_registry.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package tuf
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/docker/attest/internal/embed"
|
||||
"github.com/theupdateframework/go-tuf/v2/metadata"
|
||||
)
|
||||
|
||||
func ExampleTufRegistryClient() {
|
||||
// create a tuf client
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
tufOutputPath := filepath.Join(home, ".docker", "tuf")
|
||||
|
||||
// using oci tuf metadata and targets
|
||||
metadataURI := "regsitry-1.docker.io/docker/tuf-metadata:latest"
|
||||
targetsURI := "regsitry-1.docker.io/docker/tuf-targets"
|
||||
registryClient, err := NewTufClient(embed.DefaultRoot, tufOutputPath, metadataURI, targetsURI)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// get trusted tuf metadata
|
||||
trustedMetadata := registryClient.GetMetadata()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// top-level target files
|
||||
targets := trustedMetadata.Targets[metadata.TARGETS].Signed.Targets
|
||||
|
||||
for _, t := range targets {
|
||||
// download target files
|
||||
_, _, err := registryClient.DownloadTarget(t.Path, filepath.Join(tufOutputPath, "download"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user