Skip to content

Commit

Permalink
🧹 Refactor auth opts for registries. Pass through opts when pulling r…
Browse files Browse the repository at this point in the history
…egistry images (#3738)

* 🧹 Refactor auth opts for registries. Pass through options when pulling registry images.

Signed-off-by: Preslav <[email protected]>

* Add defaults for GetImageDescriptor and LoadImage.

---------

Signed-off-by: Preslav <[email protected]>
  • Loading branch information
preslavgerchev authored Apr 9, 2024
1 parent 46664a5 commit 1e1dc6f
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 221 deletions.
71 changes: 71 additions & 0 deletions providers/os/connection/container/auth/options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright (c) Mondoo, Inc.
// SPDX-License-Identifier: BUSL-1.1

package auth

import (
"crypto/tls"
"net"
"net/http"
"time"

"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/v1/remote"
"github.com/rs/zerolog/log"
"go.mondoo.com/cnquery/v10/logger"
"go.mondoo.com/cnquery/v10/providers-sdk/v1/vault"
)

func TransportOption(insecure bool) remote.Option {
tr := &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
DualStack: true,
}).DialContext,
ForceAttemptHTTP2: true,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}

if insecure {
tr.TLSClientConfig = &tls.Config{
InsecureSkipVerify: true,
}
}
return remote.WithTransport(tr)
}

func AuthOption(ref string, credentials []*vault.Credential) remote.Option {
for i := range credentials {
cred := credentials[i]
switch cred.Type {
case vault.CredentialType_password:
log.Debug().Msg("add password authentication")
cfg := authn.AuthConfig{
Username: cred.User,
Password: string(cred.Secret),
}
return remote.WithAuth((authn.FromConfig(cfg)))
case vault.CredentialType_bearer:
log.Debug().Str("token", string(cred.Secret)).Msg("add bearer authentication")
cfg := authn.AuthConfig{
Username: cred.User,
RegistryToken: string(cred.Secret),
}
return remote.WithAuth((authn.FromConfig(cfg)))
default:
log.Warn().Msg("unknown credentials for container image")
logger.DebugJSON(credentials)
}
}
log.Debug().Msg("no credentials for container image, falling back to default auth")
return remote.WithAuthFromKeychain(ConstructKeychain(ref))
}

func DefaultOpts(ref string, insecure bool) []remote.Option {
return []remote.Option{AuthOption(ref, nil), TransportOption(insecure)}
}
131 changes: 8 additions & 123 deletions providers/os/connection/container/image/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,139 +4,24 @@
package image

import (
"crypto/tls"
"net"
"net/http"
"time"

"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/name"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/remote"
"github.com/rs/zerolog/log"
"go.mondoo.com/cnquery/v10/logger"
"go.mondoo.com/cnquery/v10/providers-sdk/v1/vault"
"go.mondoo.com/cnquery/v10/providers/os/connection/container/auth"
)

// Option is a functional option
// see https://www.sohamkamani.com/golang/options-pattern/
type Option func(*options) error

type options struct {
insecure bool
auth authn.Authenticator
}

func WithInsecure(insecure bool) Option {
return func(o *options) error {
o.insecure = insecure
return nil
func GetImageDescriptor(ref name.Reference, opts ...remote.Option) (*remote.Descriptor, error) {
if len(opts) == 0 {
opts = auth.DefaultOpts(ref.Name(), false)
}
return remote.Get(ref, opts...)
}

func WithAuthenticator(auth authn.Authenticator) Option {
return func(o *options) error {
o.auth = auth
return nil
func LoadImageFromRegistry(ref name.Reference, opts ...remote.Option) (v1.Image, error) {
if len(opts) == 0 {
opts = auth.DefaultOpts(ref.Name(), false)
}
}

func AuthOption(credentials []*vault.Credential) []Option {
remoteOpts := []Option{}
for i := range credentials {
cred := credentials[i]
switch cred.Type {
case vault.CredentialType_password:
log.Debug().Msg("add password authentication")
cfg := authn.AuthConfig{
Username: cred.User,
Password: string(cred.Secret),
}
remoteOpts = append(remoteOpts, WithAuthenticator((authn.FromConfig(cfg))))
case vault.CredentialType_bearer:
log.Debug().Str("token", string(cred.Secret)).Msg("add bearer authentication")
cfg := authn.AuthConfig{
Username: cred.User,
RegistryToken: string(cred.Secret),
}
remoteOpts = append(remoteOpts, WithAuthenticator((authn.FromConfig(cfg))))
default:
log.Warn().Msg("unknown credentials for container image")
logger.DebugJSON(credentials)
}
}
return remoteOpts
}

func DefaultAuthOpts(ref name.Reference) (authn.Authenticator, error) {
kc := auth.ConstructKeychain(ref.Name())
return kc.Resolve(ref.Context())
}

func GetImageDescriptor(ref name.Reference, opts ...Option) (*remote.Descriptor, error) {
o := &options{
insecure: false,
}

for _, option := range opts {
if err := option(o); err != nil {
return nil, err
}
}

if o.auth == nil {
auth, err := DefaultAuthOpts(ref)
if err != nil {
return nil, err
}
o.auth = auth
}

return remote.Get(ref, remote.WithAuth(o.auth))
}

func LoadImageFromRegistry(ref name.Reference, opts ...Option) (v1.Image, error) {
o := &options{
insecure: false,
}

for _, option := range opts {
if err := option(o); err != nil {
return nil, err
}
}

if o.auth == nil {
auth, err := DefaultAuthOpts(ref)
if err != nil {
return nil, err
}
o.auth = auth
}

// mimic http.DefaultTransport
tr := &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
DualStack: true,
}).DialContext,
ForceAttemptHTTP2: true,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}

if o.insecure {
tr.TLSClientConfig = &tls.Config{
InsecureSkipVerify: true,
}
}

img, err := remote.Image(ref, remote.WithAuth(o.auth), remote.WithTransport(tr))
img, err := remote.Image(ref, opts...)
if err != nil {
return nil, err
}
Expand Down
7 changes: 3 additions & 4 deletions providers/os/connection/container/image_connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@ import (
"github.com/google/go-containerregistry/pkg/name"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/mutate"
"github.com/google/go-containerregistry/pkg/v1/remote"
"github.com/google/go-containerregistry/pkg/v1/tarball"
"github.com/rs/zerolog/log"
"go.mondoo.com/cnquery/v10/providers-sdk/v1/inventory"
"go.mondoo.com/cnquery/v10/providers-sdk/v1/plugin"
"go.mondoo.com/cnquery/v10/providers/os/connection/container/auth"
"go.mondoo.com/cnquery/v10/providers/os/connection/container/image"
"go.mondoo.com/cnquery/v10/providers/os/connection/tar"
"go.mondoo.com/cnquery/v10/providers/os/id/containerid"
Expand Down Expand Up @@ -61,10 +63,7 @@ func NewRegistryImage(id uint32, conf *inventory.Config, asset *inventory.Asset)
}
log.Debug().Str("ref", ref.Name()).Msg("found valid container registry reference")

registryOpts := []image.Option{image.WithInsecure(conf.Insecure)}
remoteOpts := image.AuthOption(conf.Credentials)
registryOpts = append(registryOpts, remoteOpts...)

registryOpts := []remote.Option{auth.TransportOption(conf.Insecure), auth.AuthOption(ref.Name(), conf.Credentials)}
img, err := image.LoadImageFromRegistry(ref, registryOpts...)
if err != nil {
return nil, err
Expand Down
59 changes: 8 additions & 51 deletions providers/os/resources/discovery/container_registry/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,8 @@
package container_registry

import (
"crypto/tls"
"fmt"
"net"
"net/http"
"net/url"
"time"

"github.com/cockroachdb/errors"
"github.com/google/go-containerregistry/pkg/name"
Expand All @@ -30,57 +26,17 @@ func NewContainerRegistryResolver(opts ...remote.Option) *DockerRegistryImages {
}

type DockerRegistryImages struct {
opts []remote.Option
Insecure bool
DisableKeychainAuth bool
}

func (a *DockerRegistryImages) DefaultAuth(name string) remote.Option {
kcs := auth.ConstructKeychain(name)
return remote.WithAuthFromKeychain(kcs)
}

func (a *DockerRegistryImages) InsecureTransportOpt() remote.Option {
// NOTE: config to get remote running with an insecure registry, we need to override the TLSClientConfig
tr := &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
DualStack: true,
}).DialContext,
ForceAttemptHTTP2: true,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
}
return remote.WithTransport(tr)
}

func (a *DockerRegistryImages) DefaultOpts(name string) []remote.Option {
options := []remote.Option{}
// does not work with bearer auth, therefore it need to be disabled when other remote auth options are used
// TODO: we should implement this a bit differently
if !a.DisableKeychainAuth {
options = append(options, a.DefaultAuth(name))
}
if a.Insecure {
options = append(options, a.InsecureTransportOpt())
}

return options
opts []remote.Option
Insecure bool
}

func (a *DockerRegistryImages) remoteOptions(name string) []remote.Option {
// either use the provided options or the default options
if len(a.opts) > 0 {
return a.opts
}
return a.DefaultOpts(name)
log.Debug().Str("name", name).Msg("using default remote options")
return auth.DefaultOpts(name, a.Insecure)
}

func (a *DockerRegistryImages) Repositories(reg name.Registry) ([]string, error) {
Expand Down Expand Up @@ -150,7 +106,8 @@ func (a *DockerRegistryImages) ListRepository(repoName string) ([]*inventory.Ass
}

// fetch tags
tags, err := remote.List(repo, a.remoteOptions(repo.Name())...)
opts := a.remoteOptions(repo.Name())
tags, err := remote.List(repo, opts...)
if err != nil {
return nil, handleUnauthorizedError(err, repo.Name())
}
Expand All @@ -164,7 +121,7 @@ func (a *DockerRegistryImages) ListRepository(repoName string) ([]*inventory.Ass
return nil, fmt.Errorf("parsing reference %q: %v", repoWithTag, err)
}

a, err := a.toAsset(ref, nil)
a, err := a.toAsset(ref, nil, opts...)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -205,7 +162,7 @@ func (a *DockerRegistryImages) GetImage(ref name.Reference, creds []*vault.Crede
}

func (a *DockerRegistryImages) toAsset(ref name.Reference, creds []*vault.Credential, opts ...remote.Option) (*inventory.Asset, error) {
desc, err := image.GetImageDescriptor(ref, image.AuthOption(creds)...)
desc, err := image.GetImageDescriptor(ref, opts...)
if err != nil {
return nil, handleUnauthorizedError(err, ref.Name())
}
Expand Down
Loading

0 comments on commit 1e1dc6f

Please sign in to comment.