Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-pages.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTuomo Ala-Vannesluoma <tuomoav@gmail.com>2018-09-22 14:52:47 +0300
committerTuomo Ala-Vannesluoma <tuomoav@gmail.com>2018-09-22 14:52:47 +0300
commit0cb2e9714fc66486b8deaeeb06c60ae7b701698f (patch)
treedb18b677b5bc60582e9ad054e15c270519883eb8
parent2c766aba4f008c4a80e328a4eabbaae186276fc9 (diff)
Add special handling for namespace projects to avoid existence leak
-rw-r--r--acceptance_test.go25
-rw-r--r--app.go27
-rw-r--r--internal/domain/domain.go72
-rw-r--r--internal/domain/domain_test.go86
-rw-r--r--internal/domain/map.go7
-rw-r--r--shared/pages/group.auth/group.auth.gitlab-example.com/public/index.html1
-rw-r--r--shared/pages/group.auth/group.auth.gitlab-example.com/public/private.project/index.html1
7 files changed, 162 insertions, 57 deletions
diff --git a/acceptance_test.go b/acceptance_test.go
index f0cdc749..17eb7cb4 100644
--- a/acceptance_test.go
+++ b/acceptance_test.go
@@ -754,6 +754,31 @@ func TestAccessControlUnderCustomDomain(t *testing.T) {
assert.Equal(t, http.StatusOK, authrsp.StatusCode)
}
+func TestAccessControlGroupDomain404RedirectsAuth(t *testing.T) {
+ skipUnlessEnabled(t)
+ teardown := RunPagesProcessWithAuth(t, *pagesBinary, listeners, "")
+ defer teardown()
+
+ rsp, err := GetRedirectPage(t, httpListener, "group.gitlab-example.com", "/nonexistent/")
+ require.NoError(t, err)
+ defer rsp.Body.Close()
+ assert.Equal(t, http.StatusFound, rsp.StatusCode)
+ // Redirects to the projects under gitlab pages domain for authentication flow
+ url, err := url.Parse(rsp.Header.Get("Location"))
+ require.NoError(t, err)
+ assert.Equal(t, "projects.gitlab-example.com", url.Host)
+ assert.Equal(t, "/auth", url.Path)
+}
+func TestAccessControlProject404DoesNotRedirect(t *testing.T) {
+ skipUnlessEnabled(t)
+ teardown := RunPagesProcessWithAuth(t, *pagesBinary, listeners, "")
+ defer teardown()
+
+ rsp, err := GetRedirectPage(t, httpListener, "group.gitlab-example.com", "/project/nonexistent/")
+ require.NoError(t, err)
+ defer rsp.Body.Close()
+ assert.Equal(t, http.StatusNotFound, rsp.StatusCode)
+}
func TestAccessControl(t *testing.T) {
skipUnlessEnabled(t, "not-inplace-chroot")
diff --git a/app.go b/app.go
index 6edf0ae5..295bc7c8 100644
--- a/app.go
+++ b/app.go
@@ -184,14 +184,37 @@ func (a *theApp) serveContent(ww http.ResponseWriter, r *http.Request, https boo
// Serve static file, applying CORS headers if necessary
if a.DisableCrossOriginRequests {
- domain.ServeHTTP(&w, r)
+ a.serveFileOrNotFound(domain, &w, r)
} else {
- corsHandler.ServeHTTP(&w, r, domain.ServeHTTP)
+ corsHandler.ServeHTTP(&w, r, a.serveFileOrNotFound(domain, &w, r))
}
metrics.ProcessedRequests.WithLabelValues(strconv.Itoa(w.status), r.Method).Inc()
}
+func (a *theApp) serveFileOrNotFound(domain *domain.D, ww http.ResponseWriter, r *http.Request) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+ fileServed := domain.ServeFileHTTP(w, r)
+
+ if !fileServed {
+ // We need to trigger authentication flow here if file does not exist to prevent exposing possibly private project existence,
+ // because the projects override the paths of the namespace project and they might be private even though
+ // namespace project is public.
+ if domain.IsNamespaceProject(r) {
+
+ if a.Auth.CheckAuthenticationWithoutProject(ww, r) {
+ return
+ }
+
+ httperrors.Serve404(ww)
+ return
+ }
+
+ domain.ServeNotFoundHTTP(w, r)
+ }
+ }
+}
+
func (a *theApp) ServeHTTP(ww http.ResponseWriter, r *http.Request) {
https := r.TLS != nil
a.serveContent(ww, r, https)
diff --git a/internal/domain/domain.go b/internal/domain/domain.go
index d98a0b91..01ee1a46 100644
--- a/internal/domain/domain.go
+++ b/internal/domain/domain.go
@@ -24,9 +24,10 @@ type locationDirectoryError struct {
}
type project struct {
- HTTPSOnly bool
- AccessControl bool
- ID uint64
+ NamespaceProject bool
+ HTTPSOnly bool
+ AccessControl bool
+ ID uint64
}
type projects map[string]*project
@@ -154,6 +155,26 @@ func (d *D) IsAccessControlEnabled(r *http.Request) bool {
return false
}
+// IsNamespaceProject figures out if the request is to a namespace project
+func (d *D) IsNamespaceProject(r *http.Request) bool {
+ if d == nil {
+ return false
+ }
+
+ // If request is to a custom domain, we do not handle it as a namespace project
+ // as there can't be multiple projects under the same custom domain
+ if d.config != nil {
+ return false
+ }
+
+ // Check projects served under the group domain, including the default one
+ if project := d.getProject(r); project != nil {
+ return project.NamespaceProject
+ }
+
+ return false
+}
+
// GetID figures out what is the ID of the project user tries to access
func (d *D) GetID(r *http.Request) uint64 {
if d == nil {
@@ -324,20 +345,27 @@ func (d *D) tryFile(w http.ResponseWriter, r *http.Request, projectName, pathSuf
return d.serveFile(w, r, fullPath)
}
-func (d *D) serveFromGroup(w http.ResponseWriter, r *http.Request) {
+func (d *D) serveFileFromGroup(w http.ResponseWriter, r *http.Request) bool {
// The Path always contains "/" at the beginning
split := strings.SplitN(r.URL.Path, "/", 3)
// Try to serve file for http://group.example.com/subpath/... => /group/subpath/...
if len(split) >= 2 && d.tryFile(w, r, split[1], split[1], split[2:]...) == nil {
- return
+ return true
}
// Try to serve file for http://group.example.com/... => /group/group.example.com/...
if r.Host != "" && d.tryFile(w, r, strings.ToLower(r.Host), "", r.URL.Path) == nil {
- return
+ return true
}
+ return false
+}
+
+func (d *D) serveNotFoundFromGroup(w http.ResponseWriter, r *http.Request) {
+ // The Path always contains "/" at the beginning
+ split := strings.SplitN(r.URL.Path, "/", 3)
+
// Try serving not found page for http://group.example.com/subpath/ => /group/subpath/404.html
if len(split) >= 2 && d.tryNotFound(w, r, split[1]) == nil {
return
@@ -352,12 +380,16 @@ func (d *D) serveFromGroup(w http.ResponseWriter, r *http.Request) {
httperrors.Serve404(w)
}
-func (d *D) serveFromConfig(w http.ResponseWriter, r *http.Request) {
+func (d *D) serveFileFromConfig(w http.ResponseWriter, r *http.Request) bool {
// Try to serve file for http://host/... => /group/project/...
if d.tryFile(w, r, d.projectName, "", r.URL.Path) == nil {
- return
+ return true
}
+ return false
+}
+
+func (d *D) serveNotFoundFromConfig(w http.ResponseWriter, r *http.Request) {
// Try serving not found page for http://host/ => /group/project/404.html
if d.tryNotFound(w, r, d.projectName) == nil {
return
@@ -384,18 +416,32 @@ func (d *D) EnsureCertificate() (*tls.Certificate, error) {
return d.certificate, d.certificateError
}
-// ServeHTTP implements http.Handler.
-func (d *D) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+// ServeFileHTTP implements http.Handler. Returns true if something was served, false if not.
+func (d *D) ServeFileHTTP(w http.ResponseWriter, r *http.Request) bool {
+ if d == nil {
+ httperrors.Serve404(w)
+ return true
+ }
+
+ if d.config != nil {
+ return d.serveFileFromConfig(w, r)
+ }
+
+ return d.serveFileFromGroup(w, r)
+}
+
+// ServeNotFoundHTTP implements http.Handler. Serves the not found pages from the projects.
+func (d *D) ServeNotFoundHTTP(w http.ResponseWriter, r *http.Request) {
if d == nil {
httperrors.Serve404(w)
return
}
if d.config != nil {
- d.serveFromConfig(w, r)
- } else {
- d.serveFromGroup(w, r)
+ d.serveNotFoundFromConfig(w, r)
}
+
+ d.serveNotFoundFromGroup(w, r)
}
func endsWithSlash(path string) bool {
diff --git a/internal/domain/domain_test.go b/internal/domain/domain_test.go
index 64d11a29..4c08aee5 100644
--- a/internal/domain/domain_test.go
+++ b/internal/domain/domain_test.go
@@ -17,6 +17,14 @@ import (
"gitlab.com/gitlab-org/gitlab-pages/internal/fixture"
)
+func serveFileOrNotFound(domain *D) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+ if !domain.ServeFileHTTP(w, r) {
+ domain.ServeNotFoundHTTP(w, r)
+ }
+ }
+}
+
func TestGroupServeHTTP(t *testing.T) {
setUpTests()
@@ -25,27 +33,27 @@ func TestGroupServeHTTP(t *testing.T) {
projectName: "",
}
- assert.HTTPBodyContains(t, testGroup.ServeHTTP, "GET", "http://group.test.io/", nil, "main-dir")
- assert.HTTPBodyContains(t, testGroup.ServeHTTP, "GET", "http://group.test.io/index.html", nil, "main-dir")
- assert.HTTPRedirect(t, testGroup.ServeHTTP, "GET", "http://group.test.io/project", nil)
- assert.HTTPBodyContains(t, testGroup.ServeHTTP, "GET", "http://group.test.io/project", nil,
+ assert.HTTPBodyContains(t, serveFileOrNotFound(testGroup), "GET", "http://group.test.io/", nil, "main-dir")
+ assert.HTTPBodyContains(t, serveFileOrNotFound(testGroup), "GET", "http://group.test.io/index.html", nil, "main-dir")
+ assert.HTTPRedirect(t, serveFileOrNotFound(testGroup), "GET", "http://group.test.io/project", nil)
+ assert.HTTPBodyContains(t, serveFileOrNotFound(testGroup), "GET", "http://group.test.io/project", nil,
`<a href="//group.test.io/project/">Found</a>`)
- assert.HTTPBodyContains(t, testGroup.ServeHTTP, "GET", "http://group.test.io/project/", nil, "project-subdir")
- assert.HTTPBodyContains(t, testGroup.ServeHTTP, "GET", "http://group.test.io/project/index.html", nil, "project-subdir")
- assert.HTTPRedirect(t, testGroup.ServeHTTP, "GET", "http://group.test.io/project/subdir", nil)
- assert.HTTPBodyContains(t, testGroup.ServeHTTP, "GET", "http://group.test.io/project/subdir", nil,
+ assert.HTTPBodyContains(t, serveFileOrNotFound(testGroup), "GET", "http://group.test.io/project/", nil, "project-subdir")
+ assert.HTTPBodyContains(t, serveFileOrNotFound(testGroup), "GET", "http://group.test.io/project/index.html", nil, "project-subdir")
+ assert.HTTPRedirect(t, serveFileOrNotFound(testGroup), "GET", "http://group.test.io/project/subdir", nil)
+ assert.HTTPBodyContains(t, serveFileOrNotFound(testGroup), "GET", "http://group.test.io/project/subdir", nil,
`<a href="//group.test.io/project/subdir/">Found</a>`)
- assert.HTTPBodyContains(t, testGroup.ServeHTTP, "GET", "http://group.test.io/project/subdir/", nil, "project-subsubdir")
- assert.HTTPBodyContains(t, testGroup.ServeHTTP, "GET", "http://group.test.io/project2/", nil, "project2-main")
- assert.HTTPBodyContains(t, testGroup.ServeHTTP, "GET", "http://group.test.io/project2/index.html", nil, "project2-main")
- assert.HTTPRedirect(t, testGroup.ServeHTTP, "GET", "http://group.test.io/private.project/", nil)
- assert.HTTPError(t, testGroup.ServeHTTP, "GET", "http://group.test.io//about.gitlab.com/%2e%2e", nil)
- assert.HTTPError(t, testGroup.ServeHTTP, "GET", "http://group.test.io/symlink", nil)
- assert.HTTPError(t, testGroup.ServeHTTP, "GET", "http://group.test.io/symlink/index.html", nil)
- assert.HTTPError(t, testGroup.ServeHTTP, "GET", "http://group.test.io/symlink/subdir/", nil)
- assert.HTTPError(t, testGroup.ServeHTTP, "GET", "http://group.test.io/project/fifo", nil)
- assert.HTTPError(t, testGroup.ServeHTTP, "GET", "http://group.test.io/not-existing-file", nil)
- assert.HTTPError(t, testGroup.ServeHTTP, "GET", "http://group.test.io/project//about.gitlab.com/%2e%2e", nil)
+ assert.HTTPBodyContains(t, serveFileOrNotFound(testGroup), "GET", "http://group.test.io/project/subdir/", nil, "project-subsubdir")
+ assert.HTTPBodyContains(t, serveFileOrNotFound(testGroup), "GET", "http://group.test.io/project2/", nil, "project2-main")
+ assert.HTTPBodyContains(t, serveFileOrNotFound(testGroup), "GET", "http://group.test.io/project2/index.html", nil, "project2-main")
+ assert.HTTPRedirect(t, serveFileOrNotFound(testGroup), "GET", "http://group.test.io/private.project/", nil)
+ assert.HTTPError(t, serveFileOrNotFound(testGroup), "GET", "http://group.test.io//about.gitlab.com/%2e%2e", nil)
+ assert.HTTPError(t, serveFileOrNotFound(testGroup), "GET", "http://group.test.io/symlink", nil)
+ assert.HTTPError(t, serveFileOrNotFound(testGroup), "GET", "http://group.test.io/symlink/index.html", nil)
+ assert.HTTPError(t, serveFileOrNotFound(testGroup), "GET", "http://group.test.io/symlink/subdir/", nil)
+ assert.HTTPError(t, serveFileOrNotFound(testGroup), "GET", "http://group.test.io/project/fifo", nil)
+ assert.HTTPError(t, serveFileOrNotFound(testGroup), "GET", "http://group.test.io/not-existing-file", nil)
+ assert.HTTPError(t, serveFileOrNotFound(testGroup), "GET", "http://group.test.io/project//about.gitlab.com/%2e%2e", nil)
}
func TestDomainServeHTTP(t *testing.T) {
@@ -59,15 +67,15 @@ func TestDomainServeHTTP(t *testing.T) {
},
}
- assert.HTTPBodyContains(t, testDomain.ServeHTTP, "GET", "/", nil, "project2-main")
- assert.HTTPBodyContains(t, testDomain.ServeHTTP, "GET", "/index.html", nil, "project2-main")
- assert.HTTPRedirect(t, testDomain.ServeHTTP, "GET", "/subdir", nil)
- assert.HTTPBodyContains(t, testDomain.ServeHTTP, "GET", "/subdir", nil,
+ assert.HTTPBodyContains(t, serveFileOrNotFound(testDomain), "GET", "/", nil, "project2-main")
+ assert.HTTPBodyContains(t, serveFileOrNotFound(testDomain), "GET", "/index.html", nil, "project2-main")
+ assert.HTTPRedirect(t, serveFileOrNotFound(testDomain), "GET", "/subdir", nil)
+ assert.HTTPBodyContains(t, serveFileOrNotFound(testDomain), "GET", "/subdir", nil,
`<a href="/subdir/">Found</a>`)
- assert.HTTPBodyContains(t, testDomain.ServeHTTP, "GET", "/subdir/", nil, "project2-subdir")
- assert.HTTPBodyContains(t, testDomain.ServeHTTP, "GET", "/subdir/index.html", nil, "project2-subdir")
- assert.HTTPError(t, testDomain.ServeHTTP, "GET", "//about.gitlab.com/%2e%2e", nil)
- assert.HTTPError(t, testDomain.ServeHTTP, "GET", "/not-existing-file", nil)
+ assert.HTTPBodyContains(t, serveFileOrNotFound(testDomain), "GET", "/subdir/", nil, "project2-subdir")
+ assert.HTTPBodyContains(t, serveFileOrNotFound(testDomain), "GET", "/subdir/index.html", nil, "project2-subdir")
+ assert.HTTPError(t, serveFileOrNotFound(testDomain), "GET", "//about.gitlab.com/%2e%2e", nil)
+ assert.HTTPError(t, serveFileOrNotFound(testDomain), "GET", "/not-existing-file", nil)
}
func TestIsHTTPSOnly(t *testing.T) {
@@ -238,7 +246,7 @@ func TestGroupServeHTTPGzip(t *testing.T) {
}
for _, tt := range testSet {
- testHTTPGzip(t, testGroup.ServeHTTP, tt.mode, tt.url, tt.params, tt.acceptEncoding, tt.body, tt.ungzip)
+ testHTTPGzip(t, serveFileOrNotFound(testGroup), tt.mode, tt.url, tt.params, tt.acceptEncoding, tt.body, tt.ungzip)
}
}
@@ -262,13 +270,13 @@ func TestGroup404ServeHTTP(t *testing.T) {
projectName: "",
}
- testHTTP404(t, testGroup.ServeHTTP, "GET", "http://group.404.test.io/project.404/not/existing-file", nil, "Custom 404 project page")
- testHTTP404(t, testGroup.ServeHTTP, "GET", "http://group.404.test.io/project.404/", nil, "Custom 404 project page")
- testHTTP404(t, testGroup.ServeHTTP, "GET", "http://group.404.test.io/project.no.404/not/existing-file", nil, "Custom 404 group page")
- testHTTP404(t, testGroup.ServeHTTP, "GET", "http://group.404.test.io/not/existing-file", nil, "Custom 404 group page")
- testHTTP404(t, testGroup.ServeHTTP, "GET", "http://group.404.test.io/not-existing-file", nil, "Custom 404 group page")
- testHTTP404(t, testGroup.ServeHTTP, "GET", "http://group.404.test.io/", nil, "Custom 404 group page")
- assert.HTTPBodyNotContains(t, testGroup.ServeHTTP, "GET", "http://group.404.test.io/project.404.symlink/not/existing-file", nil, "Custom 404 project page")
+ testHTTP404(t, serveFileOrNotFound(testGroup), "GET", "http://group.404.test.io/project.404/not/existing-file", nil, "Custom 404 project page")
+ testHTTP404(t, serveFileOrNotFound(testGroup), "GET", "http://group.404.test.io/project.404/", nil, "Custom 404 project page")
+ testHTTP404(t, serveFileOrNotFound(testGroup), "GET", "http://group.404.test.io/project.no.404/not/existing-file", nil, "Custom 404 group page")
+ testHTTP404(t, serveFileOrNotFound(testGroup), "GET", "http://group.404.test.io/not/existing-file", nil, "Custom 404 group page")
+ testHTTP404(t, serveFileOrNotFound(testGroup), "GET", "http://group.404.test.io/not-existing-file", nil, "Custom 404 group page")
+ testHTTP404(t, serveFileOrNotFound(testGroup), "GET", "http://group.404.test.io/", nil, "Custom 404 group page")
+ assert.HTTPBodyNotContains(t, serveFileOrNotFound(testGroup), "GET", "http://group.404.test.io/project.404.symlink/not/existing-file", nil, "Custom 404 project page")
}
func TestDomain404ServeHTTP(t *testing.T) {
@@ -282,8 +290,8 @@ func TestDomain404ServeHTTP(t *testing.T) {
},
}
- testHTTP404(t, testDomain.ServeHTTP, "GET", "http://group.404.test.io/not-existing-file", nil, "Custom 404 group page")
- testHTTP404(t, testDomain.ServeHTTP, "GET", "http://group.404.test.io/", nil, "Custom 404 group page")
+ testHTTP404(t, serveFileOrNotFound(testDomain), "GET", "http://group.404.test.io/not-existing-file", nil, "Custom 404 group page")
+ testHTTP404(t, serveFileOrNotFound(testDomain), "GET", "http://group.404.test.io/", nil, "Custom 404 group page")
}
func TestPredefined404ServeHTTP(t *testing.T) {
@@ -293,7 +301,7 @@ func TestPredefined404ServeHTTP(t *testing.T) {
group: "group",
}
- testHTTP404(t, testDomain.ServeHTTP, "GET", "http://group.test.io/not-existing-file", nil, "The page you're looking for could not be found")
+ testHTTP404(t, serveFileOrNotFound(testDomain), "GET", "http://group.test.io/not-existing-file", nil, "The page you're looking for could not be found")
}
func TestGroupCertificate(t *testing.T) {
@@ -348,7 +356,7 @@ func TestCacheControlHeaders(t *testing.T) {
require.NoError(t, err)
now := time.Now()
- testGroup.ServeHTTP(w, req)
+ serveFileOrNotFound(testGroup)(w, req)
assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, "max-age=600", w.Header().Get("Cache-Control"))
diff --git a/internal/domain/map.go b/internal/domain/map.go
index b832ffb3..d2e7c74f 100644
--- a/internal/domain/map.go
+++ b/internal/domain/map.go
@@ -58,9 +58,10 @@ func (dm Map) updateGroupDomain(rootDomain, group, projectName string, httpsOnly
}
groupDomain.projects[strings.ToLower(projectName)] = &project{
- HTTPSOnly: httpsOnly,
- AccessControl: accessControl,
- ID: id,
+ NamespaceProject: domainName == strings.ToLower(projectName),
+ HTTPSOnly: httpsOnly,
+ AccessControl: accessControl,
+ ID: id,
}
dm[domainName] = groupDomain
diff --git a/shared/pages/group.auth/group.auth.gitlab-example.com/public/index.html b/shared/pages/group.auth/group.auth.gitlab-example.com/public/index.html
new file mode 100644
index 00000000..d86bac9d
--- /dev/null
+++ b/shared/pages/group.auth/group.auth.gitlab-example.com/public/index.html
@@ -0,0 +1 @@
+OK
diff --git a/shared/pages/group.auth/group.auth.gitlab-example.com/public/private.project/index.html b/shared/pages/group.auth/group.auth.gitlab-example.com/public/private.project/index.html
new file mode 100644
index 00000000..7c9933f7
--- /dev/null
+++ b/shared/pages/group.auth/group.auth.gitlab-example.com/public/private.project/index.html
@@ -0,0 +1 @@
+domain project subdirectory