Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🧹 Refactor auth opts for registries. Pass through opts when pulling registry images #3738

Merged
merged 2 commits into from
Apr 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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...)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pass opts thru to ensure these are respected

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
Loading