| 1 | 1 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,136 @@ |
| 0 |
+package auth |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "encoding/base64" |
|
| 4 |
+ "encoding/json" |
|
| 5 |
+ "errors" |
|
| 6 |
+ "fmt" |
|
| 7 |
+ "io/ioutil" |
|
| 8 |
+ "net/http" |
|
| 9 |
+ "os" |
|
| 10 |
+ "strings" |
|
| 11 |
+) |
|
| 12 |
+ |
|
| 13 |
+// Where we store the config file |
|
| 14 |
+const CONFIGFILE = "/var/lib/docker/.dockercfg" |
|
| 15 |
+ |
|
| 16 |
+// the registry server we want to login against |
|
| 17 |
+const REGISTRY_SERVER = "http://registry.docker.io" |
|
| 18 |
+ |
|
| 19 |
+type AuthConfig struct {
|
|
| 20 |
+ Username string `json:"username"` |
|
| 21 |
+ Password string `json:"password"` |
|
| 22 |
+ Email string `json:"email"` |
|
| 23 |
+} |
|
| 24 |
+ |
|
| 25 |
+// create a base64 encoded auth string to store in config |
|
| 26 |
+func EncodeAuth(authConfig AuthConfig) string {
|
|
| 27 |
+ authStr := authConfig.Username + ":" + authConfig.Password |
|
| 28 |
+ msg := []byte(authStr) |
|
| 29 |
+ encoded := make([]byte, base64.StdEncoding.EncodedLen(len(msg))) |
|
| 30 |
+ base64.StdEncoding.Encode(encoded, msg) |
|
| 31 |
+ return string(encoded) |
|
| 32 |
+} |
|
| 33 |
+ |
|
| 34 |
+// decode the auth string |
|
| 35 |
+func DecodeAuth(authStr string) (AuthConfig, error) {
|
|
| 36 |
+ decLen := base64.StdEncoding.DecodedLen(len(authStr)) |
|
| 37 |
+ decoded := make([]byte, decLen) |
|
| 38 |
+ authByte := []byte(authStr) |
|
| 39 |
+ n, err := base64.StdEncoding.Decode(decoded, authByte) |
|
| 40 |
+ if err != nil {
|
|
| 41 |
+ return AuthConfig{}, err
|
|
| 42 |
+ } |
|
| 43 |
+ if n > decLen {
|
|
| 44 |
+ return AuthConfig{}, errors.New("something went wrong decoding auth config")
|
|
| 45 |
+ } |
|
| 46 |
+ arr := strings.Split(string(decoded), ":") |
|
| 47 |
+ password := strings.Trim(arr[1], "\x00") |
|
| 48 |
+ return AuthConfig{Username: arr[0], Password: password}, nil
|
|
| 49 |
+ |
|
| 50 |
+} |
|
| 51 |
+ |
|
| 52 |
+// load up the auth config information and return values |
|
| 53 |
+func LoadConfig() (AuthConfig, error) {
|
|
| 54 |
+ if _, err := os.Stat(CONFIGFILE); err == nil {
|
|
| 55 |
+ b, err := ioutil.ReadFile(CONFIGFILE) |
|
| 56 |
+ if err != nil {
|
|
| 57 |
+ return AuthConfig{}, err
|
|
| 58 |
+ } |
|
| 59 |
+ arr := strings.Split(string(b), "\n") |
|
| 60 |
+ orig_auth := strings.Split(arr[0], " = ") |
|
| 61 |
+ orig_email := strings.Split(arr[1], " = ") |
|
| 62 |
+ authConfig, err := DecodeAuth(orig_auth[1]) |
|
| 63 |
+ if err != nil {
|
|
| 64 |
+ return AuthConfig{}, err
|
|
| 65 |
+ } |
|
| 66 |
+ authConfig.Email = orig_email[1] |
|
| 67 |
+ return authConfig, nil |
|
| 68 |
+ } else {
|
|
| 69 |
+ return AuthConfig{}, nil
|
|
| 70 |
+ } |
|
| 71 |
+ return AuthConfig{}, nil
|
|
| 72 |
+} |
|
| 73 |
+ |
|
| 74 |
+// save the auth config |
|
| 75 |
+func saveConfig(authStr string, email string) error {
|
|
| 76 |
+ lines := "auth = " + authStr + "\n" + "email = " + email + "\n" |
|
| 77 |
+ b := []byte(lines) |
|
| 78 |
+ err := ioutil.WriteFile(CONFIGFILE, b, 0600) |
|
| 79 |
+ if err != nil {
|
|
| 80 |
+ return err |
|
| 81 |
+ } |
|
| 82 |
+ return nil |
|
| 83 |
+} |
|
| 84 |
+ |
|
| 85 |
+// try to register/login to the registry server |
|
| 86 |
+func Login(authConfig AuthConfig) (string, error) {
|
|
| 87 |
+ storeConfig := false |
|
| 88 |
+ reqStatusCode := 0 |
|
| 89 |
+ var status string |
|
| 90 |
+ var reqBody []byte |
|
| 91 |
+ jsonBody, _ := json.Marshal(authConfig) |
|
| 92 |
+ b := strings.NewReader(string(jsonBody)) |
|
| 93 |
+ req1, err := http.Post(REGISTRY_SERVER+"/v1/users", "application/json; charset=utf-8", b) |
|
| 94 |
+ if err == nil {
|
|
| 95 |
+ body, _ := ioutil.ReadAll(req1.Body) |
|
| 96 |
+ reqStatusCode = req1.StatusCode |
|
| 97 |
+ reqBody = body |
|
| 98 |
+ req1.Body.Close() |
|
| 99 |
+ } else {
|
|
| 100 |
+ return "", err |
|
| 101 |
+ } |
|
| 102 |
+ if reqStatusCode == 201 {
|
|
| 103 |
+ status = "Account Created\n" |
|
| 104 |
+ storeConfig = true |
|
| 105 |
+ } else if reqStatusCode == 400 {
|
|
| 106 |
+ if string(reqBody) == "Username or email already exist" {
|
|
| 107 |
+ client := &http.Client{}
|
|
| 108 |
+ req, err := http.NewRequest("GET", REGISTRY_SERVER+"/v1/users", nil)
|
|
| 109 |
+ req.SetBasicAuth(authConfig.Username, authConfig.Password) |
|
| 110 |
+ resp, err := client.Do(req) |
|
| 111 |
+ if err != nil {
|
|
| 112 |
+ return "", err |
|
| 113 |
+ } |
|
| 114 |
+ defer resp.Body.Close() |
|
| 115 |
+ body, err := ioutil.ReadAll(resp.Body) |
|
| 116 |
+ if err != nil {
|
|
| 117 |
+ return "", err |
|
| 118 |
+ } |
|
| 119 |
+ if resp.StatusCode == 200 {
|
|
| 120 |
+ status = "Login Succeeded\n" |
|
| 121 |
+ storeConfig = true |
|
| 122 |
+ } else {
|
|
| 123 |
+ storeConfig = false |
|
| 124 |
+ status = fmt.Sprintf("Error: %s\n", body)
|
|
| 125 |
+ } |
|
| 126 |
+ } else {
|
|
| 127 |
+ status = fmt.Sprintf("Error: %s\n", reqBody)
|
|
| 128 |
+ } |
|
| 129 |
+ } |
|
| 130 |
+ if storeConfig {
|
|
| 131 |
+ authStr := EncodeAuth(authConfig) |
|
| 132 |
+ saveConfig(authStr, authConfig.Email) |
|
| 133 |
+ } |
|
| 134 |
+ return status, nil |
|
| 135 |
+} |
| 0 | 136 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,23 @@ |
| 0 |
+package auth |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "testing" |
|
| 4 |
+) |
|
| 5 |
+ |
|
| 6 |
+func TestEncodeAuth(t *testing.T) {
|
|
| 7 |
+ newAuthConfig := AuthConfig{Username: "ken", Password: "test", Email: "test@example.com"}
|
|
| 8 |
+ authStr := EncodeAuth(newAuthConfig) |
|
| 9 |
+ decAuthConfig, err := DecodeAuth(authStr) |
|
| 10 |
+ if err != nil {
|
|
| 11 |
+ t.Fatal(err) |
|
| 12 |
+ } |
|
| 13 |
+ if newAuthConfig.Username != decAuthConfig.Username {
|
|
| 14 |
+ t.Fatal("Encode Username doesn't match decoded Username")
|
|
| 15 |
+ } |
|
| 16 |
+ if newAuthConfig.Password != decAuthConfig.Password {
|
|
| 17 |
+ t.Fatal("Encode Password doesn't match decoded Password")
|
|
| 18 |
+ } |
|
| 19 |
+ if authStr != "a2VuOnRlc3Q=" {
|
|
| 20 |
+ t.Fatal("AuthString encoding isn't correct.")
|
|
| 21 |
+ } |
|
| 22 |
+} |
| ... | ... |
@@ -7,6 +7,7 @@ import ( |
| 7 | 7 |
"errors" |
| 8 | 8 |
"fmt" |
| 9 | 9 |
"github.com/dotcloud/docker" |
| 10 |
+ "github.com/dotcloud/docker/auth" |
|
| 10 | 11 |
"github.com/dotcloud/docker/fs" |
| 11 | 12 |
"github.com/dotcloud/docker/future" |
| 12 | 13 |
"github.com/dotcloud/docker/rcli" |
| ... | ... |
@@ -47,6 +48,7 @@ func (srv *Server) Help() string {
|
| 47 | 47 |
{"inspect", "Return low-level information on a container"},
|
| 48 | 48 |
{"kill", "Kill a running container"},
|
| 49 | 49 |
{"layers", "(debug only) List filesystem layers"},
|
| 50 |
+ {"login", "Register or Login to the docker registry server"},
|
|
| 50 | 51 |
{"logs", "Fetch the logs of a container"},
|
| 51 | 52 |
{"ls", "List the contents of a container's directory"},
|
| 52 | 53 |
{"mirror", "(debug only) (No documentation available)"},
|
| ... | ... |
@@ -71,6 +73,43 @@ func (srv *Server) Help() string {
|
| 71 | 71 |
return help |
| 72 | 72 |
} |
| 73 | 73 |
|
| 74 |
+// 'docker login': login / register a user to registry service. |
|
| 75 |
+func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
|
| 76 |
+ var username string |
|
| 77 |
+ var password string |
|
| 78 |
+ var email string |
|
| 79 |
+ authConfig, err := auth.LoadConfig() |
|
| 80 |
+ if err != nil {
|
|
| 81 |
+ fmt.Fprintf(stdout, "Error : %s\n", err) |
|
| 82 |
+ } |
|
| 83 |
+ |
|
| 84 |
+ fmt.Fprint(stdout, "Username (", authConfig.Username, "): ")
|
|
| 85 |
+ fmt.Scanf("%s", &username)
|
|
| 86 |
+ if username == "" {
|
|
| 87 |
+ username = authConfig.Username |
|
| 88 |
+ } |
|
| 89 |
+ if username != authConfig.Username {
|
|
| 90 |
+ fmt.Fprint(stdout, "Password: ") |
|
| 91 |
+ fmt.Scanf("%s", &password)
|
|
| 92 |
+ |
|
| 93 |
+ fmt.Fprint(stdout, "Email (", authConfig.Email, "): ")
|
|
| 94 |
+ fmt.Scanf("%s", &email)
|
|
| 95 |
+ if email == "" {
|
|
| 96 |
+ email = authConfig.Email |
|
| 97 |
+ } |
|
| 98 |
+ } else {
|
|
| 99 |
+ password = authConfig.Password |
|
| 100 |
+ email = authConfig.Email |
|
| 101 |
+ } |
|
| 102 |
+ newAuthConfig := auth.AuthConfig{Username: username, Password: password, Email: email}
|
|
| 103 |
+ status, err := auth.Login(newAuthConfig) |
|
| 104 |
+ if err != nil {
|
|
| 105 |
+ fmt.Fprintf(stdout, "Error : %s\n", err) |
|
| 106 |
+ } |
|
| 107 |
+ fmt.Fprintf(stdout, status) |
|
| 108 |
+ return nil |
|
| 109 |
+} |
|
| 110 |
+ |
|
| 74 | 111 |
// 'docker wait': block until a container stops |
| 75 | 112 |
func (srv *Server) CmdWait(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
| 76 | 113 |
cmd := rcli.Subcmd(stdout, "wait", "[OPTIONS] NAME", "Block until a container stops, then print its exit code.") |