saving configuration file is not forward-compatible
thaJeztah opened this issue · 0 comments
Description
The config/configfile package always uses the current schema of the config-file to save the file to disk. This can be problematic, as there's many code-bases that use this code to authenticate (and store credentials), and those code-bases may be using an older version of the code. For example, go-containerregistry up until recently (previous patch release) used v24.0 as dependency;
https://github.com/google/go-containerregistry/blob/v0.20.1/go.mod#L7
Those libraries will continue to work, because they're only using the "auths"
field from the configuration file, but if someone would log in using those tools they may be discarding configuration options from the config-file. As an example, c5016c6 (#4376) introduced a new "features" field in the CLI configuration file. That change is part of Docker v26.1 and up, but older versions are not aware of it.
Reproducer
I'm running this inside a container, which doesn't have a credentials-helper configured. The container has an older version of the Docker CLI (v25.0);
$ docker run -it --rm -v /var/run/docker.sock:/var/run/docker.sock docker:25.0-cli sh
$ docker --version
Docker version 25.0.5, build 5dc9bcc
Inside the container, I create a config-file in a temporary directory that contains experimental
and features
fields;
$ mkdir temp-config
$ echo '{"experimental":"enabled", "features":{"hooks": "true", "something-else": "false"}}' > temp-config/config.json
When running docker login
or docker logout
, the config is overwritten with only fields known by that version of the code;
$ cat ./temp-config/config.json
{"experimental":"enabled", "features":{"hooks": "true", "something-else": "false"}}
$ docker --config=./temp-config/ logout
Removing login credentials for https://index.docker.io/v1/
$ cat temp-config/config.json
{
"auths": {},
"experimental": "enabled"
}
Some other actions, such as switching the context will result in the same;
# reset the config-file
$ echo '{"experimental":"enabled", "features":{"hooks": "true", "something-else": "false"}}' > temp-config/config.json
$ docker --config=./temp-config/ context create somecontext
somecontext
Successfully created context "somecontext"
$ cat ./temp-config/config.json
{"experimental":"enabled", "features":{"hooks": "true", "something-else": "false"}}
$ docker --config=./temp-config/ context use somecontext
somecontext
Current context is now "somecontext"
$ cat ./temp-config/config.json
{
"auths": {},
"experimental": "enabled",
"currentContext": "somecontext"
}
Solutions
The overall design of configuration file(s) as used by the CLI and plugins needs some re-thinking. This problem is partially because we have a poor separation of "config" and "state"; storing authentication in the config-file itself was never a great choice (and amplifies this issue), and ideally, the CLI would not mutate settings created by the user (or at least limited to actual config changes, not "state").
For actions mentioned (login, logout, switching default context), we only modify a single field in the config-file any other changes present in the config file don't have to be "understood" by the code, and we could change the code to "patch" the config-file.
i.e., when updating the config-file;
- read the config into a generic
map[string]any
- patch the field to update (
auths
orcurrentContext
) - marshal the
map[string]any
to JSON and write it to the file.