Skip to content

Commit

Permalink
smarter scopes handling
Browse files Browse the repository at this point in the history
Signed-off-by: razzle <[email protected]>
  • Loading branch information
Noxsios committed Jul 8, 2023
1 parent dc30300 commit 0384928
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 34 deletions.
49 changes: 25 additions & 24 deletions src/pkg/oci/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"context"
"fmt"
"net/http"
"reflect"
"strings"

zarfconfig "github.com/defenseunicorns/zarf/src/config"
Expand All @@ -32,9 +33,10 @@ const (
type OrasRemote struct {
*remote.Repository
context.Context
Transport *utils.Transport
CopyOpts oras.CopyOptions
client *auth.Client
Transport *utils.Transport
CopyOpts oras.CopyOptions
client *auth.Client
hasCredentials bool
}

// NewOrasRemote returns an oras remote repository client and context for the given url.
Expand All @@ -53,13 +55,6 @@ func NewOrasRemote(url string) (*OrasRemote, error) {
return nil, err
}

if auth.GetScopes(o.Context) != nil {
err = o.CheckAuth()
if err != nil {
return nil, fmt.Errorf("unable to authenticate to %s: %s", ref.Registry, err.Error())
}
}

copyOpts := oras.DefaultCopyOptions
copyOpts.OnCopySkipped = o.printLayerSuccess
copyOpts.PostCopy = o.printLayerSuccess
Expand All @@ -70,6 +65,7 @@ func NewOrasRemote(url string) (*OrasRemote, error) {

// WithRepository sets the repository for the remote as well as the auth client.
func (o *OrasRemote) WithRepository(ref registry.Reference) error {
o.hasCredentials = false
// patch docker.io to registry-1.docker.io
// this allows end users to use docker.io as an alias for registry-1.docker.io
if ref.Registry == "docker.io" {
Expand All @@ -91,17 +87,6 @@ func (o *OrasRemote) WithRepository(ref registry.Reference) error {
return nil
}

// withScopes returns a context with the given scopes.
//
// This is needed for pushing to Docker Hub.
func withScopes(ref registry.Reference) context.Context {
// For pushing to Docker Hub, we need to set the scope to the repository with pull+push actions, otherwise a 401 is returned
scopes := []string{
fmt.Sprintf("repository:%s:pull,push", ref.Repository),
}
return auth.WithScopes(context.TODO(), scopes...)
}

// withAuthClient returns an auth client for the given reference.
//
// The credentials are pulled using Docker's default credential store.
Expand Down Expand Up @@ -130,7 +115,7 @@ func (o *OrasRemote) withAuthClient(ref registry.Reference) (*auth.Client, error
}

if authConf.ServerAddress != "" {
o.Context = withScopes(ref)
o.hasCredentials = true
}

cred := auth.Credential{
Expand Down Expand Up @@ -158,12 +143,28 @@ func (o *OrasRemote) withAuthClient(ref registry.Reference) (*auth.Client, error
}

// CheckAuth checks if the user is authenticated to the remote registry.
func (o *OrasRemote) CheckAuth() error {
func (o *OrasRemote) CheckAuth(scopes ...string) error {
// check if we've already checked the scopes
currentScopes := auth.GetScopes(o.Context)
equal := reflect.DeepEqual(currentScopes, scopes)
// if we've already checked the scopes and we dont have credentials, return
if equal && !o.hasCredentials {
return nil
}

// if we have credentials, add the scopes to the context
if o.hasCredentials {
o.Context = auth.WithScopes(o.Context, scopes...)
}
reg, err := remote.NewRegistry(o.Repository.Reference.Registry)
if err != nil {
return err
}
reg.PlainHTTP = zarfconfig.CommonOptions.Insecure
reg.Client = o.client
return reg.Ping(o.Context)
err = reg.Ping(o.Context)
if err == nil {
return fmt.Errorf("unable to authenticate to %s: %s", reg.Reference, err.Error())
}
return nil
}
21 changes: 21 additions & 0 deletions src/pkg/oci/pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,24 @@ import (
"oras.land/oras-go/v2"
"oras.land/oras-go/v2/content"
"oras.land/oras-go/v2/content/file"
"oras.land/oras-go/v2/registry/remote/auth"
)

var (
// AlwaysPull is a list of paths that will always be pulled from the remote repository.
AlwaysPull = []string{config.ZarfYAML, config.ZarfChecksumsTxt, config.ZarfYAMLSignature}
)

func (o *OrasRemote) checkPull() error {
scopes := auth.ScopeRepository(o.Reference.Registry, auth.ActionPull)
return o.CheckAuth(scopes)
}

// LayersFromPaths returns the descriptors for the given paths from the root manifest.
func (o *OrasRemote) LayersFromPaths(requestedPaths []string) (layers []ocispec.Descriptor, err error) {
if err := o.checkPull(); err != nil {
return nil, err
}
manifest, err := o.FetchRoot()
if err != nil {
return nil, err
Expand All @@ -49,6 +58,9 @@ func (o *OrasRemote) LayersFromPaths(requestedPaths []string) (layers []ocispec.
//
// It also respects the `required` flag on components, and will retrieve all necessary layers for required components.
func (o *OrasRemote) LayersFromRequestedComponents(requestedComponents []string) (layers []ocispec.Descriptor, err error) {
if err := o.checkPull(); err != nil {
return nil, err
}
root, err := o.FetchRoot()
if err != nil {
return nil, err
Expand Down Expand Up @@ -122,6 +134,9 @@ func (o *OrasRemote) LayersFromRequestedComponents(requestedComponents []string)
// - checksums.txt
// - zarf.yaml.sig
func (o *OrasRemote) PullPackage(destinationDir string, concurrency int, layersToPull ...ocispec.Descriptor) (partialPaths []string, err error) {
if err := o.checkPull(); err != nil {
return nil, err
}
isPartialPull := len(layersToPull) > 0
ref := o.Reference

Expand Down Expand Up @@ -206,6 +221,9 @@ func (o *OrasRemote) PullPackage(destinationDir string, concurrency int, layersT

// PullLayer pulls a layer from the remote repository and saves it to `destinationDir/annotationTitle`.
func (o *OrasRemote) PullLayer(desc ocispec.Descriptor, destinationDir string) error {
if err := o.checkPull(); err != nil {
return err
}
if desc.MediaType != ZarfLayerMediaTypeBlob {
return fmt.Errorf("invalid media type for file layer: %s", desc.MediaType)
}
Expand All @@ -218,6 +236,9 @@ func (o *OrasRemote) PullLayer(desc ocispec.Descriptor, destinationDir string) e

// PullPackageMetadata pulls the package metadata from the remote repository and saves it to `destinationDir`.
func (o *OrasRemote) PullPackageMetadata(destinationDir string) (err error) {
if err := o.checkPull(); err != nil {
return err
}
root, err := o.FetchRoot()
if err != nil {
return err
Expand Down
23 changes: 13 additions & 10 deletions src/pkg/oci/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"oras.land/oras-go/v2"
"oras.land/oras-go/v2/content"
"oras.land/oras-go/v2/content/file"
"oras.land/oras-go/v2/registry/remote/auth"
)

// ConfigPartial is a partial OCI config that is used to create the manifest config.
Expand All @@ -36,17 +37,16 @@ type ConfigPartial struct {
Annotations map[string]string `json:"annotations,omitempty"`
}

// PushFile pushes the file at the given path to the remote repository.
func (o *OrasRemote) PushFile(path string) (*ocispec.Descriptor, error) {
b, err := os.ReadFile(path)
if err != nil {
return nil, err
}
return o.PushBytes(b, ZarfLayerMediaTypeBlob)
func (o *OrasRemote) checkPush() error {
scopes := auth.ScopeRepository(o.Reference.Registry, auth.ActionPull, auth.ActionPush)
return o.CheckAuth(scopes)
}

// PushBytes pushes the given bytes to the remote repository.
func (o *OrasRemote) PushBytes(b []byte, mediaType string) (*ocispec.Descriptor, error) {
// PushLayer pushes the given layer (bytes) to the remote repository.
func (o *OrasRemote) PushLayer(b []byte, mediaType string) (*ocispec.Descriptor, error) {
if err := o.checkPush(); err != nil {
return nil, err
}
desc := content.NewDescriptorFromBytes(mediaType, b)
return &desc, o.Push(o.Context, desc, bytes.NewReader(b))
}
Expand All @@ -65,7 +65,7 @@ func (o *OrasRemote) pushManifestConfigFromMetadata(metadata *types.ZarfMetadata
if err != nil {
return nil, err
}
return o.PushBytes(manifestConfigBytes, ocispec.MediaTypeImageConfig)
return o.PushLayer(manifestConfigBytes, ocispec.MediaTypeImageConfig)
}

func (o *OrasRemote) manifestAnnotationsFromMetadata(metadata *types.ZarfMetadata) map[string]string {
Expand Down Expand Up @@ -111,6 +111,9 @@ func (o *OrasRemote) generatePackManifest(src *file.Store, descs []ocispec.Descr

// PublishPackage publishes the package to the remote repository.
func (o *OrasRemote) PublishPackage(pkg *types.ZarfPackage, sourceDir string, concurrency int) error {
if err := o.checkPush(); err != nil {
return err
}
ctx := o.Context
// source file store
src, err := file.New(sourceDir)
Expand Down
6 changes: 6 additions & 0 deletions src/pkg/oci/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ func ReferenceFromMetadata(registryLocation string, metadata *types.ZarfMetadata

// FetchRoot fetches the root manifest from the remote repository.
func (o *OrasRemote) FetchRoot() (*ZarfOCIManifest, error) {
if err := o.checkPull(); err != nil {
return nil, err
}
// get the manifest descriptor
descriptor, err := o.Resolve(o.Context, o.Reference.Reference)
if err != nil {
Expand Down Expand Up @@ -88,6 +91,9 @@ func (o *OrasRemote) FetchManifest(desc ocispec.Descriptor) (manifest *ZarfOCIMa

// FetchLayer fetches the layer with the given descriptor from the remote repository.
func (o *OrasRemote) FetchLayer(desc ocispec.Descriptor) (bytes []byte, err error) {
if err := o.checkPull(); err != nil {
return nil, err
}
return content.FetchAll(o.Context, o, desc)
}

Expand Down

0 comments on commit 0384928

Please sign in to comment.