Skip to content

Commit

Permalink
fix!: Propagate Docker auths to BuildKit (#1820)
Browse files Browse the repository at this point in the history
Reviewed-by: Cezar Craciunoiu <[email protected]>
Approved-by: Cezar Craciunoiu <[email protected]>
  • Loading branch information
craciunoiuc authored Aug 5, 2024
2 parents ab0553e + e4b302a commit 2e45210
Show file tree
Hide file tree
Showing 7 changed files with 159 additions and 131 deletions.
111 changes: 111 additions & 0 deletions config/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,34 @@ 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 (
DefaultManifestIndex = "https://manifests.kraftkit.sh/index.yaml"
)

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")
Expand Down Expand Up @@ -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
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
41 changes: 41 additions & 0 deletions initrd/dockerfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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")
}
1 change: 0 additions & 1 deletion oci/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ func RegisterPackageManager() func(u *packmanager.UmbrellaManager) error {
return u.RegisterPackageManager(
OCIFormat,
NewOCIManager,
WithDefaultAuth(),
WithDefaultRegistries(),
WithDetectHandler(),
)
Expand Down
10 changes: 2 additions & 8 deletions oci/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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()
}
Expand Down
105 changes: 0 additions & 105 deletions oci/manager_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
Loading

0 comments on commit 2e45210

Please sign in to comment.