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:
authorVladimir Shushlin <vshushlin@gitlab.com>2019-06-03 14:22:03 +0300
committerNick Thomas <nick@gitlab.com>2019-06-03 14:22:03 +0300
commit9df35356572e09dc2c0907113bf64479204e46a9 (patch)
tree70297534cace3ad4c6015df32757690cc9244992
parent80fa0bb4e200a6b3b9194766dd209de28d1cf08a (diff)
Redirect unknown ACME challenges to the GitLab instance
-rw-r--r--acceptance_test.go68
-rw-r--r--app.go18
-rw-r--r--config_test.go54
-rw-r--r--internal/acme/acme.go62
-rw-r--r--internal/acme/acme_test.go54
-rw-r--r--internal/domain/domain.go34
-rw-r--r--internal/domain/domain_test.go97
-rw-r--r--internal/domain/map_test.go2
-rw-r--r--internal/host/host.go23
-rw-r--r--internal/host/host_test.go18
-rw-r--r--internal/testhelpers/testhelpers.go44
-rw-r--r--main.go47
-rw-r--r--shared/pages/group.acme/with.acme.challenge/config.json6
-rw-r--r--shared/pages/group.acme/with.acme.challenge/public/.well-known/acme-challenge/existingtoken1
-rw-r--r--shared/pages/group.acme/with.acme.challenge/public/index.html1
15 files changed, 467 insertions, 62 deletions
diff --git a/acceptance_test.go b/acceptance_test.go
index b22d5e97..eaa32318 100644
--- a/acceptance_test.go
+++ b/acceptance_test.go
@@ -381,7 +381,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 14")
+ assert.Contains(t, string(body), "gitlab_pages_domains_served_total 16")
}
}
@@ -800,6 +800,72 @@ func makeGitLabPagesAccessStub(t *testing.T) *httptest.Server {
}))
}
+var existingAcmeTokenPath = "/.well-known/acme-challenge/existingtoken"
+var notexistingAcmeTokenPath = "/.well-known/acme-challenge/notexistingtoken"
+
+func TestAcmeChallengesWhenItIsConfigured(t *testing.T) {
+ skipUnlessEnabled(t)
+
+ teardown := RunPagesProcess(t, *pagesBinary, listeners, "", "-gitlab-server=https://gitlab-acme.com")
+ defer teardown()
+
+ t.Run("When domain folder contains requested acme challenge it responds with it", func(t *testing.T) {
+ rsp, err := GetRedirectPage(t, httpListener, "withacmechallenge.domain.com",
+ existingAcmeTokenPath)
+
+ defer rsp.Body.Close()
+ require.NoError(t, err)
+ require.Equal(t, http.StatusOK, rsp.StatusCode)
+ body, _ := ioutil.ReadAll(rsp.Body)
+ require.Equal(t, "this is token\n", string(body))
+ })
+
+ t.Run("When domain folder doesn't contains requested acme challenge it redirects to GitLab",
+ func(t *testing.T) {
+ rsp, err := GetRedirectPage(t, httpListener, "withacmechallenge.domain.com",
+ notexistingAcmeTokenPath)
+
+ defer rsp.Body.Close()
+ require.NoError(t, err)
+ require.Equal(t, http.StatusTemporaryRedirect, rsp.StatusCode)
+
+ url, err := url.Parse(rsp.Header.Get("Location"))
+ require.NoError(t, err)
+
+ require.Equal(t, url.String(), "https://gitlab-acme.com/-/acme-challenge?domain=withacmechallenge.domain.com&token=notexistingtoken")
+ },
+ )
+}
+
+func TestAcmeChallengesWhenItIsNotConfigured(t *testing.T) {
+ skipUnlessEnabled(t)
+
+ teardown := RunPagesProcess(t, *pagesBinary, listeners, "", "")
+ defer teardown()
+
+ t.Run("When domain folder contains requested acme challenge it responds with it", func(t *testing.T) {
+ rsp, err := GetRedirectPage(t, httpListener, "withacmechallenge.domain.com",
+ existingAcmeTokenPath)
+
+ defer rsp.Body.Close()
+ require.NoError(t, err)
+ require.Equal(t, http.StatusOK, rsp.StatusCode)
+ body, _ := ioutil.ReadAll(rsp.Body)
+ require.Equal(t, "this is token\n", string(body))
+ })
+
+ t.Run("When domain folder doesn't contains requested acme challenge it returns 404",
+ func(t *testing.T) {
+ rsp, err := GetRedirectPage(t, httpListener, "withacmechallenge.domain.com",
+ notexistingAcmeTokenPath)
+
+ defer rsp.Body.Close()
+ require.NoError(t, err)
+ require.Equal(t, http.StatusNotFound, rsp.StatusCode)
+ },
+ )
+}
+
func TestAccessControlUnderCustomDomain(t *testing.T) {
skipUnlessEnabled(t, "not-inplace-chroot")
diff --git a/app.go b/app.go
index 08eff7e7..9d4f5bdb 100644
--- a/app.go
+++ b/app.go
@@ -17,6 +17,7 @@ import (
log "github.com/sirupsen/logrus"
mimedb "gitlab.com/lupine/go-mimedb"
+ "gitlab.com/gitlab-org/gitlab-pages/internal/acme"
"gitlab.com/gitlab-org/gitlab-pages/internal/admin"
"gitlab.com/gitlab-org/gitlab-pages/internal/artifact"
"gitlab.com/gitlab-org/gitlab-pages/internal/auth"
@@ -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
+ Auth *auth.Auth
+ AcmeMiddleware *acme.Middleware
}
func (a *theApp) isReady() bool {
@@ -166,6 +168,10 @@ func (a *theApp) serveContent(ww http.ResponseWriter, r *http.Request, https boo
host, domain := a.getHostAndDomain(r)
+ if a.AcmeMiddleware.ServeAcmeChallenges(&w, r, domain) {
+ return
+ }
+
if a.Auth.TryAuthenticate(&w, r, a.dm, &a.lock) {
return
}
@@ -360,6 +366,10 @@ func runApp(config appConfig) {
config.RedirectURI, config.GitLabServer)
}
+ if config.GitLabServer != "" {
+ a.AcmeMiddleware = &acme.Middleware{GitlabURL: config.GitLabServer}
+ }
+
configureLogging(config.LogFormat, config.LogVerbose)
if err := mimedb.LoadTypes(); err != nil {
diff --git a/config_test.go b/config_test.go
new file mode 100644
index 00000000..3ec51c56
--- /dev/null
+++ b/config_test.go
@@ -0,0 +1,54 @@
+package main
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestGitLabServerFromFlags(t *testing.T) {
+ tests := []struct {
+ name string
+ gitLabServer string
+ gitLabAuthServer string
+ artifactsServer string
+ expected string
+ }{
+ {
+ name: "When gitLabServer is set",
+ gitLabServer: "gitlabserver.com",
+ gitLabAuthServer: "authserver.com",
+ artifactsServer: "https://artifactsserver.com",
+ expected: "gitlabserver.com",
+ },
+ {
+ name: "When auth server is set",
+ gitLabServer: "",
+ gitLabAuthServer: "authserver.com",
+ artifactsServer: "https://artifactsserver.com",
+ expected: "authserver.com",
+ },
+ {
+ name: "When only artifacts server is set",
+ gitLabServer: "",
+ gitLabAuthServer: "",
+ artifactsServer: "https://artifactsserver.com",
+ expected: "artifactsserver.com",
+ },
+ {
+ name: "When only artifacts server includes path",
+ gitLabServer: "",
+ gitLabAuthServer: "",
+ artifactsServer: "https://artifactsserver.com:8080/api/path",
+ expected: "artifactsserver.com",
+ }}
+
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ gitLabServer = &test.gitLabServer
+ gitLabAuthServer = &test.gitLabAuthServer
+ artifactsServer = &test.artifactsServer
+ assert.Equal(t, test.expected, gitlabServerFromFlags())
+ })
+ }
+}
diff --git a/internal/acme/acme.go b/internal/acme/acme.go
new file mode 100644
index 00000000..89881f34
--- /dev/null
+++ b/internal/acme/acme.go
@@ -0,0 +1,62 @@
+package acme
+
+import (
+ "net/http"
+ "net/url"
+ "path/filepath"
+ "strings"
+
+ log "github.com/sirupsen/logrus"
+
+ "gitlab.com/gitlab-org/gitlab-pages/internal/host"
+)
+
+// Middleware handles acme challenges by redirecting them to GitLab instance
+type Middleware struct {
+ GitlabURL string
+}
+
+// Domain interface represent D from domain package
+type Domain interface {
+ HasAcmeChallenge(string) bool
+}
+
+// ServeAcmeChallenges identifies if request is acme-challenge and redirects to GitLab in that case
+func (m *Middleware) ServeAcmeChallenges(w http.ResponseWriter, r *http.Request, domain Domain) bool {
+ if m == nil {
+ return false
+ }
+
+ if !isAcmeChallenge(r.URL.Path) {
+ return false
+ }
+
+ if domain.HasAcmeChallenge(filepath.Base(r.URL.Path)) {
+ return false
+ }
+
+ return m.redirectToGitlab(w, r)
+}
+
+func isAcmeChallenge(path string) bool {
+ return strings.HasPrefix(filepath.Clean(path), "/.well-known/acme-challenge/")
+}
+
+func (m *Middleware) redirectToGitlab(w http.ResponseWriter, r *http.Request) bool {
+ redirectURL, err := url.Parse(m.GitlabURL)
+ if err != nil {
+ log.WithError(err).Error("Can't parse GitLab URL for acme challenge redirect")
+ return false
+ }
+
+ redirectURL.Path = "/-/acme-challenge"
+ query := redirectURL.Query()
+ query.Set("domain", host.FromRequest(r))
+ query.Set("token", filepath.Base(r.URL.Path))
+ redirectURL.RawQuery = query.Encode()
+
+ log.WithField("redirect_url", redirectURL).Debug("Redirecting to GitLab for processing acme challenge")
+
+ http.Redirect(w, r, redirectURL.String(), http.StatusTemporaryRedirect)
+ return true
+}
diff --git a/internal/acme/acme_test.go b/internal/acme/acme_test.go
new file mode 100644
index 00000000..c0daefeb
--- /dev/null
+++ b/internal/acme/acme_test.go
@@ -0,0 +1,54 @@
+package acme
+
+import (
+ "net/http"
+ "testing"
+
+ "gitlab.com/gitlab-org/gitlab-pages/internal/testhelpers"
+)
+
+type domainStub struct {
+ hasAcmeChallenge bool
+}
+
+func (d *domainStub) HasAcmeChallenge(_ string) bool {
+ return d.hasAcmeChallenge
+}
+
+func serveAcmeOrNotFound(m *Middleware, domain Domain) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+ if !m.ServeAcmeChallenges(w, r, domain) {
+ http.NotFound(w, r)
+ }
+ }
+}
+
+const (
+ baseURL = "http://example.com"
+ indexURL = baseURL + "/index.html"
+ challengeURL = baseURL + "/.well-known/acme-challenge/token"
+)
+
+var (
+ domainWithChallenge = &domainStub{hasAcmeChallenge: true}
+ domain = &domainStub{hasAcmeChallenge: false}
+ middleware = &Middleware{GitlabURL: "https://gitlab.example.com"}
+)
+
+func TestServeAcmeChallengesNotConfigured(t *testing.T) {
+ testhelpers.AssertHTTP404(t, serveAcmeOrNotFound(nil, domain), "GET", challengeURL, nil, nil)
+}
+
+func TestServeAcmeChallengeWhenPresent(t *testing.T) {
+ testhelpers.AssertHTTP404(t, serveAcmeOrNotFound(middleware, domainWithChallenge), "GET", challengeURL, nil, nil)
+}
+
+func TestServeAcmeChallengeWhenMissing(t *testing.T) {
+ testhelpers.AssertRedirectTo(
+ t, serveAcmeOrNotFound(middleware, domain),
+ "GET", challengeURL, nil,
+ "https://gitlab.example.com/-/acme-challenge?domain=example.com&token=token",
+ )
+
+ testhelpers.AssertHTTP404(t, serveAcmeOrNotFound(middleware, domain), "GET", indexURL, nil, nil)
+}
diff --git a/internal/domain/domain.go b/internal/domain/domain.go
index 1455d436..4ce8a561 100644
--- a/internal/domain/domain.go
+++ b/internal/domain/domain.go
@@ -6,7 +6,6 @@ import (
"fmt"
"io"
"mime"
- "net"
"net/http"
"os"
"path/filepath"
@@ -17,6 +16,7 @@ import (
"golang.org/x/sys/unix"
+ "gitlab.com/gitlab-org/gitlab-pages/internal/host"
"gitlab.com/gitlab-org/gitlab-pages/internal/httperrors"
"gitlab.com/gitlab-org/gitlab-pages/internal/httputil"
)
@@ -106,16 +106,6 @@ func handleGZip(w http.ResponseWriter, r *http.Request, fullPath string) string
return gzipPath
}
-func getHost(r *http.Request) string {
- host := strings.ToLower(r.Host)
-
- if splitHost, _, err := net.SplitHostPort(host); err == nil {
- host = splitHost
- }
-
- return host
-}
-
// Look up a project inside the domain based on the host and path. Returns the
// project and its name (if applicable)
func (d *D) getProjectWithSubpath(r *http.Request) (*project, string, string) {
@@ -131,7 +121,7 @@ func (d *D) getProjectWithSubpath(r *http.Request) (*project, string, string) {
// Since the URL doesn't specify a project (e.g. http://mydomain.gitlab.io),
// return the group project if it exists.
- if host := getHost(r); host != "" {
+ if host := host.FromRequest(r); host != "" {
if groupProject := d.projects[host]; groupProject != nil {
return groupProject, host, strings.Join(split[1:], "/")
}
@@ -179,6 +169,26 @@ func (d *D) IsAccessControlEnabled(r *http.Request) bool {
return false
}
+// HasAcmeChallenge checks domain directory contains particular acme challenge
+func (d *D) HasAcmeChallenge(token string) bool {
+ if d == nil {
+ return false
+ }
+
+ if d.config == nil {
+ return false
+ }
+
+ _, err := d.resolvePath(d.projectName, ".well-known/acme-challenge", token)
+
+ // there is an acme challenge on disk
+ if err == nil {
+ return true
+ }
+
+ return false
+}
+
// IsNamespaceProject figures out if the request is to a namespace project
func (d *D) IsNamespaceProject(r *http.Request) bool {
if d == nil {
diff --git a/internal/domain/domain_test.go b/internal/domain/domain_test.go
index add9b616..a05dbe7e 100644
--- a/internal/domain/domain_test.go
+++ b/internal/domain/domain_test.go
@@ -3,7 +3,6 @@ package domain
import (
"compress/gzip"
"io/ioutil"
- "mime"
"net/http"
"net/http/httptest"
"net/url"
@@ -15,6 +14,7 @@ import (
"github.com/stretchr/testify/require"
"gitlab.com/gitlab-org/gitlab-pages/internal/fixture"
+ "gitlab.com/gitlab-org/gitlab-pages/internal/testhelpers"
)
func serveFileOrNotFound(domain *D) http.HandlerFunc {
@@ -25,11 +25,6 @@ func serveFileOrNotFound(domain *D) http.HandlerFunc {
}
}
-func assertRedirectTo(t *testing.T, h http.HandlerFunc, method string, url string, values url.Values, expectedURL string) {
- assert.HTTPRedirect(t, h, method, url, values)
- assert.HTTPBodyContains(t, h, method, url, values, `<a href="//`+expectedURL+`">Found</a>`)
-}
-
func testGroupServeHTTPHost(t *testing.T, host string) {
testGroup := &D{
projectName: "",
@@ -53,12 +48,12 @@ func testGroupServeHTTPHost(t *testing.T, host string) {
assert.HTTPBodyContains(t, serve, "GET", makeURL("/"), nil, "main-dir")
assert.HTTPBodyContains(t, serve, "GET", makeURL("/index"), nil, "main-dir")
assert.HTTPBodyContains(t, serve, "GET", makeURL("/index.html"), nil, "main-dir")
- assertRedirectTo(t, serve, "GET", makeURL("/project"), nil, host+"/project/")
+ testhelpers.AssertRedirectTo(t, serve, "GET", makeURL("/project"), nil, "//"+host+"/project/")
assert.HTTPBodyContains(t, serve, "GET", makeURL("/project/"), nil, "project-subdir")
assert.HTTPBodyContains(t, serve, "GET", makeURL("/project/index"), nil, "project-subdir")
assert.HTTPBodyContains(t, serve, "GET", makeURL("/project/index/"), nil, "project-subdir")
assert.HTTPBodyContains(t, serve, "GET", makeURL("/project/index.html"), nil, "project-subdir")
- assertRedirectTo(t, serve, "GET", makeURL("/project/subdir"), nil, host+"/project/subdir/")
+ testhelpers.AssertRedirectTo(t, serve, "GET", makeURL("/project/subdir"), nil, "//"+host+"/project/subdir/")
assert.HTTPBodyContains(t, serve, "GET", makeURL("/project/subdir/"), nil, "project-subsubdir")
assert.HTTPBodyContains(t, serve, "GET", makeURL("/project2/"), nil, "project2-main")
assert.HTTPBodyContains(t, serve, "GET", makeURL("/project2/index"), nil, "project2-main")
@@ -205,7 +200,61 @@ func TestIsHTTPSOnly(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
req, _ := http.NewRequest(http.MethodGet, test.url, nil)
- assert.Equal(t, test.domain.IsHTTPSOnly(req), test.expected)
+ assert.Equal(t, test.expected, test.domain.IsHTTPSOnly(req))
+ })
+ }
+}
+
+func TestHasAcmeChallenge(t *testing.T) {
+ cleanup := setUpTests(t)
+ defer cleanup()
+
+ tests := []struct {
+ name string
+ domain *D
+ token string
+ expected bool
+ }{
+ {
+ name: "Project containing acme challenge",
+ domain: &D{
+ group: group{name: "group.acme"},
+ projectName: "with.acme.challenge",
+ config: &domainConfig{HTTPSOnly: true},
+ },
+ token: "existingtoken",
+ expected: true,
+ },
+ {
+ name: "Project containing another token",
+ domain: &D{
+ group: group{name: "group.acme"},
+ projectName: "with.acme.challenge",
+ config: &domainConfig{HTTPSOnly: true},
+ },
+ token: "notexistingtoken",
+ expected: false,
+ },
+ {
+ name: "nil domain",
+ domain: nil,
+ token: "existingtoken",
+ expected: false,
+ },
+ {
+ name: "Domain without config",
+ domain: &D{
+ group: group{name: "group.acme"},
+ projectName: "with.acme.challenge",
+ config: nil,
+ },
+ token: "existingtoken",
+ expected: false,
+ },
+ }
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ assert.Equal(t, test.expected, test.domain.HasAcmeChallenge(test.token))
})
}
}
@@ -304,18 +353,6 @@ func TestGroupServeHTTPGzip(t *testing.T) {
}
}
-func testHTTP404(t *testing.T, handler http.HandlerFunc, mode, url string, values url.Values, str interface{}) {
- w := httptest.NewRecorder()
- req, err := http.NewRequest(mode, url+"?"+values.Encode(), nil)
- require.NoError(t, err)
- handler(w, req)
-
- contentType, _, _ := mime.ParseMediaType(w.Header().Get("Content-Type"))
- assert.Equal(t, http.StatusNotFound, w.Code, "HTTP status")
- assert.Equal(t, "text/html", contentType, "Content-Type")
- assert.Contains(t, w.Body.String(), str)
-}
-
func TestGroup404ServeHTTP(t *testing.T) {
cleanup := setUpTests(t)
defer cleanup()
@@ -334,15 +371,15 @@ func TestGroup404ServeHTTP(t *testing.T) {
},
}
- 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/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")
+ testhelpers.AssertHTTP404(t, serveFileOrNotFound(testGroup), "GET", "http://group.404.test.io/project.404/not/existing-file", nil, "Custom 404 project page")
+ testhelpers.AssertHTTP404(t, serveFileOrNotFound(testGroup), "GET", "http://group.404.test.io/project.404/", nil, "Custom 404 project page")
+ testhelpers.AssertHTTP404(t, serveFileOrNotFound(testGroup), "GET", "http://group.404.test.io/not/existing-file", nil, "Custom 404 group page")
+ testhelpers.AssertHTTP404(t, serveFileOrNotFound(testGroup), "GET", "http://group.404.test.io/not-existing-file", nil, "Custom 404 group page")
+ testhelpers.AssertHTTP404(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")
// Ensure the namespace project's custom 404.html is not used by projects
- testHTTP404(t, serveFileOrNotFound(testGroup), "GET", "http://group.404.test.io/project.no.404/not/existing-file", nil, "The page you're looking for could not be found.")
+ testhelpers.AssertHTTP404(t, serveFileOrNotFound(testGroup), "GET", "http://group.404.test.io/project.no.404/not/existing-file", nil, "The page you're looking for could not be found.")
}
func TestDomain404ServeHTTP(t *testing.T) {
@@ -357,8 +394,8 @@ func TestDomain404ServeHTTP(t *testing.T) {
},
}
- 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")
+ testhelpers.AssertHTTP404(t, serveFileOrNotFound(testDomain), "GET", "http://group.404.test.io/not-existing-file", nil, "Custom 404 group page")
+ testhelpers.AssertHTTP404(t, serveFileOrNotFound(testDomain), "GET", "http://group.404.test.io/", nil, "Custom 404 group page")
}
func TestPredefined404ServeHTTP(t *testing.T) {
@@ -369,7 +406,7 @@ func TestPredefined404ServeHTTP(t *testing.T) {
group: group{name: "group"},
}
- testHTTP404(t, serveFileOrNotFound(testDomain), "GET", "http://group.test.io/not-existing-file", nil, "The page you're looking for could not be found")
+ testhelpers.AssertHTTP404(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) {
diff --git a/internal/domain/map_test.go b/internal/domain/map_test.go
index dc5e8648..156b039f 100644
--- a/internal/domain/map_test.go
+++ b/internal/domain/map_test.go
@@ -55,6 +55,8 @@ func TestReadProjects(t *testing.T) {
"no.cert.com",
"private.domain.com",
"group.auth.test.io",
+ "group.acme.test.io",
+ "withacmechallenge.domain.com",
"capitalgroup.test.io",
}
diff --git a/internal/host/host.go b/internal/host/host.go
new file mode 100644
index 00000000..dd9b1c8a
--- /dev/null
+++ b/internal/host/host.go
@@ -0,0 +1,23 @@
+package host
+
+import (
+ "net"
+ "net/http"
+ "strings"
+)
+
+// FromString tries to split host port from string, returns host or initial string if fail
+func FromString(s string) string {
+ host := strings.ToLower(s)
+
+ if splitHost, _, err := net.SplitHostPort(host); err == nil {
+ host = splitHost
+ }
+
+ return host
+}
+
+// FromRequest tries to split host port from r.Host, returns host or initial string if fail
+func FromRequest(r *http.Request) string {
+ return FromString(r.Host)
+}
diff --git a/internal/host/host_test.go b/internal/host/host_test.go
new file mode 100644
index 00000000..8395d3fc
--- /dev/null
+++ b/internal/host/host_test.go
@@ -0,0 +1,18 @@
+package host
+
+import (
+ "net/http/httptest"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestFromString(t *testing.T) {
+ assert.Equal(t, "example.com", FromString("example.com"))
+ assert.Equal(t, "example.com", FromString("eXAmpLe.com"))
+ assert.Equal(t, "example.com", FromString("example.com:8080"))
+}
+
+func TestFromRequest(t *testing.T) {
+ assert.Equal(t, "example.com", FromRequest(httptest.NewRequest("GET", "example.com:8080/123", nil)))
+}
diff --git a/internal/testhelpers/testhelpers.go b/internal/testhelpers/testhelpers.go
new file mode 100644
index 00000000..1c3a1eba
--- /dev/null
+++ b/internal/testhelpers/testhelpers.go
@@ -0,0 +1,44 @@
+package testhelpers
+
+import (
+ "mime"
+ "net/http"
+ "net/http/httptest"
+ "net/url"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+// AssertHTTP404 asserts handler returns 404 with provided str body
+func AssertHTTP404(t *testing.T, handler http.HandlerFunc, mode, url string, values url.Values, str interface{}) {
+ w := httptest.NewRecorder()
+ req, err := http.NewRequest(mode, url+"?"+values.Encode(), nil)
+ require.NoError(t, err)
+ handler(w, req)
+
+ assert.Equal(t, http.StatusNotFound, w.Code, "HTTP status")
+
+ if str != nil {
+ contentType, _, _ := mime.ParseMediaType(w.Header().Get("Content-Type"))
+ assert.Equal(t, "text/html", contentType, "Content-Type")
+ assert.Contains(t, w.Body.String(), str)
+ }
+}
+
+// AssertRedirectTo asserts that handler redirects to particular URL
+func AssertRedirectTo(t *testing.T, handler http.HandlerFunc, method string,
+ url string, values url.Values, expectedURL string) {
+
+ assert.HTTPRedirect(t, handler, method, url, values)
+
+ recorder := httptest.NewRecorder()
+
+ req, _ := http.NewRequest(method, url, nil)
+ req.URL.RawQuery = values.Encode()
+
+ handler(recorder, req)
+
+ assert.Equal(t, expectedURL, recorder.Header().Get("Location"))
+}
diff --git a/main.go b/main.go
index 185fd4b0..d043d968 100644
--- a/main.go
+++ b/main.go
@@ -11,6 +11,7 @@ import (
"github.com/namsral/flag"
log "github.com/sirupsen/logrus"
+ "gitlab.com/gitlab-org/gitlab-pages/internal/host"
"gitlab.com/gitlab-org/gitlab-pages/internal/tlsconfig"
)
@@ -48,7 +49,8 @@ var (
adminHTTPSCert = flag.String("admin-https-cert", "", "The path to the certificate file for the admin API (optional)")
adminHTTPSKey = flag.String("admin-https-key", "", "The path to the key file for the admin API (optional)")
secret = flag.String("auth-secret", "", "Cookie store hash key, should be at least 32 bytes long.")
- gitLabServer = flag.String("auth-server", "", "GitLab server, for example https://www.gitlab.com")
+ gitLabAuthServer = flag.String("auth-server", "", "DEPRECATED, use gitlab-server instead. GitLab server, for example https://www.gitlab.com")
+ gitLabServer = flag.String("gitlab-server", "", "GitLab server, for example https://www.gitlab.com")
clientID = flag.String("auth-client-id", "", "GitLab application Client ID")
clientSecret = flag.String("auth-client-secret", "", "GitLab application Client Secret")
redirectURI = flag.String("auth-redirect-uri", "", "GitLab application redirect URI")
@@ -72,10 +74,24 @@ var (
errSecretNotDefined = errors.New("auth-secret must be defined if authentication is supported")
errClientIDNotDefined = errors.New("auth-client-id must be defined if authentication is supported")
errClientSecretNotDefined = errors.New("auth-client-secret must be defined if authentication is supported")
- errGitLabServerNotDefined = errors.New("auth-server must be defined if authentication is supported")
+ errGitLabServerNotDefined = errors.New("gitlab-server must be defined if authentication is supported")
errRedirectURINotDefined = errors.New("auth-redirect-uri must be defined if authentication is supported")
)
+func gitlabServerFromFlags() string {
+ if *gitLabServer != "" {
+ return *gitLabServer
+ }
+
+ if *gitLabAuthServer != "" {
+ log.Warn("auth-server parameter is deprecated, use gitlab-server instead")
+ return *gitLabAuthServer
+ }
+
+ url, _ := url.Parse(*artifactsServer)
+ return host.FromString(url.Host)
+}
+
func configFromFlags() appConfig {
var config appConfig
@@ -129,39 +145,40 @@ func configFromFlags() appConfig {
config.ArtifactsServer = *artifactsServer
}
- checkAuthenticationConfig(config)
+ config.GitLabServer = gitlabServerFromFlags()
config.StoreSecret = *secret
config.ClientID = *clientID
config.ClientSecret = *clientSecret
- config.GitLabServer = *gitLabServer
config.RedirectURI = *redirectURI
+ checkAuthenticationConfig(config)
+
return config
}
func checkAuthenticationConfig(config appConfig) {
- if *secret != "" || *clientID != "" || *clientSecret != "" ||
- *gitLabServer != "" || *redirectURI != "" {
- // Check all auth params are valid
- assertAuthConfig()
+ if config.StoreSecret == "" && config.ClientID == "" &&
+ config.ClientSecret == "" && config.RedirectURI == "" {
+ return
}
+ assertAuthConfig(config)
}
-func assertAuthConfig() {
- if *secret == "" {
+func assertAuthConfig(config appConfig) {
+ if config.StoreSecret == "" {
log.Fatal(errSecretNotDefined)
}
- if *clientID == "" {
+ if config.ClientID == "" {
log.Fatal(errClientIDNotDefined)
}
- if *clientSecret == "" {
+ if config.ClientSecret == "" {
log.Fatal(errClientSecretNotDefined)
}
- if *gitLabServer == "" {
+ if config.GitLabServer == "" {
log.Fatal(errGitLabServerNotDefined)
}
- if *redirectURI == "" {
+ if config.RedirectURI == "" {
log.Fatal(errRedirectURINotDefined)
}
}
@@ -222,7 +239,7 @@ func appMain() {
"tls-max-version": *tlsMaxVersion,
"use-http-2": config.HTTP2,
"auth-secret": config.StoreSecret,
- "auth-server": config.GitLabServer,
+ "gitlab-server": config.GitLabServer,
"auth-client-id": config.ClientID,
"auth-client-secret": config.ClientSecret,
"auth-redirect-uri": config.RedirectURI,
diff --git a/shared/pages/group.acme/with.acme.challenge/config.json b/shared/pages/group.acme/with.acme.challenge/config.json
new file mode 100644
index 00000000..f50ba7fa
--- /dev/null
+++ b/shared/pages/group.acme/with.acme.challenge/config.json
@@ -0,0 +1,6 @@
+{ "domains": [
+ {
+ "domain": "withacmechallenge.domain.com"
+ }
+ ]
+}
diff --git a/shared/pages/group.acme/with.acme.challenge/public/.well-known/acme-challenge/existingtoken b/shared/pages/group.acme/with.acme.challenge/public/.well-known/acme-challenge/existingtoken
new file mode 100644
index 00000000..84455e1d
--- /dev/null
+++ b/shared/pages/group.acme/with.acme.challenge/public/.well-known/acme-challenge/existingtoken
@@ -0,0 +1 @@
+this is token
diff --git a/shared/pages/group.acme/with.acme.challenge/public/index.html b/shared/pages/group.acme/with.acme.challenge/public/index.html
new file mode 100644
index 00000000..9015a7a3
--- /dev/null
+++ b/shared/pages/group.acme/with.acme.challenge/public/index.html
@@ -0,0 +1 @@
+index