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:
-rw-r--r--CHANGELOG6
-rw-r--r--VERSION2
-rw-r--r--app_config.go17
-rw-r--r--go.mod1
-rw-r--r--go.sum2
-rw-r--r--internal/source/gitlab/client.go143
-rw-r--r--internal/source/gitlab/client_test.go130
-rw-r--r--internal/source/gitlab/response.go21
-rw-r--r--main.go7
9 files changed, 315 insertions, 14 deletions
diff --git a/CHANGELOG b/CHANGELOG
index 1eba177f..fd693b93 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,9 @@
+v 1.12.0
+
+- Add minimal support for the api-secret-key config flag (not yet used)
+- Add warnings about secrets given through command-line flags
+- Remove Admin gRPC api (was never used)
+
v 1.11.0
- Refactor domain package and extract disk serving !189
diff --git a/VERSION b/VERSION
index 1cac385c..0eed1a29 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.11.0
+1.12.0
diff --git a/app_config.go b/app_config.go
index b870626d..f9be6545 100644
--- a/app_config.go
+++ b/app_config.go
@@ -25,12 +25,13 @@ type appConfig struct {
LogFormat string
LogVerbose bool
- StoreSecret string
- GitLabServer string
- ClientID string
- ClientSecret string
- RedirectURI string
- SentryDSN string
- SentryEnvironment string
- CustomHeaders []string
+ StoreSecret string
+ GitLabServer string
+ GitLabAPISecretKey []byte
+ ClientID string
+ ClientSecret string
+ RedirectURI string
+ SentryDSN string
+ SentryEnvironment string
+ CustomHeaders []string
}
diff --git a/go.mod b/go.mod
index 59bd759a..e9a50332 100644
--- a/go.mod
+++ b/go.mod
@@ -4,6 +4,7 @@ go 1.12
require (
github.com/certifi/gocertifi v0.0.0-20190905060710-a5e0173ced67 // indirect
+ github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/fzipp/gocyclo v0.0.0-20150627053110-6acd4345c835
github.com/getsentry/raven-go v0.1.2 // indirect
github.com/golang/mock v1.3.1
diff --git a/go.sum b/go.sum
index d08aed58..a4d32739 100644
--- a/go.sum
+++ b/go.sum
@@ -17,6 +17,8 @@ github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
+github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fzipp/gocyclo v0.0.0-20150627053110-6acd4345c835 h1:roDmqJ4Qes7hrDOsWsMCce0vQHz3xiMPjJ9m4c2eeNs=
github.com/fzipp/gocyclo v0.0.0-20150627053110-6acd4345c835/go.mod h1:BjL/N0+C+j9uNX+1xcNuM9vdSIcXCZrQZUYbXOFbgN8=
diff --git a/internal/source/gitlab/client.go b/internal/source/gitlab/client.go
index b2dab32e..9dde7b43 100644
--- a/internal/source/gitlab/client.go
+++ b/internal/source/gitlab/client.go
@@ -1,9 +1,142 @@
package gitlab
-import "context"
+import (
+ "encoding/json"
+ "errors"
+ "net/http"
+ "net/url"
+ "time"
-// Client is an internal HTTP client used for communication with GitLab
-// instance
-type Client interface {
- Resolve(ctx context.Context, domain string) (*Lookup, int, error)
+ jwt "github.com/dgrijalva/jwt-go"
+
+ "gitlab.com/gitlab-org/gitlab-pages/internal/httptransport"
+)
+
+// Client is a HTTP client to access Pages internal API
+type Client struct {
+ secretKey []byte
+ baseURL *url.URL
+ httpClient *http.Client
+}
+
+var (
+ errUnknown = errors.New("Unknown")
+ errNoContent = errors.New("No Content")
+ errUnauthorized = errors.New("Unauthorized")
+ errNotFound = errors.New("Not Found")
+)
+
+// NewClient initializes and returns new Client
+// baseUrl is appConfig.GitLabServer
+// secretKey is appConfig.GitLabAPISecretKey (not yet implemented)
+func NewClient(baseURL string, secretKey []byte) (*Client, error) {
+ url, err := url.Parse(baseURL)
+ if err != nil {
+ return nil, err
+ }
+
+ return &Client{
+ secretKey: secretKey,
+ baseURL: url,
+ httpClient: &http.Client{
+ Timeout: 5 * time.Second,
+ Transport: httptransport.Transport,
+ },
+ }, nil
+}
+
+// GetVirtualDomain returns VirtualDomain configuration for the given host
+func (gc *Client) GetVirtualDomain(host string) (*VirtualDomain, error) {
+ params := map[string]string{"host": host}
+
+ resp, err := gc.get("/api/v4/internal/pages", params)
+ if resp != nil {
+ defer resp.Body.Close()
+ }
+
+ if err != nil {
+ return nil, err
+ }
+
+ var domain VirtualDomain
+ err = json.NewDecoder(resp.Body).Decode(&domain)
+ if err != nil {
+ return nil, err
+ }
+
+ return &domain, nil
+}
+
+func (gc *Client) get(path string, params map[string]string) (*http.Response, error) {
+ endpoint, err := gc.endpoint(path, params)
+ if err != nil {
+ return nil, err
+ }
+
+ req, err := gc.request("GET", endpoint)
+ if err != nil {
+ return nil, err
+ }
+
+ resp, err := gc.httpClient.Do(req)
+ if err != nil {
+ return nil, err
+ }
+
+ switch {
+ case resp.StatusCode == http.StatusOK:
+ return resp, nil
+ case resp.StatusCode == http.StatusNoContent:
+ return resp, errNoContent
+ case resp.StatusCode == http.StatusUnauthorized:
+ return resp, errUnauthorized
+ case resp.StatusCode == http.StatusNotFound:
+ return resp, errNotFound
+ default:
+ return resp, errUnknown
+ }
+}
+
+func (gc *Client) endpoint(path string, params map[string]string) (*url.URL, error) {
+ endpoint, err := gc.baseURL.Parse(path)
+ if err != nil {
+ return nil, err
+ }
+
+ values := url.Values{}
+ for key, value := range params {
+ values.Add(key, value)
+ }
+ endpoint.RawQuery = values.Encode()
+
+ return endpoint, nil
+}
+
+func (gc *Client) request(method string, endpoint *url.URL) (*http.Request, error) {
+ req, err := http.NewRequest("GET", endpoint.String(), nil)
+ if err != nil {
+ return nil, err
+ }
+
+ token, err := gc.token()
+ if err != nil {
+ return nil, err
+ }
+ req.Header.Set("Gitlab-Pages-Api-Request", token)
+
+ return req, nil
+}
+
+func (gc *Client) token() (string, error) {
+ claims := jwt.StandardClaims{
+ Issuer: "gitlab-pages",
+ ExpiresAt: time.Now().Add(5 * time.Second).Unix(),
+ }
+
+ token, err := jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString(gc.secretKey)
+ if err != nil {
+ return "", err
+ }
+
+ return token, nil
}
diff --git a/internal/source/gitlab/client_test.go b/internal/source/gitlab/client_test.go
new file mode 100644
index 00000000..1d63a590
--- /dev/null
+++ b/internal/source/gitlab/client_test.go
@@ -0,0 +1,130 @@
+package gitlab
+
+import (
+ "encoding/base64"
+ "fmt"
+ "net/http"
+ "net/http/httptest"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+
+ jwt "github.com/dgrijalva/jwt-go"
+)
+
+var (
+ encodedSecret = "e41rcFh7XBA7sNABWVCe2AZvxMsy6QDtJ8S9Ql1UiN8=" // 32 bytes, base64 encoded
+)
+
+func TestNewValidBaseURL(t *testing.T) {
+ _, err := NewClient("https://gitlab.com", secretKey())
+ require.NoError(t, err)
+}
+
+func TestNewInvalidBaseURL(t *testing.T) {
+ client, err := NewClient("%", secretKey())
+ require.Error(t, err)
+ require.Nil(t, client)
+}
+
+func TestGetVirtualDomainForErrorResponses(t *testing.T) {
+ tests := map[int]string{
+ http.StatusNoContent: "No Content",
+ http.StatusUnauthorized: "Unauthorized",
+ http.StatusNotFound: "Not Found",
+ }
+
+ for statusCode, expectedError := range tests {
+ name := fmt.Sprintf("%d %s", statusCode, expectedError)
+ t.Run(name, func(t *testing.T) {
+ mux := http.NewServeMux()
+
+ mux.HandleFunc("/api/v4/internal/pages", func(w http.ResponseWriter, r *http.Request) {
+ w.WriteHeader(statusCode)
+ })
+
+ server := httptest.NewServer(mux)
+ defer server.Close()
+
+ client, _ := NewClient(server.URL, secretKey())
+
+ actual, err := client.GetVirtualDomain("group.gitlab.io")
+
+ require.EqualError(t, err, expectedError)
+ require.Nil(t, actual)
+ })
+ }
+}
+
+func TestGetVirtualDomainAuthenticatedRequest(t *testing.T) {
+ mux := http.NewServeMux()
+
+ mux.HandleFunc("/api/v4/internal/pages", func(w http.ResponseWriter, r *http.Request) {
+ require.Equal(t, "GET", r.Method)
+ require.Equal(t, "group.gitlab.io", r.FormValue("host"))
+
+ validateToken(t, r.Header.Get("Gitlab-Pages-Api-Request"))
+
+ response := `{
+ "certificate": "foo",
+ "key": "bar",
+ "lookup_paths": [
+ {
+ "project_id": 123,
+ "access_control": false,
+ "source": {
+ "type": "file",
+ "path": "mygroup/myproject/public/"
+ },
+ "https_only": true,
+ "prefix": "/myproject/"
+ }
+ ]
+ }`
+
+ w.WriteHeader(http.StatusOK)
+ fmt.Fprint(w, response)
+ })
+
+ server := httptest.NewServer(mux)
+ defer server.Close()
+
+ client, _ := NewClient(server.URL, secretKey())
+
+ actual, err := client.GetVirtualDomain("group.gitlab.io")
+ require.NoError(t, err)
+
+ require.Equal(t, "foo", actual.Certificate)
+ require.Equal(t, "bar", actual.Key)
+
+ lookupPath := actual.LookupPaths[0]
+ require.Equal(t, 123, lookupPath.ProjectID)
+ require.Equal(t, false, lookupPath.AccessControl)
+ require.Equal(t, true, lookupPath.HTTPSOnly)
+ require.Equal(t, "/myproject/", lookupPath.Prefix)
+
+ require.Equal(t, "file", lookupPath.Source.Type)
+ require.Equal(t, "mygroup/myproject/public/", lookupPath.Source.Path)
+}
+
+func validateToken(t *testing.T, tokenString string) {
+ token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
+ if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
+ return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
+ }
+
+ return secretKey(), nil
+ })
+ require.NoError(t, err)
+
+ claims, ok := token.Claims.(jwt.MapClaims)
+ require.True(t, ok)
+ require.True(t, token.Valid)
+ require.NotNil(t, claims["exp"])
+ require.Equal(t, "gitlab-pages", claims["iss"])
+}
+
+func secretKey() []byte {
+ secretKey, _ := base64.StdEncoding.DecodeString(encodedSecret)
+ return secretKey
+}
diff --git a/internal/source/gitlab/response.go b/internal/source/gitlab/response.go
new file mode 100644
index 00000000..20597362
--- /dev/null
+++ b/internal/source/gitlab/response.go
@@ -0,0 +1,21 @@
+package gitlab
+
+// LookupPath represents a lookup path for a GitLab Pages virtual domain
+type LookupPath struct {
+ ProjectID int `json:"project_id,omitempty"`
+ AccessControl bool `json:"access_control,omitempty"`
+ HTTPSOnly bool `json:"https_only,omitempty"`
+ Prefix string `json:"prefix,omitempty"`
+ Source struct {
+ Type string `json:"type,omitempty"`
+ Path string `json:"path,omitempty"`
+ }
+}
+
+// VirtualDomain represents a GitLab Pages virtual domain
+type VirtualDomain struct {
+ Certificate string `json:"certificate,omitempty"`
+ Key string `json:"key,omitempty"`
+
+ LookupPaths []LookupPath `json:"lookup_paths"`
+}
diff --git a/main.go b/main.go
index 973c35cd..dc2d7873 100644
--- a/main.go
+++ b/main.go
@@ -57,6 +57,7 @@ var (
secret = flag.String("auth-secret", "", "Cookie store hash key, should be at least 32 bytes long.")
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")
+ gitLabAPISecretKey = flag.String("api-secret-key", "", "File with secret key used to authenticate with the GitLab API (NOT YET IMPLEMENTED)")
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")
@@ -143,6 +144,7 @@ func configFromFlags() appConfig {
}{
{&config.RootCertificate, *pagesRootCert},
{&config.RootKey, *pagesRootKey},
+ {&config.GitLabAPISecretKey, *gitLabAPISecretKey},
} {
if file.path != "" {
*file.contents = readFile(file.path)
@@ -237,9 +239,14 @@ func loadConfig() appConfig {
"tls-max-version": *tlsMaxVersion,
"use-http-2": config.HTTP2,
"gitlab-server": config.GitLabServer,
+ "api-secret-key": *gitLabAPISecretKey,
"auth-redirect-uri": config.RedirectURI,
}).Debug("Start daemon with configuration")
+ if *gitLabAPISecretKey != "" {
+ log.Warn("api-secret-key parameter is a placeholder for future developments, this option will be ignored.")
+ }
+
return config
}