docs: update examples in README.md

This commit is contained in:
mrjoelkamp
2024-05-02 13:35:57 -05:00
parent f6245405ee
commit 3701942bf1
5 changed files with 345 additions and 120 deletions

125
README.md
View File

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

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

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

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

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