refactor the logic and logging

This commit is contained in:
ejahnGithub 2024-08-05 12:25:52 -07:00
parent 20d3931427
commit 8d17896080
8 changed files with 120 additions and 128 deletions

View file

@ -54,14 +54,14 @@ func normalizeReference(reference string, pathSeparator rune) (normalized string
return filepath.Clean(reference), fileArtifactType, nil
}
func NewDigestedArtifact(client oci.Client, reference, digestAlg string) (artifact *DigestedArtifact, err error) {
func NewDigestedArtifact(client oci.Client, reference, digestAlg string, useBundleFromRegistry bool) (artifact *DigestedArtifact, err error) {
normalized, artifactType, err := normalizeReference(reference, os.PathSeparator)
if err != nil {
return nil, err
}
if artifactType == ociArtifactType {
// TODO: should we allow custom digestAlg for OCI artifacts?
return digestContainerImageArtifact(normalized, client)
return digestContainerImageArtifact(normalized, client, useBundleFromRegistry)
}
return digestLocalFileArtifact(normalized, digestAlg)
}

View file

@ -7,7 +7,7 @@ import (
"github.com/distribution/reference"
)
func digestContainerImageArtifact(url string, client oci.Client) (*DigestedArtifact, error) {
func digestContainerImageArtifact(url string, client oci.Client, useBundleFromRegistry bool) (*DigestedArtifact, error) {
// try to parse the url as a valid registry reference
named, err := reference.Parse(url)
if err != nil {
@ -15,15 +15,38 @@ func digestContainerImageArtifact(url string, client oci.Client) (*DigestedArtif
return nil, fmt.Errorf("artifact %s is not a valid registry reference: %v", url, err)
}
digest, attestations, err := client.GetImageDigest(named.String())
name, err := client.ParseReference(named.String())
if err != nil {
return nil, err
}
digest, err := client.GetImageDigest(name)
if err != nil {
return nil, err
}
if useBundleFromRegistry {
attestations, err := client.GetAttestations(name, digest)
if err != nil {
return nil, err
}
return &DigestedArtifact{
URL: fmt.Sprintf("oci://%s", named.String()),
digest: digest.Hex,
digestAlg: digest.Algorithm,
attestations: attestations,
}, nil
}
if err != nil {
return nil, err
}
return &DigestedArtifact{
URL: fmt.Sprintf("oci://%s", named.String()),
digest: digest.Hex,
digestAlg: digest.Algorithm,
attestations: attestations,
URL: fmt.Sprintf("oci://%s", named.String()),
digest: digest.Hex,
digestAlg: digest.Algorithm,
}, nil
}

View file

@ -1,7 +1,6 @@
package oci
import (
"bytes"
"errors"
"fmt"
"io"
@ -21,7 +20,9 @@ var ErrDenied = errors.New("the provided token was denied access to the requeste
var ErrRegistryAuthz = errors.New("remote registry authorization failed, please authenticate with the registry and try again")
type Client interface {
GetImageDigest(imgName string) (*v1.Hash, []*api.Attestation, error)
GetImageDigest(name name.Reference) (*v1.Hash, error)
GetAttestations(name name.Reference, digest *v1.Hash) ([]*api.Attestation, error)
ParseReference(ref string) (name.Reference, error)
}
func checkForUnauthorizedOrDeniedErr(err transport.Error) error {
@ -41,36 +42,40 @@ type LiveClient struct {
get func(name.Reference, ...remote.Option) (*remote.Descriptor, error)
}
func (c LiveClient) ParseReference(ref string) (name.Reference, error) {
return c.parseReference(ref)
}
// where name is formed like ghcr.io/github/my-image-repo
func (c LiveClient) GetImageDigest(imgName string) (*v1.Hash, []*api.Attestation, error) {
nameFirst, err := c.parseReference(imgName)
var buf bytes.Buffer
attestations := []*api.Attestation{}
if err != nil {
return nil, attestations, fmt.Errorf("failed to create image tag: %v", err)
}
func (c LiveClient) GetImageDigest(name name.Reference) (*v1.Hash, error) {
// The user must already be authenticated with the container registry
// The authn.DefaultKeychain argument indicates that Get should checks the
// user's configuration for the registry credentials
desc, err := c.get(nameFirst, remote.WithAuthFromKeychain(authn.DefaultKeychain))
desc, err := c.get(name, remote.WithAuthFromKeychain(authn.DefaultKeychain))
if err != nil {
var transportErr *transport.Error
if errors.As(err, &transportErr) {
if accessErr := checkForUnauthorizedOrDeniedErr(*transportErr); accessErr != nil {
return nil, attestations, accessErr
return nil, accessErr
}
}
return nil, attestations, fmt.Errorf("failed to fetch remote image: %v", err)
return nil, fmt.Errorf("failed to fetch remote image: %v", err)
}
dgst := nameFirst.Context().Digest(desc.Digest.String())
return &desc.Digest, nil
}
ref, err := remote.Referrers(dgst, remote.WithAuthFromKeychain(authn.DefaultKeychain))
func (c LiveClient) GetAttestations(name name.Reference, digest *v1.Hash) ([]*api.Attestation, error) {
attestations := []*api.Attestation{}
nameDegist := name.Context().Digest(digest.String())
ref, err := remote.Referrers(nameDegist, remote.WithAuthFromKeychain(authn.DefaultKeychain))
if err != nil {
return attestations, fmt.Errorf("failed to fetch remote image: %v", err)
}
indexManifest, err := ref.IndexManifest()
if err != nil {
return nil, attestations, fmt.Errorf("failed to fetch remote image: %v", err)
return attestations, fmt.Errorf("failed to fetch remote image: %v", err)
}
manifests := indexManifest.Manifests
@ -81,94 +86,55 @@ func (c LiveClient) GetImageDigest(imgName string) (*v1.Hash, []*api.Attestation
if allowedType == m.ArtifactType {
manifestDigest := m.Digest.String()
manifestURL := fmt.Sprintf("%s/manifests/%s", imgName, manifestDigest)
fmt.Println(manifestURL)
digest2 := nameFirst.Context().Digest(manifestDigest)
digest2 := nameDegist.Context().Digest(manifestDigest)
// replace to use GET for more correc type
img2, err := remote.Image(digest2, remote.WithAuthFromKeychain(authn.DefaultKeychain))
if err != nil {
return nil, attestations, fmt.Errorf("failed to fetch remote image: %v", err)
return attestations, fmt.Errorf("failed to fetch remote image: %v", err)
}
// manifest2, err := ref2.Manifest()
// if err != nil {
// return nil, fmt.Errorf("failed to fetch remote image: %v", err)
// }
// fmt.Println(manifest2.MediaType)
// Step 4: Get the layers
layers, err := img2.Layers()
if err != nil {
fmt.Println("Error getting layers: %v", err)
return nil, attestations, fmt.Errorf("failed to fetch remote image: %v", err)
return attestations, fmt.Errorf("failed to fetch remote image: %v", err)
}
// For simplicity, we'll just fetch the first layer
if len(layers) > 0 {
fmt.Println("how many layers?", len(layers))
layer := layers[0]
// Step 5: Read the blob (layer) content
rc, err := layer.Compressed()
if err != nil {
fmt.Println("Error getting compressed layer: %v", err)
return nil, attestations, fmt.Errorf("failed to fetch remote image: %v", err)
return attestations, fmt.Errorf("failed to fetch remote image: %v", err)
}
defer rc.Close()
layerBytes, err := io.ReadAll(rc)
if err != nil {
// If creating a gzip reader fails, it might not be compressed
fmt.Println("Layer is not gzip-compressed. Reading raw content.")
// fmt.Println("Blob content:", buf.String())
var bundle bundle.ProtobufBundle
bundle.Bundle = new(protobundle.Bundle)
err = bundle.UnmarshalJSON(layerBytes)
fmt.Println("")
fmt.Println("JSON Content:", string(layerBytes))
fmt.Println("")
if err != nil {
fmt.Println("failed to unmarshal bundle from JSON: %v", err)
} else {
fmt.Println("Bundle content:", bundle.String())
}
a := api.Attestation{Bundle: &bundle}
attestations = append(attestations, &a)
} else {
defer gz.Close()
var decompressed bytes.Buffer
if _, err := io.Copy(&decompressed, gz); err != nil {
fmt.Println("Error decompressing layer content: %v", err)
}
// Now you have the decompressed blob content in 'decompressed' buffer
fmt.Println("Decompressed blob content:", decompressed.String())
return attestations, fmt.Errorf("failed to fetch remote image: %v", err)
}
// Now you have the decompressed blob content in 'decompressed' buffer
// fmt.Println("Blob content:", decompressed.String())
var bundle bundle.ProtobufBundle
bundle.Bundle = new(protobundle.Bundle)
err = bundle.UnmarshalJSON(layerBytes)
if err != nil {
return attestations, fmt.Errorf("failed to fetch remote image: %v", err)
}
a := api.Attestation{Bundle: &bundle}
attestations = append(attestations, &a)
} else {
fmt.Println("No layers found in the image.")
return attestations, fmt.Errorf("failed to fetch remote image: %v", err)
}
}
}
}
// msgName, err := c.parseReference(msg)
// fmt.Println()
// if err != nil {
// return nil, err
// }
return &desc.Digest, attestations, nil
return attestations, nil
}
// Unlike other parts of this command set, we cannot pass a custom HTTP client

View file

@ -111,7 +111,7 @@ func NewDownloadCmd(f *cmdutil.Factory, runF func(*Options) error) *cobra.Comman
}
func runDownload(opts *Options) error {
artifact, err := artifact.NewDigestedArtifact(opts.OCIClient, opts.ArtifactPath, opts.DigestAlgorithm)
artifact, err := artifact.NewDigestedArtifact(opts.OCIClient, opts.ArtifactPath, opts.DigestAlgorithm, false)
if err != nil {
return fmt.Errorf("failed to digest artifact: %v", err)
}

View file

@ -97,7 +97,7 @@ func NewInspectCmd(f *cmdutil.Factory, runF func(*Options) error) *cobra.Command
}
func runInspect(opts *Options) error {
artifact, err := artifact.NewDigestedArtifact(opts.OCIClient, opts.ArtifactPath, opts.DigestAlgorithm)
artifact, err := artifact.NewDigestedArtifact(opts.OCIClient, opts.ArtifactPath, opts.DigestAlgorithm, false)
if err != nil {
return fmt.Errorf("failed to digest artifact: %s", err)
}

View file

@ -16,12 +16,13 @@ import (
var ErrUnrecognisedBundleExtension = errors.New("bundle file extension not supported, must be json or jsonl")
type FetchAttestationsConfig struct {
APIClient api.Client
BundlePath string
Digest string
Limit int
Owner string
Repo string
APIClient api.Client
BundlePath string
Digest string
Limit int
Owner string
Repo string
AttestationsFromOCI []*api.Attestation
}
func (c *FetchAttestationsConfig) IsBundleProvided() bool {
@ -32,6 +33,11 @@ func GetAttestations(c FetchAttestationsConfig) ([]*api.Attestation, error) {
if c.IsBundleProvided() {
return GetLocalAttestations(c.BundlePath)
}
if len(c.AttestationsFromOCI) > 0 {
return c.AttestationsFromOCI, nil
}
return GetRemoteAttestations(c)
}

View file

@ -15,27 +15,28 @@ import (
// Options captures the options for the verify command
type Options struct {
ArtifactPath string
BundlePath string
Config func() (gh.Config, error)
TrustedRoot string
DenySelfHostedRunner bool
DigestAlgorithm string
Limit int
NoPublicGood bool
OIDCIssuer string
Owner string
PredicateType string
Repo string
SAN string
SANRegex string
SignerRepo string
SignerWorkflow string
APIClient api.Client
Logger *io.Handler
OCIClient oci.Client
SigstoreVerifier verification.SigstoreVerifier
exporter cmdutil.Exporter
ArtifactPath string
BundlePath string
UseBundleFromRegistry bool
Config func() (gh.Config, error)
TrustedRoot string
DenySelfHostedRunner bool
DigestAlgorithm string
Limit int
NoPublicGood bool
OIDCIssuer string
Owner string
PredicateType string
Repo string
SAN string
SANRegex string
SignerRepo string
SignerWorkflow string
APIClient api.Client
Logger *io.Handler
OCIClient oci.Client
SigstoreVerifier verification.SigstoreVerifier
exporter cmdutil.Exporter
}
// Clean cleans the file path option values

View file

@ -150,6 +150,7 @@ func NewVerifyCmd(f *cmdutil.Factory, runF func(*Options) error) *cobra.Command
// general flags
verifyCmd.Flags().StringVarP(&opts.BundlePath, "bundle", "b", "", "Path to bundle on disk, either a single bundle in a JSON file or a JSON lines file with multiple bundles")
cmdutil.DisableAuthCheckFlag(verifyCmd.Flags().Lookup("bundle"))
verifyCmd.Flags().BoolVarP(&opts.UseBundleFromRegistry, "bundle-from-registry", "", false, "Use the bundle from the OCI registry")
cmdutil.StringEnumFlag(verifyCmd, &opts.DigestAlgorithm, "digest-alg", "d", "sha256", []string{"sha256", "sha512"}, "The algorithm used to compute a digest of the artifact")
verifyCmd.Flags().StringVarP(&opts.Owner, "owner", "o", "", "GitHub organization to scope attestation lookup by")
verifyCmd.Flags().StringVarP(&opts.Repo, "repo", "R", "", "Repository name in the format <owner>/<repo>")
@ -173,7 +174,7 @@ func NewVerifyCmd(f *cmdutil.Factory, runF func(*Options) error) *cobra.Command
}
func runVerify(opts *Options) error {
artifact, err := artifact.NewDigestedArtifact(opts.OCIClient, opts.ArtifactPath, opts.DigestAlgorithm)
artifact, err := artifact.NewDigestedArtifact(opts.OCIClient, opts.ArtifactPath, opts.DigestAlgorithm, opts.UseBundleFromRegistry)
if err != nil {
opts.Logger.Printf(opts.Logger.ColorScheme.Red("✗ Loading digest for %s failed\n"), opts.ArtifactPath)
return err
@ -182,12 +183,13 @@ func runVerify(opts *Options) error {
opts.Logger.Printf("Loaded digest %s for %s\n", artifact.DigestWithAlg(), artifact.URL)
c := verification.FetchAttestationsConfig{
APIClient: opts.APIClient,
BundlePath: opts.BundlePath,
Digest: artifact.DigestWithAlg(),
Limit: opts.Limit,
Owner: opts.Owner,
Repo: opts.Repo,
APIClient: opts.APIClient,
BundlePath: opts.BundlePath,
Digest: artifact.DigestWithAlg(),
Limit: opts.Limit,
Owner: opts.Owner,
Repo: opts.Repo,
AttestationsFromOCI: artifact.Attestations(),
}
attestations, err := verification.GetAttestations(c)
if err != nil {
@ -198,25 +200,19 @@ func runVerify(opts *Options) error {
if c.IsBundleProvided() {
opts.Logger.Printf(opts.Logger.ColorScheme.Red("✗ Loading attestations from %s failed\n"), artifact.URL)
} else if opts.UseBundleFromRegistry {
opts.Logger.Println(opts.Logger.ColorScheme.Red("✗ Loading attestations from OCI registry failed"))
} else {
opts.Logger.Println(opts.Logger.ColorScheme.Red("✗ Loading attestations from GitHub API failed"))
}
return err
}
attestationsFromOCI := artifact.Attestations()
if len(attestationsFromOCI) > 0 {
attestations = append(attestations, attestationsFromOCI...)
for _, attestation := range attestations {
// ja, err := attestation.Bundle.String()
opts.Logger.Printf("Loaded attestation from OCI registry: %s\n", attestation.Bundle.String())
}
}
pluralAttestation := text.Pluralize(len(attestations), "attestation")
if c.IsBundleProvided() {
opts.Logger.Printf("Loaded %s from %s\n", pluralAttestation, opts.BundlePath)
} else if opts.UseBundleFromRegistry {
opts.Logger.Printf("Loaded %s from %s\n", pluralAttestation, opts.ArtifactPath)
} else {
opts.Logger.Printf("Loaded %s from GitHub API\n", pluralAttestation)
}