diff options
author | Alessio Caiazza <acaiazza@gitlab.com> | 2020-01-21 12:07:51 +0300 |
---|---|---|
committer | Alessio Caiazza <acaiazza@gitlab.com> | 2020-01-21 12:07:51 +0300 |
commit | a04541aaf6e26b556fb0ea4c49d80a1494abef9e (patch) | |
tree | 93710b62480618f6ec95913047d3e134115a0fe3 /internal/source | |
parent | b3276944b2933c6e734200d14b5a9232ce1bfc74 (diff) | |
parent | e7e50231712abc7c0ea79acdcd9db3b3003c6cad (diff) |
Merge branch '274-make-gitlab-client-timeout-jtw-token-expiry-configurable' into 'master'
Make GitLab client timeout / JTW token expiry configurable
Closes #274
See merge request gitlab-org/gitlab-pages!219
Diffstat (limited to 'internal/source')
-rw-r--r-- | internal/source/domains_test.go | 8 | ||||
-rw-r--r-- | internal/source/gitlab/client/client.go | 26 | ||||
-rw-r--r-- | internal/source/gitlab/client/client_test.go | 128 | ||||
-rw-r--r-- | internal/source/gitlab/client/config.go | 4 |
4 files changed, 126 insertions, 40 deletions
diff --git a/internal/source/domains_test.go b/internal/source/domains_test.go index 5fea6ae3..bbdb8c21 100644 --- a/internal/source/domains_test.go +++ b/internal/source/domains_test.go @@ -2,6 +2,7 @@ package source import ( "testing" + "time" "github.com/stretchr/testify/require" @@ -21,6 +22,13 @@ func (c sourceConfig) GitlabServerURL() string { func (c sourceConfig) GitlabAPISecret() []byte { return []byte(c.secret) } +func (c sourceConfig) GitlabClientConnectionTimeout() time.Duration { + return 10 * time.Second +} + +func (c sourceConfig) GitlabJWTTokenExpiry() time.Duration { + return 30 * time.Second +} func TestDomainSources(t *testing.T) { t.Run("when GitLab API URL has been provided", func(t *testing.T) { diff --git a/internal/source/gitlab/client/client.go b/internal/source/gitlab/client/client.go index f5ecf355..59776a8f 100644 --- a/internal/source/gitlab/client/client.go +++ b/internal/source/gitlab/client/client.go @@ -19,18 +19,15 @@ import ( // Client is a HTTP client to access Pages internal API type Client struct { - secretKey []byte - baseURL *url.URL - httpClient *http.Client + secretKey []byte + baseURL *url.URL + httpClient *http.Client + jwtTokenExpiry time.Duration } -// TODO make these values configurable https://gitlab.com/gitlab-org/gitlab-pages/issues/274 -var tokenTimeout = 30 * time.Second -var connectionTimeout = 10 * time.Second - // NewClient initializes and returns new Client baseUrl is // appConfig.GitLabServer secretKey is appConfig.GitLabAPISecretKey -func NewClient(baseURL string, secretKey []byte) (*Client, error) { +func NewClient(baseURL string, secretKey []byte, connectionTimeout, jwtTokenExpiry time.Duration) (*Client, error) { if len(baseURL) == 0 || len(secretKey) == 0 { return nil, errors.New("GitLab API URL or API secret has not been provided") } @@ -40,6 +37,14 @@ func NewClient(baseURL string, secretKey []byte) (*Client, error) { return nil, err } + if connectionTimeout == 0 { + return nil, errors.New("GitLab HTTP client connection timeout has not been provided") + } + + if jwtTokenExpiry == 0 { + return nil, errors.New("GitLab JWT token expiry has not been provided") + } + return &Client{ secretKey: secretKey, baseURL: url, @@ -47,12 +52,13 @@ func NewClient(baseURL string, secretKey []byte) (*Client, error) { Timeout: connectionTimeout, Transport: httptransport.Transport, }, + jwtTokenExpiry: jwtTokenExpiry, }, nil } // NewFromConfig creates a new client from Config struct func NewFromConfig(config Config) (*Client, error) { - return NewClient(config.GitlabServerURL(), config.GitlabAPISecret()) + return NewClient(config.GitlabServerURL(), config.GitlabAPISecret(), config.GitlabClientConnectionTimeout(), config.GitlabJWTTokenExpiry()) } // Resolve returns a VirtualDomain configuration wrapped into a Lookup for a @@ -151,7 +157,7 @@ func (gc *Client) request(ctx context.Context, method string, endpoint *url.URL) func (gc *Client) token() (string, error) { claims := jwt.StandardClaims{ Issuer: "gitlab-pages", - ExpiresAt: time.Now().Add(tokenTimeout).Unix(), + ExpiresAt: time.Now().UTC().Add(gc.jwtTokenExpiry).Unix(), } token, err := jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString(gc.secretKey) diff --git a/internal/source/gitlab/client/client_test.go b/internal/source/gitlab/client/client_test.go index 247de5c7..bdfbe6b0 100644 --- a/internal/source/gitlab/client/client_test.go +++ b/internal/source/gitlab/client/client_test.go @@ -7,6 +7,7 @@ import ( "net/http" "net/http/httptest" "testing" + "time" jwt "github.com/dgrijalva/jwt-go" "github.com/stretchr/testify/require" @@ -14,34 +15,90 @@ import ( "gitlab.com/gitlab-org/gitlab-pages/internal/fixture" ) +const ( + defaultClientConnTimeout = 10 * time.Second + defaultJWTTokenExpiry = 30 * time.Second +) + func TestNewValidBaseURL(t *testing.T) { - _, err := NewClient("https://gitlab.com", secretKey()) + _, err := NewClient("https://gitlab.com", secretKey(t), defaultClientConnTimeout, defaultJWTTokenExpiry) require.NoError(t, err) } -func TestNewInvalidBaseURL(t *testing.T) { - t.Run("when API URL is not valid", func(t *testing.T) { - client, err := NewClient("%", secretKey()) - - require.Error(t, err) - require.Nil(t, client) - }) - - t.Run("when API URL is empty", func(t *testing.T) { - client, err := NewClient("", secretKey()) - - require.Nil(t, client) - require.EqualError(t, err, "GitLab API URL or API secret has not been provided") - }) +func TestNewInvalidConfiguration(t *testing.T) { + type args struct { + baseURL string + secretKey []byte + connectionTimeout time.Duration + jwtTokenExpiry time.Duration + } - t.Run("when API secret is empty", func(t *testing.T) { - client, err := NewClient("https://gitlab.com", []byte{}) + tests := []struct { + name string + args args + wantErrMsg string + }{ + + { + name: "invalid_api_url", + args: args{ + baseURL: "%", + secretKey: secretKey(t), + connectionTimeout: defaultClientConnTimeout, + jwtTokenExpiry: defaultJWTTokenExpiry, + }, + wantErrMsg: "parse %: invalid URL escape \"%\"", + }, + { + name: "invalid_api_url_empty", + args: args{ + baseURL: "", + secretKey: secretKey(t), + connectionTimeout: defaultClientConnTimeout, + jwtTokenExpiry: defaultJWTTokenExpiry, + }, + wantErrMsg: "GitLab API URL or API secret has not been provided", + }, + { + name: "invalid_api_secret_empty", + args: args{ + baseURL: "https://gitlab.com", + secretKey: []byte{}, + connectionTimeout: defaultClientConnTimeout, + jwtTokenExpiry: defaultJWTTokenExpiry, + }, + wantErrMsg: "GitLab API URL or API secret has not been provided", + }, + { + name: "invalid_http_client_timeout", + args: args{ + baseURL: "https://gitlab.com", + secretKey: secretKey(t), + connectionTimeout: 0, + jwtTokenExpiry: defaultJWTTokenExpiry, + }, + wantErrMsg: "GitLab HTTP client connection timeout has not been provided", + }, + { + name: "invalid_jwt_token_expiry", + args: args{ + baseURL: "https://gitlab.com", + secretKey: secretKey(t), + connectionTimeout: defaultClientConnTimeout, + jwtTokenExpiry: 0, + }, + wantErrMsg: "GitLab JWT token expiry has not been provided", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { - require.Nil(t, client) - require.EqualError(t, err, "GitLab API URL or API secret has not been provided") - }) + got, err := NewClient(tt.args.baseURL, tt.args.secretKey, tt.args.connectionTimeout, tt.args.jwtTokenExpiry) + require.Nil(t, got) + require.EqualError(t, err, tt.wantErrMsg) + }) + } } - func TestLookupForErrorResponses(t *testing.T) { tests := map[int]string{ http.StatusUnauthorized: "HTTP status: 401", @@ -60,8 +117,7 @@ func TestLookupForErrorResponses(t *testing.T) { server := httptest.NewServer(mux) defer server.Close() - client, err := NewClient(server.URL, secretKey()) - require.NoError(t, err) + client := defaultClient(t, server.URL) lookup := client.GetLookup(context.Background(), "group.gitlab.io") @@ -81,8 +137,7 @@ func TestMissingDomain(t *testing.T) { server := httptest.NewServer(mux) defer server.Close() - client, err := NewClient(server.URL, secretKey()) - require.NoError(t, err) + client := defaultClient(t, server.URL) lookup := client.GetLookup(context.Background(), "group.gitlab.io") @@ -123,8 +178,7 @@ func TestGetVirtualDomainAuthenticatedRequest(t *testing.T) { server := httptest.NewServer(mux) defer server.Close() - client, err := NewClient(server.URL, secretKey()) - require.NoError(t, err) + client := defaultClient(t, server.URL) lookup := client.GetLookup(context.Background(), "group.gitlab.io") require.NoError(t, lookup.Error) @@ -143,12 +197,13 @@ func TestGetVirtualDomainAuthenticatedRequest(t *testing.T) { } func validateToken(t *testing.T, tokenString string) { + t.Helper() 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 + return secretKey(t), nil }) require.NoError(t, err) @@ -159,7 +214,20 @@ func validateToken(t *testing.T, tokenString string) { require.Equal(t, "gitlab-pages", claims["iss"]) } -func secretKey() []byte { - secretKey, _ := base64.StdEncoding.DecodeString(fixture.GitLabAPISecretKey) +func secretKey(t *testing.T) []byte { + t.Helper() + + secretKey, err := base64.StdEncoding.DecodeString(fixture.GitLabAPISecretKey) + require.NoError(t, err) + return secretKey } + +func defaultClient(t *testing.T, url string) *Client { + t.Helper() + + client, err := NewClient(url, secretKey(t), defaultClientConnTimeout, defaultJWTTokenExpiry) + require.NoError(t, err) + + return client +} diff --git a/internal/source/gitlab/client/config.go b/internal/source/gitlab/client/config.go index 49c13a60..4ed14267 100644 --- a/internal/source/gitlab/client/config.go +++ b/internal/source/gitlab/client/config.go @@ -1,8 +1,12 @@ package client +import "time" + // Config represents an interface that is configuration provider for client // capable of comunicating with GitLab type Config interface { GitlabServerURL() string GitlabAPISecret() []byte + GitlabClientConnectionTimeout() time.Duration + GitlabJWTTokenExpiry() time.Duration } |