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-08-07 20:16:26 +0300
committerTuomo Ala-Vannesluoma <tuomoav@gmail.com>2018-08-07 20:31:02 +0300
commit90690a9d77b673df5845f05d626ff8f6e75529c7 (patch)
treeeff442c8af947a379badc80018a79efe0df1b0cc
parent2666c24dacb27efd22ad78044d4f321beed63772 (diff)
Make private pages public if gitlab and pages is ran without access control, add support for custom domains for which auth is proxied via gitlab pages domain
-rw-r--r--acceptance_test.go85
-rw-r--r--internal/auth/auth.go143
-rw-r--r--internal/domain/domain.go12
-rw-r--r--internal/domain/domain_config.go10
-rw-r--r--internal/domain/map_test.go1
-rw-r--r--shared/pages/group/private.project/config.json11
6 files changed, 228 insertions, 34 deletions
diff --git a/acceptance_test.go b/acceptance_test.go
index 483f4295..8a842290 100644
--- a/acceptance_test.go
+++ b/acceptance_test.go
@@ -298,7 +298,7 @@ func TestPrometheusMetricsCanBeScraped(t *testing.T) {
body, _ := ioutil.ReadAll(resp.Body)
assert.Contains(t, string(body), "gitlab_pages_http_sessions_active 0")
- assert.Contains(t, string(body), "gitlab_pages_domains_served_total 11")
+ assert.Contains(t, string(body), "gitlab_pages_domains_served_total 12")
}
}
@@ -576,7 +576,7 @@ func TestKnownHostInReverseProxySetupReturns200(t *testing.T) {
}
}
-func TestWhenAuthIsDisabledPrivateIsNotAccessible(t *testing.T) {
+func TestWhenAuthIsDisabledPrivateIsAccessible(t *testing.T) {
skipUnlessEnabled(t)
teardown := RunPagesProcess(t, *pagesBinary, listeners, "", "")
defer teardown()
@@ -585,7 +585,7 @@ func TestWhenAuthIsDisabledPrivateIsNotAccessible(t *testing.T) {
require.NoError(t, err)
rsp.Body.Close()
- assert.Equal(t, http.StatusInternalServerError, rsp.StatusCode)
+ assert.Equal(t, http.StatusOK, rsp.StatusCode)
}
func TestWhenAuthIsEnabledPrivateWillRedirectToAuthorize(t *testing.T) {
@@ -669,6 +669,85 @@ func TestWhenLoginCallbackWithCorrectStateWithoutEndpoint(t *testing.T) {
assert.Equal(t, http.StatusServiceUnavailable, authrsp.StatusCode)
}
+func TestAccessControlUnderCustomDomain(t *testing.T) {
+ skipUnlessEnabled(t, "not-inplace-chroot")
+
+ testServer := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ switch r.URL.Path {
+ case "/oauth/token":
+ assert.Equal(t, "POST", r.Method)
+ w.WriteHeader(http.StatusOK)
+ fmt.Fprint(w, "{\"access_token\":\"abc\"}")
+ case "/api/v4/projects/1000/pages_access":
+ assert.Equal(t, "Bearer abc", r.Header.Get("Authorization"))
+ w.WriteHeader(http.StatusOK)
+ default:
+ t.Logf("Unexpected r.URL.RawPath: %q", r.URL.Path)
+ w.Header().Set("Content-Type", "text/html; charset=utf-8")
+ w.WriteHeader(http.StatusNotFound)
+ }
+ }))
+ testServer.Start()
+ defer testServer.Close()
+
+ teardown := RunPagesProcessWithAuthServer(t, *pagesBinary, listeners, "", testServer.URL)
+ defer teardown()
+
+ rsp, err := GetRedirectPage(t, httpListener, "private.domain.com", "/")
+ require.NoError(t, err)
+ defer rsp.Body.Close()
+
+ cookie := rsp.Header.Get("Set-Cookie")
+
+ url, err := url.Parse(rsp.Header.Get("Location"))
+ require.NoError(t, err)
+
+ state := url.Query().Get("state")
+ assert.Equal(t, url.Query().Get("domain"), "private.domain.com")
+
+ pagesrsp, err := GetRedirectPage(t, httpListener, url.Host, url.Path+"?"+url.RawQuery)
+ require.NoError(t, err)
+ defer pagesrsp.Body.Close()
+
+ pagescookie := pagesrsp.Header.Get("Set-Cookie")
+
+ // Go to auth page with correct state will cause fetching the token
+ authrsp, err := GetRedirectPageWithCookie(t, httpListener, "gitlab-example.com", "/auth?code=1&state="+
+ state, pagescookie)
+
+ require.NoError(t, err)
+ defer authrsp.Body.Close()
+
+ url, err = url.Parse(authrsp.Header.Get("Location"))
+ require.NoError(t, err)
+
+ // Will redirect to custom domain
+ assert.Equal(t, "private.domain.com", url.Host)
+ assert.Equal(t, "1", url.Query().Get("code"))
+ assert.Equal(t, state, url.Query().Get("state"))
+
+ // Run auth callback in custom domain
+ authrsp, err = GetRedirectPageWithCookie(t, httpListener, "private.domain.com", "/auth?code=1&state="+
+ state, cookie)
+
+ require.NoError(t, err)
+ defer authrsp.Body.Close()
+
+ // Will redirect to the page
+ cookie = authrsp.Header.Get("Set-Cookie")
+ assert.Equal(t, http.StatusFound, authrsp.StatusCode)
+
+ url, err = url.Parse(authrsp.Header.Get("Location"))
+ require.NoError(t, err)
+
+ // Will redirect to custom domain
+ assert.Equal(t, "http://private.domain.com/", url.String())
+
+ // Fetch page in custom domain
+ authrsp, err = GetRedirectPageWithCookie(t, httpListener, "private.domain.com", "/", cookie)
+ assert.Equal(t, http.StatusOK, authrsp.StatusCode)
+}
+
func TestAccessControl(t *testing.T) {
skipUnlessEnabled(t, "not-inplace-chroot")
diff --git a/internal/auth/auth.go b/internal/auth/auth.go
index dedb9341..ea185ea2 100644
--- a/internal/auth/auth.go
+++ b/internal/auth/auth.go
@@ -16,21 +16,23 @@ import (
)
const (
- apiURLUserTemplate = "%s/api/v4/user"
- apiURLProjectTemplate = "%s/api/v4/projects/%d/pages_access"
- authorizeURLTemplate = "%s/oauth/authorize?client_id=%s&redirect_uri=%s&response_type=code&state=%s"
- tokenURLTemplate = "%s/oauth/token"
- tokenContentTemplate = "client_id=%s&client_secret=%s&code=%s&grant_type=authorization_code&redirect_uri=%s"
- callbackPath = "/auth"
+ apiURLUserTemplate = "%s/api/v4/user"
+ apiURLProjectTemplate = "%s/api/v4/projects/%d/pages_access"
+ authorizeURLTemplate = "%s/oauth/authorize?client_id=%s&redirect_uri=%s&response_type=code&state=%s"
+ tokenURLTemplate = "%s/oauth/token"
+ tokenContentTemplate = "client_id=%s&client_secret=%s&code=%s&grant_type=authorization_code&redirect_uri=%s"
+ callbackPath = "/auth"
+ authorizeProxyTemplate = "%s/auth?domain=%s&state=%s"
)
// Auth handles authenticating users with GitLab API
type Auth struct {
+ pagesDomain string
clientID string
clientSecret string
redirectURI string
gitLabServer string
- store *sessions.CookieStore
+ storeSecret string
apiClient *http.Client
}
@@ -46,10 +48,30 @@ type errorResponse struct {
ErrorDescription string `json:"error_description"`
}
+func (a *Auth) getSessionFromStore(r *http.Request) (*sessions.Session, error) {
+ store := sessions.NewCookieStore([]byte(a.storeSecret))
+
+ if strings.HasSuffix(r.Host, a.pagesDomain) {
+ // GitLab pages wide cookie
+ store.Options = &sessions.Options{
+ Path: "/",
+ Domain: a.pagesDomain,
+ }
+ } else {
+ // Cookie just for this domain
+ store.Options = &sessions.Options{
+ Path: "/",
+ Domain: r.Host,
+ }
+ }
+
+ return store.Get(r, "gitlab-pages")
+}
+
func (a *Auth) checkSession(w http.ResponseWriter, r *http.Request) bool {
// Create or get session
- session, err := a.store.Get(r, "gitlab-pages")
+ session, err := a.getSessionFromStore(r)
if err != nil {
// Save cookie again
@@ -62,7 +84,7 @@ func (a *Auth) checkSession(w http.ResponseWriter, r *http.Request) bool {
}
func (a *Auth) getSession(r *http.Request) *sessions.Session {
- session, _ := a.store.Get(r, "gitlab-pages")
+ session, _ := a.getSessionFromStore(r)
return session
}
@@ -77,15 +99,19 @@ func (a *Auth) TryAuthenticate(w http.ResponseWriter, r *http.Request) bool {
return true
}
- log.Debug("Authentication callback")
-
session := a.getSession(r)
- // If callback from authentication and the state matches
+ // Request is for auth
if r.URL.Path != callbackPath {
return false
}
+ log.Debug("Authentication callback")
+
+ if a.handleProxyingAuth(session, w, r) {
+ return true
+ }
+
// If callback is not successful
errorParam := r.URL.Query().Get("error")
if errorParam != "" {
@@ -131,6 +157,47 @@ func (a *Auth) TryAuthenticate(w http.ResponseWriter, r *http.Request) bool {
return false
}
+func (a *Auth) handleProxyingAuth(session *sessions.Session, w http.ResponseWriter, r *http.Request) bool {
+ // If request is for authenticating via custom domain
+ if shouldProxyAuth(r) {
+ customDomain := r.URL.Query().Get("domain")
+ state := r.URL.Query().Get("state")
+ log.WithField("domain", customDomain).Debug("User is authenticating via custom domain")
+
+ if r.TLS != nil {
+ session.Values["proxy_auth_domain"] = "https://" + customDomain
+ } else {
+ session.Values["proxy_auth_domain"] = "http://" + customDomain
+ }
+ session.Save(r, w)
+
+ url := fmt.Sprintf(authorizeURLTemplate, a.gitLabServer, a.clientID, a.redirectURI, state)
+ http.Redirect(w, r, url, 302)
+
+ return true
+ }
+
+ // If auth request callback should be proxied to custom domain
+ if shouldProxyCallbackToCustomDomain(r, session) {
+ // Auth request is from custom domain, proxy callback there
+ log.Debug("Redirecting auth callback to custom domain")
+
+ // Store access token
+ proxyDomain := session.Values["proxy_auth_domain"].(string)
+
+ // Clear proxying from session
+ delete(session.Values, "proxy_auth_domain")
+ session.Save(r, w)
+
+ // Redirect pages under custom domain
+ http.Redirect(w, r, proxyDomain+r.URL.Path+"?"+r.URL.RawQuery, 302)
+
+ return true
+ }
+
+ return false
+}
+
func getRequestAddress(r *http.Request) string {
if r.TLS != nil {
return "https://" + r.Host + r.RequestURI
@@ -138,6 +205,21 @@ func getRequestAddress(r *http.Request) string {
return "http://" + r.Host + r.RequestURI
}
+func getRequestDomain(r *http.Request) string {
+ if r.TLS != nil {
+ return "https://" + r.Host
+ }
+ return "http://" + r.Host
+}
+
+func shouldProxyAuth(r *http.Request) bool {
+ return r.URL.Query().Get("domain") != "" && r.URL.Query().Get("state") != ""
+}
+
+func shouldProxyCallbackToCustomDomain(r *http.Request, session *sessions.Session) bool {
+ return session.Values["proxy_auth_domain"] != nil
+}
+
func validateState(r *http.Request, session *sessions.Session) bool {
state := r.URL.Query().Get("state")
if state == "" {
@@ -201,17 +283,33 @@ func (a *Auth) checkTokenExists(session *sessions.Session, w http.ResponseWriter
state := base64.URLEncoding.EncodeToString(securecookie.GenerateRandomKey(16))
session.Values["state"] = state
session.Values["uri"] = getRequestAddress(r)
+
+ // Clear possible proxying
+ delete(session.Values, "proxy_auth_domain")
+
session.Save(r, w)
- // Redirect to OAuth login
- url := fmt.Sprintf(authorizeURLTemplate, a.gitLabServer, a.clientID, a.redirectURI, state)
- http.Redirect(w, r, url, 302)
+ // If we are in custom domain, redirect to pages domain to trigger authorization flow
+ if !strings.HasSuffix(r.Host, a.pagesDomain) {
+ http.Redirect(w, r, a.getProxyAddress(r, state), 302)
+ } else {
+ // Otherwise just redirect to OAuth login
+ url := fmt.Sprintf(authorizeURLTemplate, a.gitLabServer, a.clientID, a.redirectURI, state)
+ http.Redirect(w, r, url, 302)
+ }
return true
}
return false
}
+func (a *Auth) getProxyAddress(r *http.Request, state string) string {
+ if r.TLS != nil {
+ return fmt.Sprintf(authorizeProxyTemplate, "https://"+a.pagesDomain, r.Host, state)
+ }
+ return fmt.Sprintf(authorizeProxyTemplate, "http://"+a.pagesDomain, r.Host, state)
+}
+
func destroySession(session *sessions.Session, w http.ResponseWriter, r *http.Request) {
log.Debug("Destroying session")
@@ -286,8 +384,8 @@ func (a *Auth) CheckAuthenticationWithoutProject(w http.ResponseWriter, r *http.
func (a *Auth) CheckAuthentication(w http.ResponseWriter, r *http.Request, projectID uint64) bool {
if a == nil {
- httperrors.Serve500(w)
- return true
+ log.Warn("Authentication is disabled, falling back to PUBLIC pages")
+ return false
}
if a.checkSession(w, r) {
@@ -355,20 +453,13 @@ func checkResponseForInvalidToken(resp *http.Response, err error) bool {
// New when authentication supported this will be used to create authentication handler
func New(pagesDomain string, storeSecret string, clientID string, clientSecret string,
redirectURI string, gitLabServer string) *Auth {
-
- store := sessions.NewCookieStore([]byte(storeSecret))
-
- store.Options = &sessions.Options{
- Path: "/",
- Domain: pagesDomain,
- }
-
return &Auth{
+ pagesDomain: pagesDomain,
clientID: clientID,
clientSecret: clientSecret,
redirectURI: redirectURI,
gitLabServer: strings.TrimRight(gitLabServer, "/"),
- store: store,
+ storeSecret: storeSecret,
apiClient: &http.Client{
Timeout: 5 * time.Second,
Transport: transport,
diff --git a/internal/domain/domain.go b/internal/domain/domain.go
index e32ba097..77429372 100644
--- a/internal/domain/domain.go
+++ b/internal/domain/domain.go
@@ -133,6 +133,10 @@ func (d *D) IsAccessControlEnabled(r *http.Request) bool {
return false
}
+ if d.config != nil {
+ return d.config.AccessControl
+ }
+
project := d.getProject(r)
if project != nil {
@@ -144,6 +148,14 @@ func (d *D) IsAccessControlEnabled(r *http.Request) bool {
// 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 {
+ return 0
+ }
+
+ if d.config != nil {
+ return d.config.ID
+ }
+
project := d.getProject(r)
if project != nil {
diff --git a/internal/domain/domain_config.go b/internal/domain/domain_config.go
index 672f939c..2ab2ce6c 100644
--- a/internal/domain/domain_config.go
+++ b/internal/domain/domain_config.go
@@ -8,10 +8,12 @@ import (
)
type domainConfig struct {
- Domain string
- Certificate string
- Key string
- HTTPSOnly bool `json:"https_only"`
+ Domain string
+ Certificate string
+ Key string
+ HTTPSOnly bool `json:"https_only"`
+ ID uint64 `json:"id"`
+ AccessControl bool `json:"access_control"`
}
type domainsConfig struct {
diff --git a/internal/domain/map_test.go b/internal/domain/map_test.go
index f20f98bd..45658e95 100644
--- a/internal/domain/map_test.go
+++ b/internal/domain/map_test.go
@@ -35,6 +35,7 @@ func TestReadProjects(t *testing.T) {
"test.my-domain.com",
"test2.my-domain.com",
"no.cert.com",
+ "private.domain.com",
}
for _, expected := range domains {
diff --git a/shared/pages/group/private.project/config.json b/shared/pages/group/private.project/config.json
index 292ba673..e7d754a0 100644
--- a/shared/pages/group/private.project/config.json
+++ b/shared/pages/group/private.project/config.json
@@ -1 +1,10 @@
-{ "domains": [], "id": 1000, "access_control": true }
+{ "domains": [
+ {
+ "domain": "private.domain.com",
+ "id": 1000,
+ "access_control": true
+ }
+ ],
+ "id": 1000,
+ "access_control": true
+}