2016-12-25 14:31:52 -05:00
|
|
|
package credentials
|
|
|
|
|
|
|
|
import (
|
2017-10-15 15:39:56 -04:00
|
|
|
"github.com/docker/cli/cli/config/types"
|
2016-12-25 14:31:52 -05:00
|
|
|
"github.com/docker/docker-credential-helpers/client"
|
|
|
|
"github.com/docker/docker-credential-helpers/credentials"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
2022-03-27 14:07:52 -04:00
|
|
|
remoteCredentialsPrefix = "docker-credential-" //nolint:gosec // ignore G101: Potential hardcoded credentials
|
2016-12-25 14:31:52 -05:00
|
|
|
tokenUsername = "<token>"
|
|
|
|
)
|
|
|
|
|
|
|
|
// nativeStore implements a credentials store
|
|
|
|
// using native keychain to keep credentials secure.
|
|
|
|
// It piggybacks into a file store to keep users' emails.
|
|
|
|
type nativeStore struct {
|
|
|
|
programFunc client.ProgramFunc
|
|
|
|
fileStore Store
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewNativeStore creates a new native store that
|
|
|
|
// uses a remote helper program to manage credentials.
|
2017-06-21 16:47:06 -04:00
|
|
|
func NewNativeStore(file store, helperSuffix string) Store {
|
2016-12-25 14:31:52 -05:00
|
|
|
name := remoteCredentialsPrefix + helperSuffix
|
|
|
|
return &nativeStore{
|
|
|
|
programFunc: client.NewShellProgramFunc(name),
|
|
|
|
fileStore: NewFileStore(file),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Erase removes the given credentials from the native store.
|
|
|
|
func (c *nativeStore) Erase(serverAddress string) error {
|
|
|
|
if err := client.Erase(c.programFunc, serverAddress); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fallback to plain text store to remove email
|
|
|
|
return c.fileStore.Erase(serverAddress)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get retrieves credentials for a specific server from the native store.
|
|
|
|
func (c *nativeStore) Get(serverAddress string) (types.AuthConfig, error) {
|
|
|
|
// load user email if it exist or an empty auth config.
|
|
|
|
auth, _ := c.fileStore.Get(serverAddress)
|
|
|
|
|
|
|
|
creds, err := c.getCredentialsFromStore(serverAddress)
|
|
|
|
if err != nil {
|
|
|
|
return auth, err
|
|
|
|
}
|
|
|
|
auth.Username = creds.Username
|
|
|
|
auth.IdentityToken = creds.IdentityToken
|
|
|
|
auth.Password = creds.Password
|
Fix setting ServerAddress property in NativeStore
This will return the ServerAddress property when using the NativeStore.
This happens when you use docker credential helpers, not the credential
store.
The reason this fix is needed is because it needs to be propagated
properly down towards `moby/moby` project in the following logic:
```golang
func authorizationCredsFromAuthConfig(authConfig registrytypes.AuthConfig) docker.AuthorizerOpt {
cfgHost := registry.ConvertToHostname(authConfig.ServerAddress)
if cfgHost == "" || cfgHost == registry.IndexHostname {
cfgHost = registry.DefaultRegistryHost
}
return docker.WithAuthCreds(func(host string) (string, string, error) {
if cfgHost != host {
logrus.WithFields(logrus.Fields{
"host": host,
"cfgHost": cfgHost,
}).Warn("Host doesn't match")
return "", "", nil
}
if authConfig.IdentityToken != "" {
return "", authConfig.IdentityToken, nil
}
return authConfig.Username, authConfig.Password, nil
})
}
```
This logic resides in the following file :
`daemon/containerd/resolver.go` .
In the case when using the containerd storage feature when setting the
`cfgHost` variable from the `authConfig.ServerAddress` it will always be
empty. Since it will never be returned from the NativeStore currently.
Therefore Docker Hub images will work fine, but anything else will fail
since the `cfgHost` will always be the `registry.DefaultRegistryHost`.
Signed-off-by: Eric Bode <eric.bode@foundries.io>
2023-11-10 16:38:10 -05:00
|
|
|
auth.ServerAddress = creds.ServerAddress
|
2016-12-25 14:31:52 -05:00
|
|
|
|
|
|
|
return auth, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetAll retrieves all the credentials from the native store.
|
|
|
|
func (c *nativeStore) GetAll() (map[string]types.AuthConfig, error) {
|
|
|
|
auths, err := c.listCredentialsInStore()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Emails are only stored in the file store.
|
|
|
|
// This call can be safely eliminated when emails are removed.
|
|
|
|
fileConfigs, _ := c.fileStore.GetAll()
|
|
|
|
|
|
|
|
authConfigs := make(map[string]types.AuthConfig)
|
|
|
|
for registry := range auths {
|
|
|
|
creds, err := c.getCredentialsFromStore(registry)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2017-06-09 17:42:16 -04:00
|
|
|
ac := fileConfigs[registry] // might contain Email
|
2016-12-25 14:31:52 -05:00
|
|
|
ac.Username = creds.Username
|
|
|
|
ac.Password = creds.Password
|
|
|
|
ac.IdentityToken = creds.IdentityToken
|
Fix setting ServerAddress property in NativeStore
This will return the ServerAddress property when using the NativeStore.
This happens when you use docker credential helpers, not the credential
store.
The reason this fix is needed is because it needs to be propagated
properly down towards `moby/moby` project in the following logic:
```golang
func authorizationCredsFromAuthConfig(authConfig registrytypes.AuthConfig) docker.AuthorizerOpt {
cfgHost := registry.ConvertToHostname(authConfig.ServerAddress)
if cfgHost == "" || cfgHost == registry.IndexHostname {
cfgHost = registry.DefaultRegistryHost
}
return docker.WithAuthCreds(func(host string) (string, string, error) {
if cfgHost != host {
logrus.WithFields(logrus.Fields{
"host": host,
"cfgHost": cfgHost,
}).Warn("Host doesn't match")
return "", "", nil
}
if authConfig.IdentityToken != "" {
return "", authConfig.IdentityToken, nil
}
return authConfig.Username, authConfig.Password, nil
})
}
```
This logic resides in the following file :
`daemon/containerd/resolver.go` .
In the case when using the containerd storage feature when setting the
`cfgHost` variable from the `authConfig.ServerAddress` it will always be
empty. Since it will never be returned from the NativeStore currently.
Therefore Docker Hub images will work fine, but anything else will fail
since the `cfgHost` will always be the `registry.DefaultRegistryHost`.
Signed-off-by: Eric Bode <eric.bode@foundries.io>
2023-11-10 16:38:10 -05:00
|
|
|
if ac.ServerAddress == "" {
|
|
|
|
ac.ServerAddress = creds.ServerAddress
|
|
|
|
}
|
2016-12-25 14:31:52 -05:00
|
|
|
authConfigs[registry] = ac
|
|
|
|
}
|
|
|
|
|
|
|
|
return authConfigs, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Store saves the given credentials in the file store.
|
|
|
|
func (c *nativeStore) Store(authConfig types.AuthConfig) error {
|
|
|
|
if err := c.storeCredentialsInStore(authConfig); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
authConfig.Username = ""
|
|
|
|
authConfig.Password = ""
|
|
|
|
authConfig.IdentityToken = ""
|
|
|
|
|
|
|
|
// Fallback to old credential in plain text to save only the email
|
|
|
|
return c.fileStore.Store(authConfig)
|
|
|
|
}
|
|
|
|
|
|
|
|
// storeCredentialsInStore executes the command to store the credentials in the native store.
|
|
|
|
func (c *nativeStore) storeCredentialsInStore(config types.AuthConfig) error {
|
|
|
|
creds := &credentials.Credentials{
|
|
|
|
ServerURL: config.ServerAddress,
|
|
|
|
Username: config.Username,
|
|
|
|
Secret: config.Password,
|
|
|
|
}
|
|
|
|
|
|
|
|
if config.IdentityToken != "" {
|
|
|
|
creds.Username = tokenUsername
|
|
|
|
creds.Secret = config.IdentityToken
|
|
|
|
}
|
|
|
|
|
|
|
|
return client.Store(c.programFunc, creds)
|
|
|
|
}
|
|
|
|
|
|
|
|
// getCredentialsFromStore executes the command to get the credentials from the native store.
|
|
|
|
func (c *nativeStore) getCredentialsFromStore(serverAddress string) (types.AuthConfig, error) {
|
|
|
|
var ret types.AuthConfig
|
|
|
|
|
|
|
|
creds, err := client.Get(c.programFunc, serverAddress)
|
|
|
|
if err != nil {
|
|
|
|
if credentials.IsErrCredentialsNotFound(err) {
|
|
|
|
// do not return an error if the credentials are not
|
2017-02-16 10:56:53 -05:00
|
|
|
// in the keychain. Let docker ask for new credentials.
|
2016-12-25 14:31:52 -05:00
|
|
|
return ret, nil
|
|
|
|
}
|
|
|
|
return ret, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if creds.Username == tokenUsername {
|
|
|
|
ret.IdentityToken = creds.Secret
|
|
|
|
} else {
|
|
|
|
ret.Password = creds.Secret
|
|
|
|
ret.Username = creds.Username
|
|
|
|
}
|
|
|
|
|
|
|
|
ret.ServerAddress = serverAddress
|
|
|
|
return ret, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// listCredentialsInStore returns a listing of stored credentials as a map of
|
|
|
|
// URL -> username.
|
|
|
|
func (c *nativeStore) listCredentialsInStore() (map[string]string, error) {
|
|
|
|
return client.List(c.programFunc)
|
|
|
|
}
|