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