Deprecation: config: remove support for old ~/.dockercfg

The `~/.dockercfg` file was replaced by `~/.docker/config.json` in 2015
(github.com/docker/docker/commit/18c9b6c6455f116ae59cde8544413b3d7d294a5e),
but the CLI still falls back to checking if this file exists if no current
(`~/.docker/config.json`) file was found.

Given that no version of the CLI since Docker v1.7.0 has created this file,
and if such a file exists, it means someone hasn't re-authenticated for
5 years, it's probably safe to remove this fallback.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
Sebastiaan van Stijn 2020-05-07 19:37:16 +02:00
parent 59449a57f8
commit ee218fa89e
No known key found for this signature in database
GPG Key ID: 76698F39D527CE8C
4 changed files with 12 additions and 224 deletions

View File

@ -19,7 +19,7 @@ const (
// ConfigFileName is the name of config file // ConfigFileName is the name of config file
ConfigFileName = "config.json" ConfigFileName = "config.json"
configFileDir = ".docker" configFileDir = ".docker"
oldConfigfile = ".dockercfg" oldConfigfile = ".dockercfg" // Deprecated: remove once we stop printing deprecation warning
contextsDir = "contexts" contextsDir = "contexts"
) )
@ -84,16 +84,6 @@ func Path(p ...string) (string, error) {
return path, nil return path, nil
} }
// LegacyLoadFromReader is a convenience function that creates a ConfigFile object from
// a non-nested reader
func LegacyLoadFromReader(configData io.Reader) (*configfile.ConfigFile, error) {
configFile := configfile.ConfigFile{
AuthConfigs: make(map[string]types.AuthConfig),
}
err := configFile.LegacyLoadFromReader(configData)
return &configFile, err
}
// LoadFromReader is a convenience function that creates a ConfigFile object from // LoadFromReader is a convenience function that creates a ConfigFile object from
// a reader // a reader
func LoadFromReader(configData io.Reader) (*configfile.ConfigFile, error) { func LoadFromReader(configData io.Reader) (*configfile.ConfigFile, error) {
@ -140,12 +130,8 @@ func load(configDir string) (*configfile.ConfigFile, bool, error) {
// Can't find latest config file so check for the old one // Can't find latest config file so check for the old one
filename = filepath.Join(getHomeDir(), oldConfigfile) filename = filepath.Join(getHomeDir(), oldConfigfile)
if file, err := os.Open(filename); err == nil { if _, err := os.Stat(filename); err == nil {
printLegacyFileWarning = true printLegacyFileWarning = true
defer file.Close()
if err := configFile.LegacyLoadFromReader(file); err != nil {
return configFile, printLegacyFileWarning, errors.Wrap(err, filename)
}
} }
return configFile, printLegacyFileWarning, nil return configFile, printLegacyFileWarning, nil
} }
@ -158,7 +144,7 @@ func LoadDefaultConfigFile(stderr io.Writer) *configfile.ConfigFile {
fmt.Fprintf(stderr, "WARNING: Error loading config file: %v\n", err) fmt.Fprintf(stderr, "WARNING: Error loading config file: %v\n", err)
} }
if printLegacyFileWarning { if printLegacyFileWarning {
_, _ = fmt.Fprintln(stderr, "WARNING: Support for the legacy ~/.dockercfg configuration file and file-format is deprecated and will be removed in an upcoming release") _, _ = fmt.Fprintln(stderr, "WARNING: Support for the legacy ~/.dockercfg configuration file and file-format has been removed and the configuration file will be ignored")
} }
if !configFile.ContainsAuth() { if !configFile.ContainsAuth() {
configFile.CredentialsStore = credentials.DetectDefaultStore(configFile.CredentialsStore) configFile.CredentialsStore = credentials.DetectDefaultStore(configFile.CredentialsStore)

View File

@ -96,115 +96,6 @@ func TestEmptyJSON(t *testing.T) {
saveConfigAndValidateNewFormat(t, config, tmpHome) saveConfigAndValidateNewFormat(t, config, tmpHome)
} }
func TestOldInvalidsAuth(t *testing.T) {
invalids := map[string]string{
`username = test`: "The Auth config file is empty",
`username
password`: "Invalid Auth config file",
`username = test
email`: "Invalid auth configuration file",
}
resetHomeDir()
tmpHome := t.TempDir()
defer env.Patch(t, homeKey, tmpHome)()
for content, expectedError := range invalids {
fn := filepath.Join(tmpHome, oldConfigfile)
err := os.WriteFile(fn, []byte(content), 0600)
assert.NilError(t, err)
_, err = Load(tmpHome)
assert.ErrorContains(t, err, expectedError)
}
}
func TestOldValidAuth(t *testing.T) {
resetHomeDir()
tmpHome := t.TempDir()
defer env.Patch(t, homeKey, tmpHome)()
fn := filepath.Join(tmpHome, oldConfigfile)
js := `username = am9lam9lOmhlbGxv
email = user@example.com`
err := os.WriteFile(fn, []byte(js), 0600)
assert.NilError(t, err)
config, err := Load(tmpHome)
assert.NilError(t, err)
// defaultIndexserver is https://index.docker.io/v1/
ac := config.AuthConfigs["https://index.docker.io/v1/"]
assert.Equal(t, ac.Username, "joejoe")
assert.Equal(t, ac.Password, "hello")
// Now save it and make sure it shows up in new form
configStr := saveConfigAndValidateNewFormat(t, config, tmpHome)
expConfStr := `{
"auths": {
"https://index.docker.io/v1/": {
"auth": "am9lam9lOmhlbGxv"
}
}
}`
assert.Check(t, is.Equal(expConfStr, configStr))
}
func TestOldJSONInvalid(t *testing.T) {
resetHomeDir()
tmpHome := t.TempDir()
defer env.Patch(t, homeKey, tmpHome)()
fn := filepath.Join(tmpHome, oldConfigfile)
js := `{"https://index.docker.io/v1/":{"auth":"test","email":"user@example.com"}}`
if err := os.WriteFile(fn, []byte(js), 0600); err != nil {
t.Fatal(err)
}
config, err := Load(tmpHome)
// Use Contains instead of == since the file name will change each time
if err == nil || !strings.Contains(err.Error(), "Invalid auth configuration file") {
t.Fatalf("Expected an error got : %v, %v", config, err)
}
}
func TestOldJSON(t *testing.T) {
resetHomeDir()
tmpHome := t.TempDir()
defer env.Patch(t, homeKey, tmpHome)()
fn := filepath.Join(tmpHome, oldConfigfile)
js := `{"https://index.docker.io/v1/":{"auth":"am9lam9lOmhlbGxv","email":"user@example.com"}}`
if err := os.WriteFile(fn, []byte(js), 0600); err != nil {
t.Fatal(err)
}
config, err := Load(tmpHome)
assert.NilError(t, err)
ac := config.AuthConfigs["https://index.docker.io/v1/"]
assert.Equal(t, ac.Username, "joejoe")
assert.Equal(t, ac.Password, "hello")
// Now save it and make sure it shows up in new form
configStr := saveConfigAndValidateNewFormat(t, config, tmpHome)
expConfStr := `{
"auths": {
"https://index.docker.io/v1/": {
"auth": "am9lam9lOmhlbGxv",
"email": "user@example.com"
}
}
}`
if configStr != expConfStr {
t.Fatalf("Should have save in new form: \n'%s'\n not \n'%s'\n", configStr, expConfStr)
}
}
func TestOldJSONFallbackDeprecationWarning(t *testing.T) { func TestOldJSONFallbackDeprecationWarning(t *testing.T) {
js := `{"https://index.docker.io/v1/":{"auth":"am9lam9lOmhlbGxv","email":"user@example.com"}}` js := `{"https://index.docker.io/v1/":{"auth":"am9lam9lOmhlbGxv","email":"user@example.com"}}`
tmpHome := fs.NewDir(t, t.Name(), fs.WithFile(oldConfigfile, js)) tmpHome := fs.NewDir(t, t.Name(), fs.WithFile(oldConfigfile, js))
@ -218,15 +109,8 @@ func TestOldJSONFallbackDeprecationWarning(t *testing.T) {
buffer := new(bytes.Buffer) buffer := new(bytes.Buffer)
configFile := LoadDefaultConfigFile(buffer) configFile := LoadDefaultConfigFile(buffer)
expected := configfile.New(tmpHome.Join(configFileDir, ConfigFileName)) expected := configfile.New(tmpHome.Join(configFileDir, ConfigFileName))
expected.AuthConfigs = map[string]types.AuthConfig{ expected.AuthConfigs = map[string]types.AuthConfig{}
"https://index.docker.io/v1/": { assert.Assert(t, strings.Contains(buffer.String(), "WARNING: Support for the legacy ~/.dockercfg configuration file and file-format has been removed and the configuration file will be ignored"))
Username: "joejoe",
Password: "hello",
Email: "user@example.com",
ServerAddress: "https://index.docker.io/v1/",
},
}
assert.Assert(t, strings.Contains(buffer.String(), "WARNING: Support for the legacy ~/.dockercfg configuration file and file-format is deprecated and will be removed in an upcoming release"))
assert.Check(t, is.DeepEqual(expected, configFile)) assert.Check(t, is.DeepEqual(expected, configFile))
} }
@ -418,17 +302,6 @@ func TestJSONReaderNoFile(t *testing.T) {
assert.Equal(t, ac.Password, "hello") assert.Equal(t, ac.Password, "hello")
} }
func TestOldJSONReaderNoFile(t *testing.T) {
js := `{"https://index.docker.io/v1/":{"auth":"am9lam9lOmhlbGxv","email":"user@example.com"}}`
config, err := LegacyLoadFromReader(strings.NewReader(js))
assert.NilError(t, err)
ac := config.AuthConfigs["https://index.docker.io/v1/"]
assert.Equal(t, ac.Username, "joejoe")
assert.Equal(t, ac.Password, "hello")
}
func TestJSONWithPsFormatNoFile(t *testing.T) { func TestJSONWithPsFormatNoFile(t *testing.T) {
js := `{ js := `{
"auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } }, "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } },
@ -474,36 +347,6 @@ func TestJSONSaveWithNoFile(t *testing.T) {
} }
} }
func TestLegacyJSONSaveWithNoFile(t *testing.T) {
js := `{"https://index.docker.io/v1/":{"auth":"am9lam9lOmhlbGxv","email":"user@example.com"}}`
config, err := LegacyLoadFromReader(strings.NewReader(js))
assert.NilError(t, err)
err = config.Save()
assert.ErrorContains(t, err, "with empty filename")
tmpHome := t.TempDir()
fn := filepath.Join(tmpHome, ConfigFileName)
f, _ := os.OpenFile(fn, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
defer f.Close()
assert.NilError(t, config.SaveToWriter(f))
buf, err := os.ReadFile(filepath.Join(tmpHome, ConfigFileName))
assert.NilError(t, err)
expConfStr := `{
"auths": {
"https://index.docker.io/v1/": {
"auth": "am9lam9lOmhlbGxv",
"email": "user@example.com"
}
}
}`
if string(buf) != expConfStr {
t.Fatalf("Should have save in new form: \n%s\n not \n%s", string(buf), expConfStr)
}
}
func TestLoadDefaultConfigFile(t *testing.T) { func TestLoadDefaultConfigFile(t *testing.T) {
dir := setupConfigDir(t) dir := setupConfigDir(t)
buffer := new(bytes.Buffer) buffer := new(bytes.Buffer)

View File

@ -14,13 +14,6 @@ import (
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
const (
// This constant is only used for really old config files when the
// URL wasn't saved as part of the config file and it was just
// assumed to be this value.
defaultIndexServer = "https://index.docker.io/v1/"
)
// ConfigFile ~/.docker/config.json file info // ConfigFile ~/.docker/config.json file info
type ConfigFile struct { type ConfigFile struct {
AuthConfigs map[string]types.AuthConfig `json:"auths"` AuthConfigs map[string]types.AuthConfig `json:"auths"`
@ -71,44 +64,6 @@ func New(fn string) *ConfigFile {
} }
} }
// LegacyLoadFromReader reads the non-nested configuration data given and sets up the
// auth config information with given directory and populates the receiver object
func (configFile *ConfigFile) LegacyLoadFromReader(configData io.Reader) error {
b, err := io.ReadAll(configData)
if err != nil {
return err
}
if err := json.Unmarshal(b, &configFile.AuthConfigs); err != nil {
arr := strings.Split(string(b), "\n")
if len(arr) < 2 {
return errors.Errorf("The Auth config file is empty")
}
authConfig := types.AuthConfig{}
origAuth := strings.Split(arr[0], " = ")
if len(origAuth) != 2 {
return errors.Errorf("Invalid Auth config file")
}
authConfig.Username, authConfig.Password, err = decodeAuth(origAuth[1])
if err != nil {
return err
}
authConfig.ServerAddress = defaultIndexServer
configFile.AuthConfigs[defaultIndexServer] = authConfig
} else {
for k, authConfig := range configFile.AuthConfigs {
authConfig.Username, authConfig.Password, err = decodeAuth(authConfig.Auth)
if err != nil {
return err
}
authConfig.Auth = ""
authConfig.ServerAddress = k
configFile.AuthConfigs[k] = authConfig
}
}
return nil
}
// LoadFromReader reads the configuration data given and sets up the auth config // LoadFromReader reads the configuration data given and sets up the auth config
// information with given directory and populates the receiver object // information with given directory and populates the receiver object
func (configFile *ConfigFile) LoadFromReader(configData io.Reader) error { func (configFile *ConfigFile) LoadFromReader(configData io.Reader) error {

View File

@ -59,7 +59,7 @@ Removed | [Linux containers on Windows (LCOW)](#linux-containers-on-windows-l
Deprecated | [BLKIO weight options with cgroups v1](#blkio-weight-options-with-cgroups-v1) | v20.10 | - Deprecated | [BLKIO weight options with cgroups v1](#blkio-weight-options-with-cgroups-v1) | v20.10 | -
Deprecated | [Kernel memory limit](#kernel-memory-limit) | v20.10 | - Deprecated | [Kernel memory limit](#kernel-memory-limit) | v20.10 | -
Deprecated | [Classic Swarm and overlay networks using external key/value stores](#classic-swarm-and-overlay-networks-using-cluster-store) | v20.10 | - Deprecated | [Classic Swarm and overlay networks using external key/value stores](#classic-swarm-and-overlay-networks-using-cluster-store) | v20.10 | -
Deprecated | [Support for the legacy `~/.dockercfg` configuration file for authentication](#support-for-legacy-dockercfg-configuration-files) | v20.10 | - Removed | [Support for the legacy `~/.dockercfg` configuration file for authentication](#support-for-legacy-dockercfg-configuration-files) | v20.10 | v21.xx
Deprecated | [CLI plugins support](#cli-plugins-support) | v20.10 | - Deprecated | [CLI plugins support](#cli-plugins-support) | v20.10 | -
Deprecated | [Dockerfile legacy `ENV name value` syntax](#dockerfile-legacy-env-name-value-syntax) | v20.10 | - Deprecated | [Dockerfile legacy `ENV name value` syntax](#dockerfile-legacy-env-name-value-syntax) | v20.10 | -
Removed | [`docker build --stream` flag (experimental)](#docker-build---stream-flag-experimental) | v20.10 | v20.10 Removed | [`docker build --stream` flag (experimental)](#docker-build---stream-flag-experimental) | v20.10 | v20.10
@ -298,6 +298,7 @@ deprecated, and will be disabled or removed in a future release.
### Support for legacy `~/.dockercfg` configuration files ### Support for legacy `~/.dockercfg` configuration files
**Deprecated in Release: v20.10** **Deprecated in Release: v20.10**
**Removed in Release: v21.xx**
The docker CLI up until v1.7.0 used the `~/.dockercfg` file to store credentials The docker CLI up until v1.7.0 used the `~/.dockercfg` file to store credentials
after authenticating to a registry (`docker login`). Docker v1.7.0 replaced this after authenticating to a registry (`docker login`). Docker v1.7.0 replaced this
@ -307,8 +308,11 @@ as a fall-back, to assist existing users with migrating to the new file.
Given that the old file format encourages insecure storage of credentials Given that the old file format encourages insecure storage of credentials
(credentials are stored unencrypted), and that no version of the CLI since (credentials are stored unencrypted), and that no version of the CLI since
Docker v1.7.0 has created this file, the file is marked deprecated, and support Docker v1.7.0 has created this file, support for this file, and its format has
for this file will be removed in a future release. been removed.
A warning is printed in situations where the CLI would fall back to the old file,
notifying the user that the legacy file is present, but ignored.
### Configuration options for experimental CLI features ### Configuration options for experimental CLI features