diff --git a/.gitignore b/.gitignore index 1a214e55..79b8cffc 100644 --- a/.gitignore +++ b/.gitignore @@ -19,4 +19,5 @@ test/data/ .idea/ coverage.html tags -vendor/ \ No newline at end of file +vendor/ +.vscode/ \ No newline at end of file diff --git a/pkg/api/auth.go b/pkg/api/auth.go index 2efaa4fe..402cbe86 100644 --- a/pkg/api/auth.go +++ b/pkg/api/auth.go @@ -141,15 +141,16 @@ func basicAuthHandler(c *Controller) mux.MiddlewareFunc { if err != nil { panic(err) } + defer f.Close() - for { - r := bufio.NewReader(f) - line, err := r.ReadString('\n') - if err != nil { - break + scanner := bufio.NewScanner(f) + + for scanner.Scan() { + line := scanner.Text() + if strings.Contains(line, ":") { + tokens := strings.Split(scanner.Text(), ":") + credMap[tokens[0]] = tokens[1] } - tokens := strings.Split(line, ":") - credMap[tokens[0]] = tokens[1] } } } diff --git a/pkg/api/controller_test.go b/pkg/api/controller_test.go index 4cf4bd22..95643a15 100644 --- a/pkg/api/controller_test.go +++ b/pkg/api/controller_test.go @@ -18,6 +18,8 @@ import ( "testing" "time" + "golang.org/x/crypto/bcrypt" + "github.com/anuvu/zot/pkg/api" "github.com/chartmuseum/auth" "github.com/mitchellh/mapstructure" @@ -71,6 +73,31 @@ func makeHtpasswdFile() string { return f.Name() } +func makeHtpasswdFileFromString(fileContent string) string { + f, err := ioutil.TempFile("", "htpasswd-") + if err != nil { + panic(err) + } + + // bcrypt(username="test", passwd="test") + content := []byte(fileContent) + if err := ioutil.WriteFile(f.Name(), content, 0644); err != nil { + panic(err) + } + + return f.Name() +} + +func getCredString(username, password string) string { + hash, err := bcrypt.GenerateFromPassword([]byte(password), 10) + if err != nil { + panic(err) + } + + usernameAndHash := fmt.Sprintf("%s:%s", username, string(hash)) + + return usernameAndHash +} func TestNew(t *testing.T) { Convey("Make a new controller", t, func() { config := api.NewConfig() @@ -79,6 +106,199 @@ func TestNew(t *testing.T) { }) } +func TestHtpasswdSingleCred(t *testing.T) { + Convey("Single cred", t, func() { + singleCredtests := []string{} + user := "alice" + password := "alice" + singleCredtests = append(singleCredtests, getCredString(user, password)) + singleCredtests = append(singleCredtests, getCredString(user, password)+"\n") + + for _, testString := range singleCredtests { + func() { + config := api.NewConfig() + config.HTTP.Port = SecurePort1 + + htpasswdPath := makeHtpasswdFileFromString(testString) + defer os.Remove(htpasswdPath) + config.HTTP.Auth = &api.AuthConfig{ + HTPasswd: api.AuthHTPasswd{ + Path: htpasswdPath, + }, + } + c := api.NewController(config) + dir, err := ioutil.TempDir("", "oci-repo-test") + if err != nil { + panic(err) + } + defer os.RemoveAll(dir) + c.Config.Storage.RootDirectory = dir + go func(controller *api.Controller) { + // this blocks + if err := controller.Run(); err != nil { + return + } + }(c) + // wait till ready + for { + _, err := resty.R().Get(BaseURL1) + if err == nil { + break + } + time.Sleep(100 * time.Millisecond) + } + defer func(controller *api.Controller) { + ctx := context.Background() + _ = controller.Server.Shutdown(ctx) + }(c) + // with creds, should get expected status code + resp, _ := resty.R().SetBasicAuth(user, password).Get(BaseURL1 + "/v2/") + So(resp, ShouldNotBeNil) + So(resp.StatusCode(), ShouldEqual, 200) + + //with invalid creds, it should fail + resp, _ = resty.R().SetBasicAuth("chuck", "chuck").Get(BaseURL1 + "/v2/") + So(resp, ShouldNotBeNil) + So(resp.StatusCode(), ShouldEqual, 401) + }() + } + }) +} + +func TestHtpasswdTwoCreds(t *testing.T) { + Convey("Two creds", t, func() { + twoCredTests := []string{} + user1 := "alicia" + password1 := "aliciapassword" + user2 := "bob" + password2 := "robert" + twoCredTests = append(twoCredTests, getCredString(user1, password1)+"\n"+ + getCredString(user2, password2)) + + twoCredTests = append(twoCredTests, getCredString(user1, password1)+"\n"+ + getCredString(user2, password2)+"\n") + + twoCredTests = append(twoCredTests, getCredString(user1, password1)+"\n\n"+ + getCredString(user2, password2)+"\n\n") + + for _, testString := range twoCredTests { + func() { + config := api.NewConfig() + config.HTTP.Port = SecurePort1 + htpasswdPath := makeHtpasswdFileFromString(testString) + defer os.Remove(htpasswdPath) + config.HTTP.Auth = &api.AuthConfig{ + HTPasswd: api.AuthHTPasswd{ + Path: htpasswdPath, + }, + } + c := api.NewController(config) + dir, err := ioutil.TempDir("", "oci-repo-test") + if err != nil { + panic(err) + } + defer os.RemoveAll(dir) + c.Config.Storage.RootDirectory = dir + go func(controller *api.Controller) { + // this blocks + if err := controller.Run(); err != nil { + return + } + }(c) + // wait till ready + for { + _, err := resty.R().Get(BaseURL1) + if err == nil { + break + } + time.Sleep(100 * time.Millisecond) + } + + defer func(controller *api.Controller) { + ctx := context.Background() + _ = controller.Server.Shutdown(ctx) + }(c) + // with creds, should get expected status code + resp, _ := resty.R().SetBasicAuth(user1, password1).Get(BaseURL1 + "/v2/") + So(resp, ShouldNotBeNil) + So(resp.StatusCode(), ShouldEqual, 200) + + resp, _ = resty.R().SetBasicAuth(user2, password2).Get(BaseURL1 + "/v2/") + So(resp, ShouldNotBeNil) + So(resp.StatusCode(), ShouldEqual, 200) + + //with invalid creds, it should fail + resp, _ = resty.R().SetBasicAuth("chuck", "chuck").Get(BaseURL1 + "/v2/") + So(resp, ShouldNotBeNil) + So(resp.StatusCode(), ShouldEqual, 401) + }() + } + }) +} +func TestHtpasswdFiveCreds(t *testing.T) { + Convey("Five creds", t, func() { + tests := map[string]string{ + "michael": "scott", + "jim": "halpert", + "dwight": "shrute", + "pam": "bessley", + "creed": "bratton", + } + credString := strings.Builder{} + for key, val := range tests { + credString.WriteString(getCredString(key, val) + "\n") + } + + func() { + config := api.NewConfig() + config.HTTP.Port = SecurePort1 + htpasswdPath := makeHtpasswdFileFromString(credString.String()) + defer os.Remove(htpasswdPath) + config.HTTP.Auth = &api.AuthConfig{ + HTPasswd: api.AuthHTPasswd{ + Path: htpasswdPath, + }, + } + c := api.NewController(config) + dir, err := ioutil.TempDir("", "oci-repo-test") + if err != nil { + panic(err) + } + defer os.RemoveAll(dir) + c.Config.Storage.RootDirectory = dir + go func(controller *api.Controller) { + // this blocks + if err := controller.Run(); err != nil { + return + } + }(c) + // wait till ready + for { + _, err := resty.R().Get(BaseURL1) + if err == nil { + break + } + time.Sleep(100 * time.Millisecond) + } + + defer func(controller *api.Controller) { + ctx := context.Background() + _ = controller.Server.Shutdown(ctx) + }(c) + // with creds, should get expected status code + for key, val := range tests { + resp, _ := resty.R().SetBasicAuth(key, val).Get(BaseURL1 + "/v2/") + So(resp, ShouldNotBeNil) + So(resp.StatusCode(), ShouldEqual, 200) + } + + //with invalid creds, it should fail + resp, _ := resty.R().SetBasicAuth("chuck", "chuck").Get(BaseURL1 + "/v2/") + So(resp, ShouldNotBeNil) + So(resp.StatusCode(), ShouldEqual, 401) + }() + }) +} func TestBasicAuth(t *testing.T) { Convey("Make a new controller", t, func() { config := api.NewConfig()