refactor the logic and logging
This commit is contained in:
parent
20d3931427
commit
8d17896080
8 changed files with 120 additions and 128 deletions
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue