summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPatrick Bajao <ebajao@gitlab.com>2022-04-27 03:04:21 +0000
committerPatrick Bajao <ebajao@gitlab.com>2022-04-27 03:04:21 +0000
commit73a8361eb3c49979d4ff5d9f1ba82a9935b22915 (patch)
tree0cc59978a7ec94dacca7d32bccd179638b35745d
parent67164bdf589ec2729f20ef4434cf83d1535a9d24 (diff)
parent71231b3393c2e81558b74c8a574e297b2a1f900f (diff)
downloadgitlab-shell-73a8361eb3c49979d4ff5d9f1ba82a9935b22915.tar.gz
Merge branch 'id-gitlabnet-jwt' into 'main'
Add JWT token to GitLab Rails request See merge request gitlab-org/gitlab-shell!596
-rw-r--r--client/client_test.go58
-rw-r--r--client/gitlabnet.go25
-rw-r--r--go.mod1
-rw-r--r--go.sum2
4 files changed, 77 insertions, 9 deletions
diff --git a/client/client_test.go b/client/client_test.go
index 186f919..4b26898 100644
--- a/client/client_test.go
+++ b/client/client_test.go
@@ -10,13 +10,19 @@ import (
"path"
"strings"
"testing"
+ "time"
+ "github.com/golang-jwt/jwt/v4"
"github.com/stretchr/testify/require"
"gitlab.com/gitlab-org/gitlab-shell/client/testserver"
"gitlab.com/gitlab-org/gitlab-shell/internal/testhelper"
)
+var (
+ secret = []byte("sssh, it's a secret")
+)
+
func TestClients(t *testing.T) {
testhelper.PrepareTestRootDir(t)
@@ -57,12 +63,10 @@ func TestClients(t *testing.T) {
t.Run(tc.desc, func(t *testing.T) {
url := tc.server(t, buildRequests(t, tc.relativeURLRoot))
- secret := "sssh, it's a secret"
-
httpClient, err := NewHTTPClientWithOpts(url, tc.relativeURLRoot, tc.caFile, "", 1, nil)
require.NoError(t, err)
- client, err := NewGitlabNetClient("", "", secret, httpClient)
+ client, err := NewGitlabNetClient("", "", string(secret), httpClient)
require.NoError(t, err)
testBrokenRequest(t, client)
@@ -71,6 +75,7 @@ func TestClients(t *testing.T) {
testMissing(t, client)
testErrorMessage(t, client)
testAuthenticationHeader(t, client)
+ testJWTAuthenticationHeader(t, client)
})
}
}
@@ -160,7 +165,7 @@ func testAuthenticationHeader(t *testing.T, client *GitlabNetClient) {
header, err := base64.StdEncoding.DecodeString(string(responseBody))
require.NoError(t, err)
- require.Equal(t, "sssh, it's a secret", string(header))
+ require.Equal(t, secret, header)
})
t.Run("Authentication headers for POST", func(t *testing.T) {
@@ -175,7 +180,44 @@ func testAuthenticationHeader(t *testing.T, client *GitlabNetClient) {
header, err := base64.StdEncoding.DecodeString(string(responseBody))
require.NoError(t, err)
- require.Equal(t, "sssh, it's a secret", string(header))
+ require.Equal(t, secret, header)
+ })
+}
+
+func testJWTAuthenticationHeader(t *testing.T, client *GitlabNetClient) {
+ verifyJWTToken := func(t *testing.T, response *http.Response) {
+ responseBody, err := io.ReadAll(response.Body)
+ require.NoError(t, err)
+
+ claims := &jwt.RegisteredClaims{}
+ token, err := jwt.ParseWithClaims(string(responseBody), claims, func(token *jwt.Token) (interface{}, error) {
+ return secret, nil
+ })
+ require.NoError(t, err)
+ require.True(t, token.Valid)
+ require.Equal(t, "gitlab-shell", claims.Issuer)
+ require.Equal(t, time.Now().Truncate(time.Second), claims.IssuedAt.Time, time.Second)
+ require.Equal(t, time.Now().Truncate(time.Second).Add(time.Minute), claims.ExpiresAt.Time, time.Second)
+ }
+
+ t.Run("JWT authentication headers for GET", func(t *testing.T) {
+ response, err := client.Get(context.Background(), "/jwt_auth")
+ require.NoError(t, err)
+ require.NotNil(t, response)
+
+ defer response.Body.Close()
+
+ verifyJWTToken(t, response)
+ })
+
+ t.Run("JWT authentication headers for POST", func(t *testing.T) {
+ response, err := client.Post(context.Background(), "/jwt_auth", map[string]string{})
+ require.NoError(t, err)
+ require.NotNil(t, response)
+
+ defer response.Body.Close()
+
+ verifyJWTToken(t, response)
})
}
@@ -209,6 +251,12 @@ func buildRequests(t *testing.T, relativeURLRoot string) []testserver.TestReques
},
},
{
+ Path: "/api/v4/internal/jwt_auth",
+ Handler: func(w http.ResponseWriter, r *http.Request) {
+ fmt.Fprint(w, r.Header.Get(apiSecretHeaderName))
+ },
+ },
+ {
Path: "/api/v4/internal/error",
Handler: func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
diff --git a/client/gitlabnet.go b/client/gitlabnet.go
index f71c110..ac056aa 100644
--- a/client/gitlabnet.go
+++ b/client/gitlabnet.go
@@ -11,13 +11,18 @@ import (
"strings"
"time"
+ "github.com/golang-jwt/jwt/v4"
+
"gitlab.com/gitlab-org/labkit/log"
)
const (
- internalApiPath = "/api/v4/internal"
- secretHeaderName = "Gitlab-Shared-Secret"
- defaultUserAgent = "GitLab-Shell"
+ internalApiPath = "/api/v4/internal"
+ secretHeaderName = "Gitlab-Shared-Secret"
+ apiSecretHeaderName = "Gitlab-Shell-Api-Request"
+ defaultUserAgent = "GitLab-Shell"
+ jwtTTL = time.Minute
+ jwtIssuer = "gitlab-shell"
)
type ErrorResponse struct {
@@ -121,10 +126,22 @@ func (c *GitlabNetClient) DoRequest(ctx context.Context, method, path string, da
if user != "" && password != "" {
request.SetBasicAuth(user, password)
}
+ secretBytes := []byte(c.secret)
- encodedSecret := base64.StdEncoding.EncodeToString([]byte(c.secret))
+ encodedSecret := base64.StdEncoding.EncodeToString(secretBytes)
request.Header.Set(secretHeaderName, encodedSecret)
+ claims := jwt.RegisteredClaims{
+ Issuer: jwtIssuer,
+ IssuedAt: jwt.NewNumericDate(time.Now()),
+ ExpiresAt: jwt.NewNumericDate(time.Now().Add(jwtTTL)),
+ }
+ tokenString, err := jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString(secretBytes)
+ if err != nil {
+ return nil, err
+ }
+ request.Header.Set(apiSecretHeaderName, tokenString)
+
request.Header.Add("Content-Type", "application/json")
request.Header.Add("User-Agent", c.userAgent)
request.Close = true
diff --git a/go.mod b/go.mod
index 7b9067e..3db58c3 100644
--- a/go.mod
+++ b/go.mod
@@ -3,6 +3,7 @@ module gitlab.com/gitlab-org/gitlab-shell
go 1.17
require (
+ github.com/golang-jwt/jwt/v4 v4.4.1
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
github.com/mattn/go-shellwords v1.0.11
diff --git a/go.sum b/go.sum
index 642a5dc..2835f81 100644
--- a/go.sum
+++ b/go.sum
@@ -316,6 +316,8 @@ github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zV
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
+github.com/golang-jwt/jwt/v4 v4.4.1 h1:pC5DB52sCeK48Wlb9oPcdhnjkz1TKt1D/P7WKJ0kUcQ=
+github.com/golang-jwt/jwt/v4 v4.4.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=