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:
authorAlessio Caiazza <acaiazza@gitlab.com>2023-11-28 16:35:27 +0300
committerAlessio Caiazza <acaiazza@gitlab.com>2023-11-28 16:35:27 +0300
commit121f1e96d05346988e88349597ee4992dfdd9b15 (patch)
treebe015457e45fb14e7cf78fcad31b646800f0446a
parent93d2dc70ccac3920439712fba3afde55f05777fa (diff)
parent7e9d46766d1926f93bb4a0d6f00d03d122316ad6 (diff)
Merge branch 'naman/17584-pages-namespace-in-path' into 'master'
Add path in session cookie when namespace is provided in path See merge request https://gitlab.com/gitlab-org/gitlab-pages/-/merge_requests/929 Merged-by: Alessio Caiazza <acaiazza@gitlab.com> Approved-by: Alessio Caiazza <acaiazza@gitlab.com> Co-authored-by: ngala <ngala@gitlab.com>
-rw-r--r--app.go1
-rw-r--r--internal/auth/auth.go39
-rw-r--r--internal/auth/auth_test.go51
-rw-r--r--internal/auth/session.go19
-rw-r--r--internal/config/config.go4
-rw-r--r--internal/config/flags.go2
6 files changed, 108 insertions, 8 deletions
diff --git a/app.go b/app.go
index b022d7c2..086e4493 100644
--- a/app.go
+++ b/app.go
@@ -422,6 +422,7 @@ func (a *theApp) setAuth(config *cfg.Config) error {
AuthScope: config.Authentication.Scope,
AuthTimeout: config.Authentication.Timeout,
CookieSessionTimeout: config.Authentication.CookieSessionTimeout,
+ AllowNamespaceInPath: config.General.NamespaceInPath,
})
if err != nil {
return fmt.Errorf("could not initialize auth package: %w", err)
diff --git a/internal/auth/auth.go b/internal/auth/auth.go
index dcc81eee..fe092ae9 100644
--- a/internal/auth/auth.go
+++ b/internal/auth/auth.go
@@ -67,6 +67,7 @@ type Auth struct {
store sessions.Store
now func() time.Time // allows to stub time.Now() easily in tests
cookieSessionTimeout time.Duration
+ allowNamespaceInPath bool
}
type tokenResponse struct {
@@ -140,7 +141,8 @@ func (a *Auth) checkAuthenticationResponse(session *hostSession, w http.Response
return
}
- decryptedCode, err := a.DecryptCode(r.URL.Query().Get("code"), getRequestDomain(r))
+ decryptedCode, err := a.DecryptCode(r.URL.Query().Get("code"),
+ getRequestDomain(r, session.getNamespaceInPathFromSession()))
if err != nil {
logRequest(r).WithError(err).Error("failed to decrypt secure code")
errortracking.CaptureErrWithReqAndStackTrace(err, r)
@@ -317,11 +319,16 @@ func getRequestAddress(r *http.Request) string {
return "http://" + r.Host + r.RequestURI
}
-func getRequestDomain(r *http.Request) string {
+func getRequestDomain(r *http.Request, namespace string) string {
+ requestDomain := r.Host
+ if len(namespace) > 0 && strings.HasPrefix(r.Host, namespace) {
+ requestDomain = strings.TrimPrefix(r.Host, namespace+".") + "/" + namespace
+ }
+
if request.IsHTTPS(r) {
- return "https://" + r.Host
+ return "https://" + requestDomain
}
- return "http://" + r.Host
+ return "http://" + requestDomain
}
func shouldProxyAuthToGitlab(r *http.Request) bool {
@@ -440,15 +447,18 @@ func (a *Auth) checkTokenExists(session *hostSession, w http.ResponseWriter, r *
// Because the pages domain might be in public suffix list, we have to
// redirect to pages domain to trigger authorization flow
- http.Redirect(w, r, a.getProxyAddress(r, session.Values["state"].(string)), http.StatusFound)
+ http.Redirect(w,
+ r,
+ a.getProxyAddress(r, session.Values["state"].(string), session.getNamespaceInPathFromSession()),
+ http.StatusFound)
return true
}
return false
}
-func (a *Auth) getProxyAddress(r *http.Request, state string) string {
- return fmt.Sprintf(authorizeProxyTemplate, a.redirectURI, getRequestDomain(r), state)
+func (a *Auth) getProxyAddress(r *http.Request, state string, namespace string) string {
+ return fmt.Sprintf(authorizeProxyTemplate, a.redirectURI, getRequestDomain(r, namespace), state)
}
func destroySession(session *hostSession, w http.ResponseWriter, r *http.Request) {
@@ -602,6 +612,19 @@ func (a *Auth) CheckResponseForInvalidToken(w http.ResponseWriter, r *http.Reque
return false
}
+func (a *Auth) getNamespaceInPath(r *http.Request) string {
+ if !a.allowNamespaceInPath {
+ return ""
+ }
+
+ namespaceInPath := r.Header.Get("X-Gitlab-Namespace-In-Path")
+ if namespaceInPath == "" || !strings.HasPrefix(r.Host, namespaceInPath+"."+a.pagesDomain) {
+ return ""
+ }
+
+ return namespaceInPath
+}
+
func checkResponseForInvalidToken(resp *http.Response, session *hostSession, w http.ResponseWriter, r *http.Request) bool {
if resp.StatusCode == http.StatusUnauthorized {
errResp := errorResponse{}
@@ -664,6 +687,7 @@ type Options struct {
AuthScope string
AuthTimeout time.Duration
CookieSessionTimeout time.Duration
+ AllowNamespaceInPath bool
}
// New when authentication supported this will be used to create authentication handler
@@ -692,5 +716,6 @@ func New(options *Options) (*Auth, error) {
jwtExpiry: time.Minute,
now: time.Now,
cookieSessionTimeout: options.CookieSessionTimeout,
+ allowNamespaceInPath: options.AllowNamespaceInPath,
}, nil
}
diff --git a/internal/auth/auth_test.go b/internal/auth/auth_test.go
index 40c6db15..8582a973 100644
--- a/internal/auth/auth_test.go
+++ b/internal/auth/auth_test.go
@@ -33,6 +33,7 @@ func createTestAuth(t *testing.T, internalServer string, publicServer string) *A
AuthScope: "scope",
AuthTimeout: 5 * time.Second,
CookieSessionTimeout: 10 * time.Minute,
+ AllowNamespaceInPath: false,
})
require.NoError(t, err)
@@ -63,6 +64,9 @@ func setSessionValues(t *testing.T, r *http.Request, auth *Auth, values map[inte
t.Helper()
tmpRequest, err := http.NewRequest("GET", "http://"+r.Host, nil)
+ if len(r.Header.Get("X-Gitlab-Namespace-In-Path")) > 0 {
+ tmpRequest.Header.Set("X-Gitlab-Namespace-In-Path", r.Header.Get("X-Gitlab-Namespace-In-Path"))
+ }
require.NoError(t, err)
result := httptest.NewRecorder()
@@ -629,3 +633,50 @@ func TestDomainAllowed(t *testing.T) {
})
}
}
+
+func testNamespaceInPath(t *testing.T, allowNamespaceInPath bool, namespace string, expectedPath string, msg string) {
+ auth := createTestAuth(t, "", "")
+ auth.allowNamespaceInPath = allowNamespaceInPath
+
+ result := httptest.NewRecorder()
+
+ r, err := http.NewRequest("Get", "https://namespace.pages.gitlab-example.com", nil)
+ r.Header.Set("X-Gitlab-Namespace-In-Path", namespace)
+ require.NoError(t, err)
+
+ // pre-set an state
+ setSessionValues(t, r, auth, map[interface{}]interface{}{
+ "state": "given_state",
+ })
+
+ contentServed := auth.CheckAuthentication(result, r, &domainMock{projectID: 1000})
+ require.True(t, contentServed)
+
+ // check if the state was re-used instead of re-created
+ session, _ := auth.getSessionFromStore(r)
+ require.Equal(t, expectedPath, session.Options.Path, msg)
+}
+
+func TestValidSessionPathWhenNamespaceInPathIsDisabled(t *testing.T) {
+ testNamespaceInPath(t,
+ false,
+ "namespace",
+ "/",
+ "did not set path as '/' in session options")
+}
+
+func TestValidSessionPathWhenNamespaceInPathIsEnabled(t *testing.T) {
+ testNamespaceInPath(t,
+ true,
+ "namespace",
+ "/namespace",
+ "did not set namespace path in session options")
+}
+
+func TestForgedNamespaceWhenNamespaceInPathIsEnabled(t *testing.T) {
+ testNamespaceInPath(t,
+ true,
+ "namespace-forged",
+ "/",
+ "did not set path as '/' in session options")
+}
diff --git a/internal/auth/session.go b/internal/auth/session.go
index 5bfc8e03..c9b4ee8d 100644
--- a/internal/auth/session.go
+++ b/internal/auth/session.go
@@ -4,6 +4,7 @@ import (
"net/http"
"github.com/gorilla/sessions"
+ "gitlab.com/gitlab-org/labkit/log"
"gitlab.com/gitlab-org/gitlab-pages/internal/errortracking"
"gitlab.com/gitlab-org/gitlab-pages/internal/httperrors"
@@ -22,17 +23,33 @@ func (s *hostSession) Save(r *http.Request, w http.ResponseWriter) error {
return s.Session.Save(r, w)
}
+func (s *hostSession) getNamespaceInPathFromSession() string {
+ namespaceInPath := ""
+ if len(s.Options.Path) > 1 && s.Options.Path[0] == '/' {
+ namespaceInPath = s.Options.Path[1:]
+ }
+ return namespaceInPath
+}
+
func (a *Auth) getSessionFromStore(r *http.Request) (*hostSession, error) {
session, err := a.store.Get(r, "gitlab-pages")
if session != nil {
+ namespaceInPath := a.getNamespaceInPath(r)
+
// Cookie just for this domain
- session.Options.Path = "/"
+ session.Options.Path = "/" + namespaceInPath
session.Options.HttpOnly = true
session.Options.Secure = request.IsHTTPS(r)
session.Options.MaxAge = int(a.cookieSessionTimeout.Seconds())
if session.Values[sessionHostKey] == nil || session.Values[sessionHostKey] != r.Host {
+ logRequest(r).WithFields(log.Fields{
+ "Session host": session.Values[sessionHostKey],
+ "Request host": r.Host,
+ "Namespace in path": namespaceInPath,
+ }).Info("Resetting session values")
+
session.Values = make(map[interface{}]interface{})
}
}
diff --git a/internal/config/config.go b/internal/config/config.go
index d2016550..ba227016 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -61,6 +61,8 @@ type General struct {
ShowVersion bool
CustomHeaders http.Header
+
+ NamespaceInPath bool
}
// RateLimit config struct
@@ -317,6 +319,7 @@ func loadConfig() (*Config, error) {
InsecureCiphers: *insecureCiphers,
PropagateCorrelationID: *propagateCorrelationID,
ShowVersion: *showVersion,
+ NamespaceInPath: *namespaceInPath,
},
RateLimit: RateLimit{
SourceIPLimitPerSecond: *rateLimitSourceIP,
@@ -518,6 +521,7 @@ func logFields(config *Config) map[string]any {
"sentry-dsn": config.Sentry.DSN,
"sentry-environment": config.Sentry.Environment,
"version": config.General.ShowVersion,
+ "namespace-in-path": *namespaceInPath,
}
}
diff --git a/internal/config/flags.go b/internal/config/flags.go
index bce3b07e..069173c3 100644
--- a/internal/config/flags.go
+++ b/internal/config/flags.go
@@ -107,6 +107,8 @@ var (
header = MultiStringFlag{separator: ";;"}
+ namespaceInPath = flag.Bool("namespace-in-path", false, "Enable Namespace in path")
+
// flags that won't be logged to the output on Pages boot
nonLoggableFlags = map[string]bool{
"auth-client-id": true,