Files
docker-cli/cli/config/memorystore/store.go
Alano Terblanche 9b83d5bbf9 Use DOCKER_AUTH_CONFIG env as credential store
This patch enables the CLI to natively pick up the `DOCKER_AUTH_CONFIG`
environment variable and use it as a credential store.

The `DOCKER_AUTH_CONFIG` value should be a JSON object and must store
the credentials in a base64 encoded string under the `auth` key.
Specifying additional fields will cause the parser to fail.

For example:
`printf "username:pat" | openssl base64 -A`

`export DOCKER_AUTH_CONFIG='{
  "auths": {
    "https://index.docker.io/v1/": {
      "auth": "aGk6KTpkY2tyX3BhdF9oZWxsbw=="
    }
  }
}'`

Credentials stored in `DOCKER_AUTH_CONFIG` would take precedence over any
credential stored in the file store (`~/.docker/config.json`) or native store
(credential helper).

Destructive actions, such as deleting a credential would result in a noop if
found in the environment credential. Credentials found in the file or
native store would get removed.

Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com>
2025-06-18 18:55:42 +02:00

127 lines
2.9 KiB
Go

//go:build go1.23
package memorystore
import (
"errors"
"fmt"
"maps"
"os"
"sync"
"github.com/docker/cli/cli/config/credentials"
"github.com/docker/cli/cli/config/types"
)
var errValueNotFound = errors.New("value not found")
func IsErrValueNotFound(err error) bool {
return errors.Is(err, errValueNotFound)
}
type Config struct {
lock sync.RWMutex
memoryCredentials map[string]types.AuthConfig
fallbackStore credentials.Store
}
func (e *Config) Erase(serverAddress string) error {
e.lock.Lock()
defer e.lock.Unlock()
delete(e.memoryCredentials, serverAddress)
if e.fallbackStore != nil {
err := e.fallbackStore.Erase(serverAddress)
if err != nil {
_, _ = fmt.Fprintln(os.Stderr, "memorystore: ", err)
}
}
return nil
}
func (e *Config) Get(serverAddress string) (types.AuthConfig, error) {
e.lock.RLock()
defer e.lock.RUnlock()
authConfig, ok := e.memoryCredentials[serverAddress]
if !ok {
if e.fallbackStore != nil {
return e.fallbackStore.Get(serverAddress)
}
return types.AuthConfig{}, errValueNotFound
}
return authConfig, nil
}
func (e *Config) GetAll() (map[string]types.AuthConfig, error) {
e.lock.RLock()
defer e.lock.RUnlock()
creds := make(map[string]types.AuthConfig)
if e.fallbackStore != nil {
fileCredentials, err := e.fallbackStore.GetAll()
if err != nil {
_, _ = fmt.Fprintln(os.Stderr, "memorystore: ", err)
} else {
creds = fileCredentials
}
}
maps.Copy(creds, e.memoryCredentials)
return creds, nil
}
func (e *Config) Store(authConfig types.AuthConfig) error {
e.lock.Lock()
defer e.lock.Unlock()
e.memoryCredentials[authConfig.ServerAddress] = authConfig
if e.fallbackStore != nil {
return e.fallbackStore.Store(authConfig)
}
return nil
}
// WithFallbackStore sets a fallback store.
//
// Write operations will be performed on both the memory store and the
// fallback store.
//
// Read operations will first check the memory store, and if the credential
// is not found, it will then check the fallback store.
//
// Retrieving all credentials will return from both the memory store and the
// fallback store, merging the results from both stores into a single map.
//
// Data stored in the memory store will take precedence over data in the
// fallback store.
func WithFallbackStore(store credentials.Store) Options {
return func(s *Config) error {
s.fallbackStore = store
return nil
}
}
// WithAuthConfig allows to set the initial credentials in the memory store.
func WithAuthConfig(config map[string]types.AuthConfig) Options {
return func(s *Config) error {
s.memoryCredentials = config
return nil
}
}
type Options func(*Config) error
// New creates a new in memory credential store
func New(opts ...Options) (credentials.Store, error) {
m := &Config{
memoryCredentials: make(map[string]types.AuthConfig),
}
for _, opt := range opts {
if err := opt(m); err != nil {
return nil, err
}
}
return m, nil
}