github.com/thajeztah/cli@v0.0.0-20240223162942-dc6bfac81a8b/cli/config/credentials/native_store.go (about) 1 package credentials 2 3 import ( 4 "github.com/docker/cli/cli/config/types" 5 "github.com/docker/docker-credential-helpers/client" 6 "github.com/docker/docker-credential-helpers/credentials" 7 ) 8 9 const ( 10 remoteCredentialsPrefix = "docker-credential-" //nolint:gosec // ignore G101: Potential hardcoded credentials 11 tokenUsername = "<token>" 12 ) 13 14 // nativeStore implements a credentials store 15 // using native keychain to keep credentials secure. 16 // It piggybacks into a file store to keep users' emails. 17 type nativeStore struct { 18 programFunc client.ProgramFunc 19 fileStore Store 20 } 21 22 // NewNativeStore creates a new native store that 23 // uses a remote helper program to manage credentials. 24 func NewNativeStore(file store, helperSuffix string) Store { 25 name := remoteCredentialsPrefix + helperSuffix 26 return &nativeStore{ 27 programFunc: client.NewShellProgramFunc(name), 28 fileStore: NewFileStore(file), 29 } 30 } 31 32 // Erase removes the given credentials from the native store. 33 func (c *nativeStore) Erase(serverAddress string) error { 34 if err := client.Erase(c.programFunc, serverAddress); err != nil { 35 return err 36 } 37 38 // Fallback to plain text store to remove email 39 return c.fileStore.Erase(serverAddress) 40 } 41 42 // Get retrieves credentials for a specific server from the native store. 43 func (c *nativeStore) Get(serverAddress string) (types.AuthConfig, error) { 44 // load user email if it exist or an empty auth config. 45 auth, _ := c.fileStore.Get(serverAddress) 46 47 creds, err := c.getCredentialsFromStore(serverAddress) 48 if err != nil { 49 return auth, err 50 } 51 auth.Username = creds.Username 52 auth.IdentityToken = creds.IdentityToken 53 auth.Password = creds.Password 54 auth.ServerAddress = creds.ServerAddress 55 56 return auth, nil 57 } 58 59 // GetAll retrieves all the credentials from the native store. 60 func (c *nativeStore) GetAll() (map[string]types.AuthConfig, error) { 61 auths, err := c.listCredentialsInStore() 62 if err != nil { 63 return nil, err 64 } 65 66 // Emails are only stored in the file store. 67 // This call can be safely eliminated when emails are removed. 68 fileConfigs, _ := c.fileStore.GetAll() 69 70 authConfigs := make(map[string]types.AuthConfig) 71 for registry := range auths { 72 creds, err := c.getCredentialsFromStore(registry) 73 if err != nil { 74 return nil, err 75 } 76 ac := fileConfigs[registry] // might contain Email 77 ac.Username = creds.Username 78 ac.Password = creds.Password 79 ac.IdentityToken = creds.IdentityToken 80 if ac.ServerAddress == "" { 81 ac.ServerAddress = creds.ServerAddress 82 } 83 authConfigs[registry] = ac 84 } 85 86 return authConfigs, nil 87 } 88 89 // Store saves the given credentials in the file store. 90 func (c *nativeStore) Store(authConfig types.AuthConfig) error { 91 if err := c.storeCredentialsInStore(authConfig); err != nil { 92 return err 93 } 94 authConfig.Username = "" 95 authConfig.Password = "" 96 authConfig.IdentityToken = "" 97 98 // Fallback to old credential in plain text to save only the email 99 return c.fileStore.Store(authConfig) 100 } 101 102 // storeCredentialsInStore executes the command to store the credentials in the native store. 103 func (c *nativeStore) storeCredentialsInStore(config types.AuthConfig) error { 104 creds := &credentials.Credentials{ 105 ServerURL: config.ServerAddress, 106 Username: config.Username, 107 Secret: config.Password, 108 } 109 110 if config.IdentityToken != "" { 111 creds.Username = tokenUsername 112 creds.Secret = config.IdentityToken 113 } 114 115 return client.Store(c.programFunc, creds) 116 } 117 118 // getCredentialsFromStore executes the command to get the credentials from the native store. 119 func (c *nativeStore) getCredentialsFromStore(serverAddress string) (types.AuthConfig, error) { 120 var ret types.AuthConfig 121 122 creds, err := client.Get(c.programFunc, serverAddress) 123 if err != nil { 124 if credentials.IsErrCredentialsNotFound(err) { 125 // do not return an error if the credentials are not 126 // in the keychain. Let docker ask for new credentials. 127 return ret, nil 128 } 129 return ret, err 130 } 131 132 if creds.Username == tokenUsername { 133 ret.IdentityToken = creds.Secret 134 } else { 135 ret.Password = creds.Secret 136 ret.Username = creds.Username 137 } 138 139 ret.ServerAddress = serverAddress 140 return ret, nil 141 } 142 143 // listCredentialsInStore returns a listing of stored credentials as a map of 144 // URL -> username. 145 func (c *nativeStore) listCredentialsInStore() (map[string]string, error) { 146 return client.List(c.programFunc) 147 }