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  }