diff --git a/config/defaults.go b/config/defaults.go index 838a5f966..6e6576dcf 100644 --- a/config/defaults.go +++ b/config/defaults.go @@ -6,9 +6,15 @@ package config import ( "fmt" + "net/url" + "os" "path/filepath" "reflect" "strconv" + + cliconfig "github.com/docker/cli/cli/config" + "github.com/docker/cli/cli/config/configfile" + "github.com/mitchellh/go-homedir" ) const ( @@ -16,12 +22,18 @@ const ( ) func NewDefaultKraftKitConfig() (*KraftKit, error) { + var err error c := &KraftKit{} if err := setDefaults(c); err != nil { return nil, fmt.Errorf("could not set defaults for config: %s", err) } + c.Auth, err = defaultAuths() + if err != nil { + return nil, fmt.Errorf("could not get default auths: %s", err) + } + // Add default path for plugins.. if len(c.Paths.Plugins) == 0 { c.Paths.Plugins = filepath.Join(DataDir(), "plugins") @@ -119,3 +131,102 @@ func setDefaultValue(v reflect.Value, def string) error { return nil } + +// defaultAuths uses the provided context to locate possible authentication +// values which can be used when speaking with remote registries. +func defaultAuths() (map[string]AuthConfig, error) { + auths := make(map[string]AuthConfig) + + // Podman users may have their container registry auth configured in a + // different location, that Docker packages aren't aware of. + // If the Docker config file isn't found, we'll fallback to look where + // Podman configures it, and parse that as a Docker auth config instead. + + // First, check $HOME/.docker/ + var home string + var err error + var configPath string + foundDockerConfig := false + + // If this is run in the context of GitHub actions, use an alternative path + // for the $HOME. + if os.Getenv("GITUB_ACTION") == "yes" { + home = "/github/home" + } else { + home, err = homedir.Dir() + } + if err == nil { + foundDockerConfig = fileExists(filepath.Join(home, ".docker", "config.json")) + + if foundDockerConfig { + configPath = filepath.Join(home, ".docker") + } + } + + // If $HOME/.docker/config.json isn't found, check $DOCKER_CONFIG (if set) + if !foundDockerConfig && os.Getenv("DOCKER_CONFIG") != "" { + foundDockerConfig = fileExists(filepath.Join(os.Getenv("DOCKER_CONFIG"), "config.json")) + + if foundDockerConfig { + configPath = os.Getenv("DOCKER_CONFIG") + } + } + + // If either of those locations are found, load it using Docker's + // config.Load, which may fail if the config can't be parsed. + // + // If neither was found, look for Podman's auth at + // $XDG_RUNTIME_DIR/containers/auth.json and attempt to load it as a + // Docker config. + var cf *configfile.ConfigFile + if foundDockerConfig { + cf, err = cliconfig.Load(configPath) + if err != nil { + return nil, err + } + } else if f, err := os.Open(filepath.Join(os.Getenv("XDG_RUNTIME_DIR"), "containers", "auth.json")); err == nil { + defer f.Close() + + cf, err = cliconfig.LoadFromReader(f) + if err != nil { + return nil, err + } + } + + if cf != nil { + a, err := cf.GetAllCredentials() + if err != nil { + return nil, err + } + + for domain, cfg := range a { + if cfg.Username == "" && cfg.Password == "" { + continue + } + + purl, err := url.Parse(domain) + if err != nil { + return nil, err + } + + u := purl.Host + if u == "" { + domain = purl.Path // Sometimes occurs with ghcr.io + } + if u == "" { + u = cfg.ServerAddress + } + if u == "" { + u = domain + } + + auths[u] = AuthConfig{ + Endpoint: u, + User: cfg.Username, + Token: cfg.Password, + } + } + } + + return auths, nil +} diff --git a/go.mod b/go.mod index 9bd1f62c2..c0d60231f 100644 --- a/go.mod +++ b/go.mod @@ -74,6 +74,7 @@ require ( golang.org/x/sync v0.7.0 golang.org/x/sys v0.22.0 golang.org/x/term v0.22.0 + google.golang.org/grpc v1.62.2 gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 k8s.io/api v0.30.3 @@ -273,7 +274,6 @@ require ( google.golang.org/genproto v0.0.0-20240311173647-c811ad7063a7 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect - google.golang.org/grpc v1.62.2 // indirect google.golang.org/protobuf v1.34.2 // indirect gopkg.in/go-jose/go-jose.v2 v2.6.3 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/initrd/dockerfile.go b/initrd/dockerfile.go index 0e7c33b2e..79b54280c 100644 --- a/initrd/dockerfile.go +++ b/initrd/dockerfile.go @@ -15,6 +15,9 @@ import ( "strings" "golang.org/x/sync/errgroup" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" "kraftkit.sh/config" "kraftkit.sh/cpio" "kraftkit.sh/log" @@ -23,6 +26,8 @@ import ( soci "github.com/anchore/stereoscope/pkg/image/oci" "github.com/moby/buildkit/client" "github.com/moby/buildkit/identity" + "github.com/moby/buildkit/session" + "github.com/moby/buildkit/session/auth" "github.com/moby/buildkit/session/filesync" "github.com/moby/buildkit/util/progress/progressui" "github.com/testcontainers/testcontainers-go" @@ -279,6 +284,11 @@ func (initrd *dockerfile) Build(ctx context.Context) (string, error) { solveOpt := &client.SolveOpt{ Ref: identity.NewID(), + Session: []session.Attachable{ + &buildkitAuthProvider{ + config.G[config.KraftKit](ctx).Auth, + }, + }, Exports: []client.ExportEntry{ { Type: client.ExporterTar, @@ -522,3 +532,34 @@ func (initrd *dockerfile) Env() []string { func (initrd *dockerfile) Args() []string { return initrd.args } + +type buildkitAuthProvider struct { + auths map[string]config.AuthConfig +} + +func (ap *buildkitAuthProvider) Register(server *grpc.Server) { + auth.RegisterAuthServer(server, ap) +} + +func (ap *buildkitAuthProvider) Credentials(ctx context.Context, req *auth.CredentialsRequest) (*auth.CredentialsResponse, error) { + res := &auth.CredentialsResponse{} + + if a, ok := ap.auths[req.Host]; ok { + res.Username = a.User + res.Secret = a.Token + } + + return res, nil +} + +func (ap *buildkitAuthProvider) FetchToken(ctx context.Context, req *auth.FetchTokenRequest) (*auth.FetchTokenResponse, error) { + return nil, status.Errorf(codes.Unavailable, "client side tokens disabled") +} + +func (ap *buildkitAuthProvider) GetTokenAuthority(ctx context.Context, req *auth.GetTokenAuthorityRequest) (*auth.GetTokenAuthorityResponse, error) { + return nil, status.Errorf(codes.Unavailable, "client side tokens disabled") +} + +func (ap *buildkitAuthProvider) VerifyTokenAuthority(ctx context.Context, req *auth.VerifyTokenAuthorityRequest) (*auth.VerifyTokenAuthorityResponse, error) { + return nil, status.Errorf(codes.Unavailable, "client side tokens disabled") +} diff --git a/oci/init.go b/oci/init.go index e844d7903..db6844d6b 100644 --- a/oci/init.go +++ b/oci/init.go @@ -13,7 +13,6 @@ func RegisterPackageManager() func(u *packmanager.UmbrellaManager) error { return u.RegisterPackageManager( OCIFormat, NewOCIManager, - WithDefaultAuth(), WithDefaultRegistries(), WithDetectHandler(), ) diff --git a/oci/manager.go b/oci/manager.go index a0ca533d6..8b5ac2e02 100644 --- a/oci/manager.go +++ b/oci/manager.go @@ -95,10 +95,7 @@ func (manager *ociManager) update(ctx context.Context, auths map[string]config.A } if auths == nil { - auths, err = defaultAuths(ctx) - if err != nil { - return nil, fmt.Errorf("accessing credentials: %w", err) - } + auths = config.G[config.KraftKit](ctx).Auth } packs := make(map[string]pack.Package) @@ -420,10 +417,7 @@ func (manager *ociManager) Catalog(ctx context.Context, qopts ...packmanager.Que var auths map[string]config.AuthConfig if query.Auths() == nil { - auths, err = defaultAuths(ctx) - if err != nil { - return nil, fmt.Errorf("could not access credentials: %w", err) - } + auths = config.G[config.KraftKit](ctx).Auth } else { auths = query.Auths() } diff --git a/oci/manager_options.go b/oci/manager_options.go index f439497d4..7e877cee2 100644 --- a/oci/manager_options.go +++ b/oci/manager_options.go @@ -15,12 +15,9 @@ import ( "kraftkit.sh/log" "kraftkit.sh/oci/handler" - cliconfig "github.com/docker/cli/cli/config" - "github.com/docker/cli/cli/config/configfile" regtypes "github.com/docker/docker/api/types/registry" "github.com/google/go-containerregistry/pkg/name" "github.com/google/go-containerregistry/pkg/v1/remote/transport" - "github.com/mitchellh/go-homedir" ) type OCIManagerOption func(context.Context, *ociManager) error @@ -145,108 +142,6 @@ func WithDefaultRegistries() OCIManagerOption { } } -// fileExists returns true if the given path exists and is not a directory. -func fileExists(path string) bool { - fi, err := os.Stat(path) - return err == nil && !fi.IsDir() -} - -// defaultAuths uses the provided context to locate possible authentication -// values which can be used when speaking with remote registries. -func defaultAuths(ctx context.Context) (map[string]config.AuthConfig, error) { - auths := make(map[string]config.AuthConfig) - - // Podman users may have their container registry auth configured in a - // different location, that Docker packages aren't aware of. - // If the Docker config file isn't found, we'll fallback to look where - // Podman configures it, and parse that as a Docker auth config instead. - - // First, check $HOME/.docker/ - var home string - var err error - var configPath string - foundDockerConfig := false - - // If this is run in the context of GitHub actions, use an alternative path - // for the $HOME. - if os.Getenv("GITUB_ACTION") == "yes" { - home = "/github/home" - } else { - home, err = homedir.Dir() - } - if err == nil { - foundDockerConfig = fileExists(filepath.Join(home, ".docker", "config.json")) - - if foundDockerConfig { - configPath = filepath.Join(home, ".docker") - } - } - - // If $HOME/.docker/config.json isn't found, check $DOCKER_CONFIG (if set) - if !foundDockerConfig && os.Getenv("DOCKER_CONFIG") != "" { - foundDockerConfig = fileExists(filepath.Join(os.Getenv("DOCKER_CONFIG"), "config.json")) - - if foundDockerConfig { - configPath = os.Getenv("DOCKER_CONFIG") - } - } - - // If either of those locations are found, load it using Docker's - // config.Load, which may fail if the config can't be parsed. - // - // If neither was found, look for Podman's auth at - // $XDG_RUNTIME_DIR/containers/auth.json and attempt to load it as a - // Docker config. - var cf *configfile.ConfigFile - if foundDockerConfig { - cf, err = cliconfig.Load(configPath) - if err != nil { - return nil, err - } - } else if f, err := os.Open(filepath.Join(os.Getenv("XDG_RUNTIME_DIR"), "containers", "auth.json")); err == nil { - defer f.Close() - - cf, err = cliconfig.LoadFromReader(f) - if err != nil { - return nil, err - } - } - - if cf != nil { - for domain, cfg := range cf.AuthConfigs { - if cfg.Username == "" && cfg.Password == "" { - continue - } - auths[domain] = config.AuthConfig{ - Endpoint: cfg.ServerAddress, - User: cfg.Username, - Token: cfg.Password, - } - } - } - - for domain, auth := range config.G[config.KraftKit](ctx).Auth { - auths[domain] = auth - } - - return auths, nil -} - -// WithDefaultAuth uses the KraftKit-set configuration for authentication -// against remote registries. -func WithDefaultAuth() OCIManagerOption { - return func(ctx context.Context, manager *ociManager) error { - var err error - - manager.auths, err = defaultAuths(ctx) - if err != nil { - return err - } - - return nil - } -} - // WithRegistries sets the list of registries to use when making calls to // non-canonically named OCI references. func WithRegistries(registries ...string) OCIManagerOption { diff --git a/oci/pack.go b/oci/pack.go index 8c2c3c7ad..7ff8be422 100644 --- a/oci/pack.go +++ b/oci/pack.go @@ -126,11 +126,6 @@ func NewPackageFromTarget(ctx context.Context, targ target.Target, opts ...packm return nil, fmt.Errorf("could not parse image reference: %w", err) } - auths, err := defaultAuths(ctx) - if err != nil { - return nil, err - } - if contAddr := config.G[config.KraftKit](ctx).ContainerdAddr; len(contAddr) > 0 { namespace := DefaultNamespace if n := os.Getenv("CONTAINERD_NAMESPACE"); n != "" { @@ -142,7 +137,7 @@ func NewPackageFromTarget(ctx context.Context, targ target.Target, opts ...packm WithField("namespace", namespace). Debug("packaging via containerd") - ctx, ocipack.handle, err = handler.NewContainerdHandler(ctx, contAddr, namespace, auths) + ctx, ocipack.handle, err = handler.NewContainerdHandler(ctx, contAddr, namespace, config.G[config.KraftKit](ctx).Auth) } else { if gerr := os.MkdirAll(config.G[config.KraftKit](ctx).RuntimeDir, fs.ModeSetgid|0o775); gerr != nil { return nil, fmt.Errorf("could not create local oci cache directory: %w", gerr) @@ -154,7 +149,7 @@ func NewPackageFromTarget(ctx context.Context, targ target.Target, opts ...packm WithField("path", ociDir). Trace("directory handler") - ocipack.handle, err = handler.NewDirectoryHandler(ociDir, auths) + ocipack.handle, err = handler.NewDirectoryHandler(ociDir, config.G[config.KraftKit](ctx).Auth) } if err != nil { return nil, err @@ -443,10 +438,7 @@ func newIndexAndManifestFromRemoteDigest(ctx context.Context, handle handler.Han } if auths == nil { - auths, err = defaultAuths(ctx) - if err != nil { - return nil, nil, fmt.Errorf("could not gather authentication details") - } + auths = config.G[config.KraftKit](ctx).Auth } var retManifest *Manifest @@ -761,13 +753,9 @@ func (ocipack *ociPackage) Push(ctx context.Context, opts ...pack.PushOption) er authConfig := &authn.AuthConfig{} transport := http.DefaultTransport.(*http.Transport).Clone() - auths, err := defaultAuths(ctx) - if err != nil { - return err - } // Annoyingly convert between regtypes and authn. - if auth, ok := auths[ocipack.ref.Context().RegistryStr()]; ok { + if auth, ok := config.G[config.KraftKit](ctx).Auth[ocipack.ref.Context().RegistryStr()]; ok { authConfig.Username = auth.User authConfig.Password = auth.Token