github.com/sijibomii/docker@v0.0.0-20231230191044-5cf6ca554647/cliconfig/credentials/native_store.go (about) 1 package credentials 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "io" 9 "strings" 10 11 "github.com/Sirupsen/logrus" 12 "github.com/docker/docker/cliconfig" 13 "github.com/docker/engine-api/types" 14 ) 15 16 const ( 17 remoteCredentialsPrefix = "docker-credential-" 18 tokenUsername = "<token>" 19 ) 20 21 // Standarize the not found error, so every helper returns 22 // the same message and docker can handle it properly. 23 var errCredentialsNotFound = errors.New("credentials not found in native keychain") 24 25 // command is an interface that remote executed commands implement. 26 type command interface { 27 Output() ([]byte, error) 28 Input(in io.Reader) 29 } 30 31 // credentialsRequest holds information shared between docker and a remote credential store. 32 type credentialsRequest struct { 33 ServerURL string 34 Username string 35 Secret string 36 } 37 38 // credentialsGetResponse is the information serialized from a remote store 39 // when the plugin sends requests to get the user credentials. 40 type credentialsGetResponse struct { 41 Username string 42 Secret string 43 } 44 45 // nativeStore implements a credentials store 46 // using native keychain to keep credentials secure. 47 // It piggybacks into a file store to keep users' emails. 48 type nativeStore struct { 49 commandFn func(args ...string) command 50 fileStore Store 51 } 52 53 // NewNativeStore creates a new native store that 54 // uses a remote helper program to manage credentials. 55 func NewNativeStore(file *cliconfig.ConfigFile) Store { 56 return &nativeStore{ 57 commandFn: shellCommandFn(file.CredentialsStore), 58 fileStore: NewFileStore(file), 59 } 60 } 61 62 // Erase removes the given credentials from the native store. 63 func (c *nativeStore) Erase(serverAddress string) error { 64 if err := c.eraseCredentialsFromStore(serverAddress); err != nil { 65 return err 66 } 67 68 // Fallback to plain text store to remove email 69 return c.fileStore.Erase(serverAddress) 70 } 71 72 // Get retrieves credentials for a specific server from the native store. 73 func (c *nativeStore) Get(serverAddress string) (types.AuthConfig, error) { 74 // load user email if it exist or an empty auth config. 75 auth, _ := c.fileStore.Get(serverAddress) 76 77 creds, err := c.getCredentialsFromStore(serverAddress) 78 if err != nil { 79 return auth, err 80 } 81 auth.Username = creds.Username 82 auth.IdentityToken = creds.IdentityToken 83 auth.Password = creds.Password 84 85 return auth, nil 86 } 87 88 // GetAll retrieves all the credentials from the native store. 89 func (c *nativeStore) GetAll() (map[string]types.AuthConfig, error) { 90 auths, _ := c.fileStore.GetAll() 91 92 for s, ac := range auths { 93 creds, _ := c.getCredentialsFromStore(s) 94 ac.Username = creds.Username 95 ac.Password = creds.Password 96 ac.IdentityToken = creds.IdentityToken 97 auths[s] = ac 98 } 99 100 return auths, nil 101 } 102 103 // Store saves the given credentials in the file store. 104 func (c *nativeStore) Store(authConfig types.AuthConfig) error { 105 if err := c.storeCredentialsInStore(authConfig); err != nil { 106 return err 107 } 108 authConfig.Username = "" 109 authConfig.Password = "" 110 authConfig.IdentityToken = "" 111 112 // Fallback to old credential in plain text to save only the email 113 return c.fileStore.Store(authConfig) 114 } 115 116 // storeCredentialsInStore executes the command to store the credentials in the native store. 117 func (c *nativeStore) storeCredentialsInStore(config types.AuthConfig) error { 118 cmd := c.commandFn("store") 119 creds := &credentialsRequest{ 120 ServerURL: config.ServerAddress, 121 Username: config.Username, 122 Secret: config.Password, 123 } 124 125 if config.IdentityToken != "" { 126 creds.Username = tokenUsername 127 creds.Secret = config.IdentityToken 128 } 129 130 buffer := new(bytes.Buffer) 131 if err := json.NewEncoder(buffer).Encode(creds); err != nil { 132 return err 133 } 134 cmd.Input(buffer) 135 136 out, err := cmd.Output() 137 if err != nil { 138 t := strings.TrimSpace(string(out)) 139 logrus.Debugf("error adding credentials - err: %v, out: `%s`", err, t) 140 return fmt.Errorf(t) 141 } 142 143 return nil 144 } 145 146 // getCredentialsFromStore executes the command to get the credentials from the native store. 147 func (c *nativeStore) getCredentialsFromStore(serverAddress string) (types.AuthConfig, error) { 148 var ret types.AuthConfig 149 150 cmd := c.commandFn("get") 151 cmd.Input(strings.NewReader(serverAddress)) 152 153 out, err := cmd.Output() 154 if err != nil { 155 t := strings.TrimSpace(string(out)) 156 157 // do not return an error if the credentials are not 158 // in the keyckain. Let docker ask for new credentials. 159 if t == errCredentialsNotFound.Error() { 160 return ret, nil 161 } 162 163 logrus.Debugf("error getting credentials - err: %v, out: `%s`", err, t) 164 return ret, fmt.Errorf(t) 165 } 166 167 var resp credentialsGetResponse 168 if err := json.NewDecoder(bytes.NewReader(out)).Decode(&resp); err != nil { 169 return ret, err 170 } 171 172 if resp.Username == tokenUsername { 173 ret.IdentityToken = resp.Secret 174 } else { 175 ret.Password = resp.Secret 176 ret.Username = resp.Username 177 } 178 179 ret.ServerAddress = serverAddress 180 return ret, nil 181 } 182 183 // eraseCredentialsFromStore executes the command to remove the server credentails from the native store. 184 func (c *nativeStore) eraseCredentialsFromStore(serverURL string) error { 185 cmd := c.commandFn("erase") 186 cmd.Input(strings.NewReader(serverURL)) 187 188 out, err := cmd.Output() 189 if err != nil { 190 t := strings.TrimSpace(string(out)) 191 logrus.Debugf("error erasing credentials - err: %v, out: `%s`", err, t) 192 return fmt.Errorf(t) 193 } 194 195 return nil 196 }