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:
authorFrancisco Javier López <fjlopez@gitlab.com>2019-05-29 19:03:25 +0300
committerFrancisco Javier López <fjlopez@gitlab.com>2019-05-29 19:03:25 +0300
commit209939c0edb2512f5801ca2c90f962058c678120 (patch)
tree739f9eaea74efcff682177356c08f1f09beaf425
parentef7fff4fa64c9cb3ca57faef3f26fa59f4f51ecb (diff)
-rw-r--r--app.go32
-rw-r--r--client.html45
-rw-r--r--internal/auth/auth.go42
-rw-r--r--internal/buildservice/buildservice.go120
4 files changed, 205 insertions, 34 deletions
diff --git a/app.go b/app.go
index 7d7ca9a2..9d6a76ef 100644
--- a/app.go
+++ b/app.go
@@ -20,6 +20,7 @@ import (
"gitlab.com/gitlab-org/gitlab-pages/internal/admin"
"gitlab.com/gitlab-org/gitlab-pages/internal/artifact"
"gitlab.com/gitlab-org/gitlab-pages/internal/auth"
+ "gitlab.com/gitlab-org/gitlab-pages/internal/buildservice"
"gitlab.com/gitlab-org/gitlab-pages/internal/domain"
"gitlab.com/gitlab-org/gitlab-pages/internal/httperrors"
"gitlab.com/gitlab-org/gitlab-pages/internal/netutil"
@@ -38,10 +39,11 @@ var (
type theApp struct {
appConfig
- dm domain.Map
- lock sync.RWMutex
- Artifact *artifact.Artifact
- Auth *auth.Auth
+ dm domain.Map
+ lock sync.RWMutex
+ Artifact *artifact.Artifact
+ BuildService *buildservice.BuildService
+ Auth *auth.Auth
}
func (a *theApp) isReady() bool {
@@ -95,12 +97,15 @@ func (a *theApp) getHostAndDomain(r *http.Request) (host string, domain *domain.
return host, a.domain(host)
}
+// IsAuthSupported checks if pages is running with the authentication support
+func (a *theApp) IsAuthSupported() bool {
+ return a.Auth != nil
+}
+
func (a *theApp) checkAuthenticationIfNotExists(domain *domain.D, w http.ResponseWriter, r *http.Request) bool {
if domain == nil || !domain.HasProject(r) {
-
// Only if auth is supported
- if a.Auth.IsAuthSupported() {
-
+ if a.IsAuthSupported() {
// To avoid user knowing if pages exist, we will force user to login and authorize pages
if a.Auth.CheckAuthenticationWithoutProject(w, r) {
return true
@@ -140,6 +145,14 @@ func (a *theApp) tryAuxiliaryHandlers(w http.ResponseWriter, r *http.Request, ht
return true
}
+ if a.IsAuthSupported() && a.BuildService != nil {
+ if token, err := a.Auth.GetSessionAccessToken(r); err == nil && token != "" {
+ if a.BuildService.TryMakeRequest(host, token, w, r) {
+ return true
+ }
+ }
+ }
+
if !a.isReady() {
httperrors.Serve503(w)
return true
@@ -160,12 +173,10 @@ func (a *theApp) tryAuxiliaryHandlers(w http.ResponseWriter, r *http.Request, ht
func (a *theApp) serveContent(ww http.ResponseWriter, r *http.Request, https bool) {
w := newLoggingResponseWriter(ww)
defer w.Log(r)
-
metrics.SessionsActive.Inc()
defer metrics.SessionsActive.Dec()
host, domain := a.getHostAndDomain(r)
-
if a.Auth.TryAuthenticate(&w, r, a.dm, &a.lock) {
return
}
@@ -200,7 +211,6 @@ func (a *theApp) serveFileOrNotFound(domain *domain.D) http.HandlerFunc {
// 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(w, r) {
return
}
@@ -358,6 +368,8 @@ func runApp(config appConfig) {
if config.ClientID != "" {
a.Auth = auth.New(config.Domain, config.StoreSecret, config.ClientID, config.ClientSecret,
config.RedirectURI, config.GitLabServer)
+
+ a.BuildService = buildservice.New(config.GitLabServer, config.ArtifactsServerTimeout, config.Domain)
}
configureLogging(config.LogFormat, config.LogVerbose)
diff --git a/client.html b/client.html
new file mode 100644
index 00000000..47d77de1
--- /dev/null
+++ b/client.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title></title>
+</head>
+<body>
+ <select id="method" value="get">
+ <option value="get">GET</option>
+ <option value="post">POST</option>
+ <option value="delete">DELETE</option>
+ <option value="put">PUT</option>
+ </select>
+
+ <input id="url" type="text" value="http://root.proxy.5029.service_name.service_port.127.0.0.1.xip.io:3010" style="width:100%"></input>
+ <button id="perform">PERFORM</button>
+
+<script src="https://ajax.aspnetcdn.com/ajax/jQuery/jquery-3.4.0.min.js"></script>
+<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js"></script>
+<script type="text/javascript">
+
+$("#perform").on("click", function(){
+ axios({
+ url: $("#url").val(),
+ method: $("#method").val(),
+ headers: {
+ // 'Access-Control-Allow-Origin': null,
+ // 'Access-Control-Allow-Origin': 'http://root.proxy.5029.service_name.service_port.127.0.0.1.xip.io:3010',
+ }
+ })
+ .then(function (response) {
+ console.log("ENTRO")
+ // handle success
+ console.log(response);
+ })
+ .catch(function (error) {
+ console.log("lalal")
+ // handle error
+ console.log(error);
+ })
+})
+
+
+</script>
+</body>
+</html>
diff --git a/internal/auth/auth.go b/internal/auth/auth.go
index 02879568..9f28d879 100644
--- a/internal/auth/auth.go
+++ b/internal/auth/auth.go
@@ -91,11 +91,6 @@ func (a *Auth) checkSession(w http.ResponseWriter, r *http.Request) (*sessions.S
// TryAuthenticate tries to authenticate user and fetch access token if request is a callback to auth
func (a *Auth) TryAuthenticate(w http.ResponseWriter, r *http.Request, dm domain.Map, lock *sync.RWMutex) bool {
-
- if a == nil {
- return false
- }
-
session, err := a.checkSession(w, r)
if err != nil {
return true
@@ -105,7 +100,6 @@ func (a *Auth) TryAuthenticate(w http.ResponseWriter, r *http.Request, dm domain
if r.URL.Path != callbackPath {
return false
}
-
logRequest(r).Info("Receive OAuth authentication callback")
if a.handleProxyingAuth(session, w, r, dm, lock) {
@@ -125,12 +119,10 @@ func (a *Auth) TryAuthenticate(w http.ResponseWriter, r *http.Request, dm domain
a.checkAuthenticationResponse(session, w, r)
return true
}
-
return false
}
func (a *Auth) checkAuthenticationResponse(session *sessions.Session, w http.ResponseWriter, r *http.Request) {
-
if !validateState(r, session) {
// State is NOT ok
logRequest(r).Warn("Authentication state did not match expected")
@@ -380,22 +372,8 @@ func destroySession(session *sessions.Session, w http.ResponseWriter, r *http.Re
http.Redirect(w, r, getRequestAddress(r), 302)
}
-// IsAuthSupported checks if pages is running with the authentication support
-func (a *Auth) IsAuthSupported() bool {
- if a == nil {
- return false
- }
- return true
-}
-
// CheckAuthenticationWithoutProject checks if user is authenticated and has a valid token
func (a *Auth) CheckAuthenticationWithoutProject(w http.ResponseWriter, r *http.Request) bool {
-
- if a == nil {
- // No auth supported
- return false
- }
-
session, err := a.checkSession(w, r)
if err != nil {
return true
@@ -408,7 +386,6 @@ func (a *Auth) CheckAuthenticationWithoutProject(w http.ResponseWriter, r *http.
// Access token exists, authorize request
url := fmt.Sprintf(apiURLUserTemplate, a.gitLabServer)
req, err := http.NewRequest("GET", url, nil)
-
if err != nil {
logRequest(r).WithError(err).Error("Failed to authenticate request")
@@ -418,7 +395,6 @@ func (a *Auth) CheckAuthenticationWithoutProject(w http.ResponseWriter, r *http.
req.Header.Add("Authorization", "Bearer "+session.Values["access_token"].(string))
resp, err := a.apiClient.Do(req)
-
if checkResponseForInvalidToken(resp, err) {
logRequest(r).Warn("Access token was invalid, destroying session")
@@ -535,3 +511,21 @@ func New(pagesDomain string, storeSecret string, clientID string, clientSecret s
store: sessions.NewCookieStore([]byte(storeSecret)),
}
}
+
+func (a *Auth) GetSessionAccessToken(r *http.Request) (string, error) {
+ session, err := a.getSessionFromStore(r)
+ if err != nil {
+ return "", err
+ }
+
+ if session == nil {
+ return "", fmt.Errorf("session not present")
+ }
+
+ value := session.Values["access_token"]
+ if value == nil {
+ return "", fmt.Errorf("access_token is not present in the session")
+ }
+
+ return value.(string), nil
+}
diff --git a/internal/buildservice/buildservice.go b/internal/buildservice/buildservice.go
new file mode 100644
index 00000000..a6c82766
--- /dev/null
+++ b/internal/buildservice/buildservice.go
@@ -0,0 +1,120 @@
+package buildservice
+
+import (
+ "fmt"
+ "log"
+ "net/http"
+ "net/http/httputil"
+ "net/url"
+ "regexp"
+ "strings"
+ "time"
+
+ "gitlab.com/gitlab-org/gitlab-pages/internal/httptransport"
+)
+
+const (
+ proxyURLTemplate = "%s/api/v4/jobs/%s/proxy/%s/%s"
+ domainKeyword = "proxy"
+)
+
+// The domain structure is:
+// username.domainKeywork.build_id.service_name.service_port.pages.domain
+// Ex: root.proxy.1.service_name.service_port.xip.io:3010
+// Service_port can be either a number or a string
+var (
+ // Captures subgroup + project, job ID and artifacts path
+ domainExtractor = regexp.MustCompile(`(?i)\A(.+)\.` + domainKeyword + `\.(\d+)\.(.+)\.(.+)\z`)
+)
+
+// Artifact proxies requests for artifact files to the GitLab artifacts API
+type BuildService struct {
+ server string
+ suffix string
+ client *http.Client
+}
+
+// New when provided the arguments defined herein, returns a pointer to an
+// BuildService that is used to proxy requests.
+func New(server string, timeoutSeconds int, pagesDomain string) *BuildService {
+ return &BuildService{
+ server: strings.TrimRight(server, "/"),
+ suffix: "." + strings.ToLower(pagesDomain),
+ client: &http.Client{
+ Timeout: time.Second * time.Duration(timeoutSeconds),
+ Transport: httptransport.Transport,
+ },
+ }
+}
+
+// TryMakeRequest will attempt to proxy a request and write it to the argument
+// http.ResponseWriter, ultimately returning a bool that indicates if the
+// http.ResponseWriter has been written to in any capacity.
+func (a *BuildService) TryMakeRequest(host string, token string, w http.ResponseWriter, r *http.Request) bool {
+ if a.server == "" || host == "" || token == "" {
+ return false
+ }
+
+ rewrittenReq, ok := a.rewriteRequest(host, r)
+ if !ok {
+ return false
+ }
+
+ a.makeRequest(w, rewrittenReq, token)
+ return true
+}
+
+func (a *BuildService) makeRequest(w http.ResponseWriter, req *http.Request, token string) {
+ u, _ := url.Parse(a.server)
+ proxy := httputil.NewSingleHostReverseProxy(u)
+
+ req.Header.Add("Authorization", "Bearer "+token)
+ proxy.ServeHTTP(w, req)
+}
+
+// BuildURL returns a pointer to a url.URL for where the request should be
+// proxied to. The returned bool will indicate if there is some sort of issue
+// with the url while it is being generated.
+//
+// The URL is generated from the host (which contains the top-level group and
+// ends with the pagesDomain) and the path (which contains any subgroups, the
+// project, a job ID
+func (a *BuildService) rewriteRequest(host string, r *http.Request) (*http.Request, bool) {
+ if !strings.HasSuffix(strings.ToLower(host), a.suffix) {
+ return nil, false
+ }
+
+ topDomain := host[0 : len(host)-len(a.suffix)]
+ domainParts := domainExtractor.FindAllStringSubmatch(topDomain, 1)
+ if len(domainParts) != 1 || len(domainParts[0]) != 5 {
+ return nil, false
+ }
+
+ jobID := domainParts[0][2]
+ serviceName := domainParts[0][3]
+ servicePort := domainParts[0][4]
+ generated := fmt.Sprintf(proxyURLTemplate, a.server, jobID, serviceName, servicePort)
+
+ u, err := url.Parse(generated)
+ if err != nil {
+ return nil, false
+ }
+
+ log.Printf("r.URL.RawQuery: %#+v\n", r.URL.RawQuery)
+ q := url.Values{"path": []string{r.URL.Path}}
+ q["wrapped_raw_query_"+jobID] = []string{r.URL.RawQuery}
+ u.RawQuery = q.Encode()
+
+ // Rewritten request
+ r.URL = u
+ log.Printf("r.URL.Pathj: %#+v\n", r.URL.Path)
+ log.Printf("r.URL.Query(): %#+v\n", r.URL.Query())
+
+ // if err := r.ParseForm(); err != nil {
+ // return nil, false
+ // }
+
+ // r.Form.Set("HTTP_JOB_TOKEN", "2z3OV406fjsxgAF3xUjS0JA/+u9sVpr0vKhU0FuLRwquFJx5")
+
+ return r, true
+}