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:
authorGrzegorz Bizon <grzesiek.bizon@gmail.com>2019-11-29 18:06:34 +0300
committerGrzegorz Bizon <grzesiek.bizon@gmail.com>2019-11-29 18:12:13 +0300
commitaf335dc002bde2f71e0a0c2de78b0890d7c07b30 (patch)
tree6e9fb74faa3a747a46d032d7cf099d139b9219e1
parentd72bbda34c0e7453337fea9add9276a7fabb4a96 (diff)
parent29b0a2d8ebc10ef9fa070d5634ab5d18938c935e (diff)
Merge branch 'master' into feature/gb/gitlab-domains-source
* master: Improve GitLab client tests Change GitLab API JWT expire time to 5s Read the context of api-secret-key file and store it in app config Improve gitlab client tests Ensure there is response before defer close it in gitlab.GetVirtualDomain Document acrguments for gitlab.NewClient Release 1.12.0 Add HTTP client to consume GitLab internal API for Pages Add minimal support for the api-secret-key config flag Conflicts: internal/source/gitlab/client.go
-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
}