diff --git a/cli/docker.go b/cli/docker.go index a6b4b97..7d1e3aa 100644 --- a/cli/docker.go +++ b/cli/docker.go @@ -15,6 +15,7 @@ import ( dockertypes "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" + "github.com/google/go-containerregistry/pkg/name" "github.com/spf13/cobra" "golang.org/x/exp/slices" "golang.org/x/xerrors" @@ -98,6 +99,7 @@ var ( EnvMemory = "CODER_MEMORY" EnvAddGPU = "CODER_ADD_GPU" EnvUsrLibDir = "CODER_USR_LIB_DIR" + EnvDockerConfig = "CODER_DOCKER_CONFIG" EnvDebug = "CODER_DEBUG" EnvDisableIDMappedMount = "CODER_DISABLE_IDMAPPED_MOUNT" ) @@ -133,6 +135,7 @@ type flags struct { boostrapScript string containerMounts string hostUsrLibDir string + dockerConfig string cpus int memory int disableIDMappedMount bool @@ -336,6 +339,7 @@ func dockerCmd() *cobra.Command { cliflag.StringVarP(cmd.Flags(), &flags.boostrapScript, "boostrap-script", "", EnvBootstrap, "", "The script to use to bootstrap the container. This should typically install and start the agent.") cliflag.StringVarP(cmd.Flags(), &flags.containerMounts, "mounts", "", EnvMounts, "", "Comma separated list of mounts in the form of ':[:options]' (e.g. /var/lib/docker:/var/lib/docker:ro,/usr/src:/usr/src).") cliflag.StringVarP(cmd.Flags(), &flags.hostUsrLibDir, "usr-lib-dir", "", EnvUsrLibDir, "", "The host /usr/lib mountpoint. Used to detect GPU drivers to mount into inner container.") + cliflag.StringVarP(cmd.Flags(), &flags.dockerConfig, "docker-config", "", EnvDockerConfig, "/root/.docker/config.json", "The path to the docker config to consult when pulling an image.") cliflag.BoolVarP(cmd.Flags(), &flags.addTUN, "add-tun", "", EnvAddTun, false, "Add a TUN device to the inner container.") cliflag.BoolVarP(cmd.Flags(), &flags.addFUSE, "add-fuse", "", EnvAddFuse, false, "Add a FUSE device to the inner container.") cliflag.BoolVarP(cmd.Flags(), &flags.addGPU, "add-gpu", "", EnvAddGPU, false, "Add detected GPUs to the inner container.") @@ -369,6 +373,19 @@ func runDockerCVM(ctx context.Context, log slog.Logger, client dockerutil.Docker } } + log.Info(ctx, "checking for docker config file", slog.F("path", flags.dockerConfig)) + if _, err := fs.Stat(flags.dockerConfig); err == nil { + log.Info(ctx, "detected file", slog.F("image", flags.innerImage)) + ref, err := name.NewTag(flags.innerImage) + if err != nil { + return xerrors.Errorf("parse ref: %w", err) + } + dockerAuth, err = dockerutil.AuthConfigFromPath(flags.dockerConfig, ref.RegistryStr()) + if err != nil && !xerrors.Is(err, os.ErrNotExist) { + return xerrors.Errorf("auth config from file: %w", err) + } + } + envs := defaultContainerEnvs(ctx, flags.agentToken) innerEnvsTokens := strings.Split(flags.innerEnvs, ",") diff --git a/dockerutil/client.go b/dockerutil/client.go index 99b1c1b..7e97f71 100644 --- a/dockerutil/client.go +++ b/dockerutil/client.go @@ -4,9 +4,12 @@ import ( "context" "encoding/base64" "encoding/json" + "os" + "github.com/cpuguy83/dockercfg" dockertypes "github.com/docker/docker/api/types" dockerclient "github.com/docker/docker/client" + "golang.org/x/xerrors" ) @@ -45,6 +48,39 @@ func (a AuthConfig) Base64() (string, error) { return base64.URLEncoding.EncodeToString(authStr), nil } +func AuthConfigFromPath(path string, registry string) (AuthConfig, error) { + var config dockercfg.Config + err := dockercfg.FromFile(path, &config) + if err != nil { + return AuthConfig{}, xerrors.Errorf("load config: %w", err) + } + + hostname := dockercfg.ResolveRegistryHost(registry) + + if config, ok := config.AuthConfigs[registry]; ok { + return AuthConfig(config), nil + } + + username, secret, err := config.GetRegistryCredentials(hostname) + if err != nil { + return AuthConfig{}, xerrors.Errorf("get credentials from helper: %w", err) + } + + if secret != "" { + if username == "" { + return AuthConfig{ + IdentityToken: secret, + }, nil + } + return AuthConfig{ + Username: username, + Password: secret, + }, nil + } + + return AuthConfig{}, xerrors.Errorf("no auth config found for registry %s: %w", registry, os.ErrNotExist) +} + func ParseAuthConfig(raw string) (AuthConfig, error) { type dockerConfig struct { AuthConfigs map[string]dockertypes.AuthConfig `json:"auths"` diff --git a/go.mod b/go.mod index 0ea7206..ad9b03e 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,9 @@ require ( cdr.dev/slog v1.5.4 github.com/coder/coder v0.27.3 github.com/coder/retry v1.4.0 + github.com/cpuguy83/dockercfg v0.3.1 github.com/docker/docker v23.0.8+incompatible + github.com/google/go-containerregistry v0.9.0 github.com/opencontainers/image-spec v1.1.0-rc2 github.com/ory/dockertest/v3 v3.10.0 github.com/quasilyte/go-ruleguard/dsl v0.3.22 @@ -121,7 +123,6 @@ require ( github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/moby/sys/mountinfo v0.6.2 // indirect github.com/moby/term v0.5.0 // indirect - github.com/morikuni/aec v1.0.0 // indirect github.com/muesli/reflow v0.3.0 // indirect github.com/muesli/termenv v0.15.1 // indirect github.com/open-policy-agent/opa v0.51.0 // indirect diff --git a/go.sum b/go.sum index 49ae56b..fc49115 100644 --- a/go.sum +++ b/go.sum @@ -155,6 +155,8 @@ github.com/coreos/go-iptables v0.6.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFE github.com/coreos/go-oidc/v3 v3.6.0 h1:AKVxfYw1Gmkn/w96z0DbT/B/xFnzTd3MkZvWLjF4n/o= github.com/coreos/go-oidc/v3 v3.6.0/go.mod h1:ZpHUsHBucTUj6WOkrP4E20UPynbLZzhTQ1XKCXkxyPc= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= +github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= @@ -322,6 +324,8 @@ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-containerregistry v0.9.0 h1:5Ths7RjxyFV0huKChQTgY6fLzvHhZMpLTFNja8U0/0w= +github.com/google/go-containerregistry v0.9.0/go.mod h1:9eq4BnSufyT1kHNffX+vSXVonaJ7yaIOulrKZejMxnQ= github.com/google/go-github/v43 v43.0.1-0.20220414155304-00e42332e405 h1:DdHws/YnnPrSywrjNYu2lEHqYHWp/LnEx56w59esd54= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -516,7 +520,6 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= -github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=