From e4fd318fa611af9e05561b4f9e35d675ea092fec Mon Sep 17 00:00:00 2001 From: Gavin Schreiber Date: Tue, 30 Jul 2024 09:44:46 +0100 Subject: [PATCH 1/6] feat(issue-102): add service account token type for gitlab.com accounts --- README.md | 42 +++++++++++++++++++- gitlab_client.go | 42 ++++++++++++++++++++ gitlab_client_test.go | 16 ++++++++ helpers_test.go | 61 +++++++++++++++++++++++++--- path_role.go | 4 +- path_role_test.go | 88 +++++++++++++++++++++++++++++++++++++++++ path_token_role.go | 11 ++++++ path_token_role_test.go | 9 +++++ secret_access_tokens.go | 2 + type_token_type.go | 8 ++-- type_token_type_test.go | 4 ++ 11 files changed, 276 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 045a322..8fb1e55 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ through Vault. - Gitlab Personal Access Tokens: [https://docs.gitlab.com/ee/api/personal_access_tokens.html] - Gitlab Project Access Tokens: [https://docs.gitlab.com/ee/api/project_access_tokens.html] - Gitlab Group Access Tokens: [https://docs.gitlab.com/ee/api/group_access_tokens.html] +- Gitlab Service Account Personal Access Tokens: [https://docs.gitlab.com/ee/api/groups.html#create-personal-access-token-for-service-account-user] ## Getting Started @@ -81,6 +82,7 @@ For a list of available roles check https://docs.gitlab.com/ee/user/permissions. Depending on the type of token you have different scopes: * `Personal` - https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html#personal-access-token-scopes +* `Service Account` - https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html#personal-access-token-scopes * `Project` - https://docs.gitlab.com/ee/user/project/settings/project_access_tokens.html#scopes-for-a-project-access-token * `Group` - https://docs.gitlab.com/ee/user/group/settings/group_access_tokens.html#scopes-for-a-group-access-token @@ -89,6 +91,7 @@ Depending on the type of token you have different scopes: Can be * personal +* service-account (gitlab.com / SaaS only) * project * group @@ -160,6 +163,8 @@ This will create three roles, one of each type. # personal access tokens can only be created by Gitlab Administrators (see https://docs.gitlab.com/ee/api/users.html#create-a-personal-access-token) $ vault write gitlab/roles/personal name=personal-token-name path=username scopes="read_api" token_type=personal ttl=48h +$ vault write gitlab/roles/service-account name=service-account-personal-token-name path=group/username scopes="read_api" token_type=service-account ttl=48h + $ vault write gitlab/roles/project name=project-token-name path=group/project scopes="read_api" access_level=guest token_type=project ttl=48h $ vault write gitlab/roles/group name=group-token-name path=group/subgroup scopes="read_api" access_level=developer token_type=group ttl=48h @@ -187,7 +192,7 @@ token 7mbpSExz7ruyw1QgTjL- $ vault lease revoke gitlab/token/personal/0FrzLFkRKaUNZSfa6WfFqjWK All revocation operations queued successfully! ``` -##### Service accounts +##### Service accounts (self hosted) The service account users from Gitlab 16.1 are for all purposes users that don't use seats. So creating a service account and setting the path to the service account user would work the same as on a real user. ```shell $ curl --request POST --header "PRIVATE-TOKEN: $GITLAB_TOKEN" "https://gitlab/api/v4/service_accounts" | jq . @@ -221,6 +226,41 @@ scopes [api read_api read_repository read_registry] token -senkScjDo-SoGwST9PP ``` +##### Service accounts (SaaS / gitlab.com) +The service account users for SaaS gitlab work slightly differently than that of self-hosted instances and are created under groups +```shell +$ curl --request POST --header "PRIVATE-TOKEN: $GITLAB_TOKEN" "https://gitlab.com/api/v4/groups/345/service_accounts" | jq . +{ + "id": 2017, + "username": "service_account_00b069cb73a15d0a7ba8cd67a653599c", + "name": "Service account user" +} +``` + +In this case you would create a role like +```shell +$ vault write gitlab/roles/sa name=sa-name path=345/service_account_00b069cb73a15d0a7ba8cd67a653599c scopes="read_api" token_type=service-account token_ttl=24h +$ vault read gitlab/token/sa +vault read gitlab/token/sa + +Key Value +--- ----- +lease_id gitlab/token/sa/oFI2vpUdvykvMgNum6pZReYZ +lease_duration 20h1m37s +lease_renewable false +access_level n/a +created_at 2023-08-31T03:58:23.069Z +expires_at 2023-09-01T00:00:00Z +name vault-generated-service-account-access-token-f6417198 +role_name sa-name +path 345/service_account_00b069cb73a15d0a7ba8cd67a653599c +scopes [read_api] +token -senkScjDo-SoGwST9PP + +vault lease revoke gitlab/token/sa/oFI2vpUdvykvMgNum6pZReYZ +All revocation operations queued successfully! +``` + #### Group ```shell $ vault read gitlab/token/group diff --git a/gitlab_client.go b/gitlab_client.go index 7b873ad..5e655de 100644 --- a/gitlab_client.go +++ b/gitlab_client.go @@ -24,12 +24,14 @@ type Client interface { CurrentTokenInfo() (*EntryToken, error) RotateCurrentToken() (newToken *EntryToken, oldToken *EntryToken, err error) CreatePersonalAccessToken(username string, userId int, name string, expiresAt time.Time, scopes []string) (*EntryToken, error) + CreateServiceAccountPersonalAccessToken(path string, groupId string, userId int, name string, expiresAt time.Time, scopes []string) (*EntryToken, error) CreateGroupAccessToken(groupId string, name string, expiresAt time.Time, scopes []string, accessLevel AccessLevel) (*EntryToken, error) CreateProjectAccessToken(projectId string, name string, expiresAt time.Time, scopes []string, accessLevel AccessLevel) (*EntryToken, error) RevokePersonalAccessToken(tokenId int) error RevokeProjectAccessToken(tokenId int, projectId string) error RevokeGroupAccessToken(tokenId int, groupId string) error GetUserIdByUsername(username string) (int, error) + GetRolePathParts(path string) (interface{}, interface{}, error) } type gitlabClient struct { @@ -167,6 +169,46 @@ func (gc *gitlabClient) CreatePersonalAccessToken(username string, userId int, n return et, nil } +func (gc *gitlabClient) GetRolePathParts(path string) (interface{}, interface{}, error) { + parts := strings.Split(path, "/") + if len(parts) != 2 { + return nil, nil, errors.New("Too many arguments for service account path - eg: 1234/my-service-account") + } + groupId := parts[0] + username := parts[1] + + return groupId, username, nil +} + +func (gc *gitlabClient) CreateServiceAccountPersonalAccessToken(path string, groupId string, userId int, name string, expiresAt time.Time, scopes []string) (et *EntryToken, err error) { + var at *g.PersonalAccessToken + defer func() { + gc.logger.Debug("Create service account personal access token", "pat", at, "et", et, "groupId", groupId, "serviceAccountId", userId, "name", name, "expiresAt", expiresAt, "scopes", scopes, "error", err) + }() + at, _, err = gc.client.Groups.CreateServiceAccountPersonalAccessToken(groupId, userId, &g.CreateServiceAccountPersonalAccessTokenOptions{ + Name: g.Ptr(name), + ExpiresAt: (*g.ISOTime)(&expiresAt), + Scopes: &scopes, + }) + if err != nil { + return nil, err + } + et = &EntryToken{ + TokenID: at.ID, + UserID: userId, + ParentID: groupId, + Path: path, + Name: name, + Token: at.Token, + TokenType: TokenTypeServiceAccount, + CreatedAt: at.CreatedAt, + ExpiresAt: (*time.Time)(at.ExpiresAt), + Scopes: scopes, + AccessLevel: AccessLevelUnknown, + } + return et, nil +} + func (gc *gitlabClient) CreateGroupAccessToken(groupId string, name string, expiresAt time.Time, scopes []string, accessLevel AccessLevel) (et *EntryToken, err error) { var at *g.GroupAccessToken defer func() { diff --git a/gitlab_client_test.go b/gitlab_client_test.go index 16eabc3..0ac44ec 100644 --- a/gitlab_client_test.go +++ b/gitlab_client_test.go @@ -226,6 +226,22 @@ func TestGitlabClient_CreateAccessToken_And_Revoke(t *testing.T) { require.EqualValues(t, gitlab.TokenTypePersonal, entryToken.TokenType) require.NotEmpty(t, entryToken.Token) require.NoError(t, client.RevokePersonalAccessToken(entryToken.TokenID)) + + groupId, username, _ := client.GetRolePathParts("123/service-account-user") + serviceAccountId, err := client.GetUserIdByUsername(username.(string)) + entryToken, err = client.CreateServiceAccountPersonalAccessToken( + "example/service-account-user", + groupId.(string), + serviceAccountId, + "name", + time.Now(), + []string{gitlab.TokenScopeReadApi.String()}, + ) + require.NoError(t, err) + require.NotNil(t, entryToken) + require.EqualValues(t, gitlab.TokenTypeServiceAccount, entryToken.TokenType) + require.NotEmpty(t, entryToken.Token) + require.NoError(t, client.RevokePersonalAccessToken(entryToken.TokenID)) } func TestGitlabClient_RotateCurrentToken(t *testing.T) { diff --git a/helpers_test.go b/helpers_test.go index 65ddc16..52adcc1 100644 --- a/helpers_test.go +++ b/helpers_test.go @@ -123,12 +123,14 @@ type inMemoryClient struct { muLock sync.Mutex valid bool - personalAccessTokenRevokeError bool - groupAccessTokenRevokeError bool - projectAccessTokenRevokeError bool - personalAccessTokenCreateError bool - groupAccessTokenCreateError bool - projectAccessTokenCreateError bool + personalAccessTokenRevokeError bool + serviceAccountPersonalAccessTokenRevokeError bool + groupAccessTokenRevokeError bool + projectAccessTokenRevokeError bool + personalAccessTokenCreateError bool + serviceAccountPersonalAccessTokenCreateError bool + groupAccessTokenCreateError bool + projectAccessTokenCreateError bool calledMainToken int calledRotateMainToken int @@ -235,6 +237,32 @@ func (i *inMemoryClient) CreateProjectAccessToken(projectId string, name string, return &entryToken, nil } +func (i *inMemoryClient) CreateServiceAccountPersonalAccessToken(path string, groupId string, userId int, name string, expiresAt time.Time, scopes []string) (*gitlab.EntryToken, error) { + i.muLock.Lock() + defer i.muLock.Unlock() + if i.serviceAccountPersonalAccessTokenCreateError { + return nil, fmt.Errorf("CreateServiceAccountPersonalAccessToken") + } + i.internalCounter++ + + var tokenId = i.internalCounter + var entryToken = gitlab.EntryToken{ + TokenID: tokenId, + UserID: userId, + ParentID: groupId, + Path: path, + Name: name, + Token: "", + TokenType: gitlab.TokenTypeServiceAccount, + CreatedAt: g.Ptr(time.Now()), + ExpiresAt: &expiresAt, + Scopes: scopes, + AccessLevel: gitlab.AccessLevelUnknown, + } + i.accessTokens[fmt.Sprintf("%s_%v", gitlab.TokenTypeServiceAccount.String(), tokenId)] = entryToken + return &entryToken, nil +} + func (i *inMemoryClient) RevokePersonalAccessToken(tokenId int) error { i.muLock.Lock() defer i.muLock.Unlock() @@ -265,6 +293,16 @@ func (i *inMemoryClient) RevokeGroupAccessToken(tokenId int, groupId string) err return nil } +func (i *inMemoryClient) RevokeServiceAccountPersonalAccessToken(tokenId int) error { + i.muLock.Lock() + defer i.muLock.Unlock() + if i.serviceAccountPersonalAccessTokenRevokeError { + return fmt.Errorf("RevokeServiceAccountPersonalAccessToken") + } + delete(i.accessTokens, fmt.Sprintf("%s_%v", gitlab.TokenTypeServiceAccount.String(), tokenId)) + return nil +} + func (i *inMemoryClient) GetUserIdByUsername(username string) (int, error) { idx := slices.Index(i.users, username) if idx == -1 { @@ -274,6 +312,17 @@ func (i *inMemoryClient) GetUserIdByUsername(username string) (int, error) { return idx, nil } +func (i *inMemoryClient) GetRolePathParts(path string) (interface{}, interface{}, error) { + parts := strings.Split(path, "/") + if len(parts) != 2 { + return nil, 0, errors.New("Too many arguments for service account path - eg: 1234/my-service-account") + } + groupId := parts[0] + username := parts[1] + + return groupId, username, nil +} + var _ gitlab.Client = new(inMemoryClient) func sanitizePath(path string) string { diff --git a/path_role.go b/path_role.go index b51f423..a47143f 100644 --- a/path_role.go +++ b/path_role.go @@ -260,6 +260,8 @@ func (b *Backend) pathRolesWrite(ctx context.Context, req *logical.Request, data switch tokenType { case TokenTypePersonal: validAccessLevels = ValidPersonalAccessLevels + case TokenTypeServiceAccount: + validAccessLevels = ValidPersonalAccessLevels case TokenTypeGroup: validAccessLevels = ValidGroupAccessLevels case TokenTypeProject: @@ -273,7 +275,7 @@ func (b *Backend) pathRolesWrite(ctx context.Context, req *logical.Request, data // validate scopes var invalidScopes []string var validScopes = validTokenScopes - if tokenType == TokenTypePersonal { + if tokenType == TokenTypePersonal || tokenType == TokenTypeServiceAccount { validScopes = append(validScopes, ValidPersonalTokenScopes...) } for _, scope := range role.Scopes { diff --git a/path_role_test.go b/path_role_test.go index c19d809..6a8bbca 100644 --- a/path_role_test.go +++ b/path_role_test.go @@ -199,6 +199,51 @@ func TestPathRoles(t *testing.T) { }) }) + t.Run(gitlab.TokenTypeServiceAccount.String(), func(t *testing.T) { + t.Run("no access level defined", func(t *testing.T) { + ctx := getCtxGitlabClient(t) + var b, l, err = getBackendWithConfig(ctx, defaultConfig) + require.NoError(t, err) + resp, err := b.HandleRequest(ctx, &logical.Request{ + Operation: logical.CreateOperation, + Path: fmt.Sprintf("%s/test", gitlab.PathRoleStorage), Storage: l, + Data: map[string]any{ + "path": "user", + "name": gitlab.TokenTypeServiceAccount.String(), + "token_type": gitlab.TokenTypeServiceAccount.String(), + "ttl": gitlab.DefaultAccessTokenMinTTL, + "scopes": gitlab.ValidPersonalTokenScopes, + "gitlab_revokes_token": false, + }, + }) + require.NoError(t, err) + require.NotNil(t, resp) + require.NoError(t, resp.Error()) + require.Empty(t, resp.Warnings) + }) + t.Run("with access level defined", func(t *testing.T) { + ctx := getCtxGitlabClient(t) + var b, l, err = getBackendWithConfig(ctx, defaultConfig) + require.NoError(t, err) + resp, err := b.HandleRequest(ctx, &logical.Request{ + Operation: logical.CreateOperation, + Path: fmt.Sprintf("%s/test", gitlab.PathRoleStorage), Storage: l, + Data: map[string]any{ + "path": "user", + "name": gitlab.TokenTypeServiceAccount.String(), + "access_level": gitlab.AccessLevelOwnerPermissions.String(), + "token_type": gitlab.TokenTypeServiceAccount.String(), + "ttl": gitlab.DefaultAccessTokenMinTTL, + "scopes": gitlab.ValidPersonalTokenScopes, + "gitlab_revokes_token": false, + }, + }) + require.Error(t, err) + require.NotNil(t, resp) + require.Error(t, resp.Error()) + }) + }) + }) t.Run("create with missing parameters", func(t *testing.T) { @@ -349,6 +394,49 @@ func TestPathRoles(t *testing.T) { }) }) + t.Run("Service Account Personal token scopes", func(t *testing.T) { + t.Run("valid scopes", func(t *testing.T) { + ctx := getCtxGitlabClient(t) + var b, l, err = getBackendWithConfig(ctx, defaultConfig) + require.NoError(t, err) + resp, err := b.HandleRequest(ctx, &logical.Request{ + Operation: logical.CreateOperation, + Path: fmt.Sprintf("%s/%d", gitlab.PathRoleStorage, time.Now().UnixNano()), Storage: l, + Data: map[string]any{ + "path": "user", + "name": "Example service account user personal token", + "ttl": "48h", + "token_type": gitlab.TokenTypeServiceAccount.String(), + "scopes": gitlab.ValidPersonalTokenScopes, + }, + }) + require.NoError(t, err) + require.NotNil(t, resp) + }) + + t.Run("invalid scopes", func(t *testing.T) { + ctx := getCtxGitlabClient(t) + var b, l, err = getBackendWithConfig(ctx, defaultConfig) + require.NoError(t, err) + resp, err := b.HandleRequest(ctx, &logical.Request{ + Operation: logical.CreateOperation, + Path: fmt.Sprintf("%s/%d", gitlab.PathRoleStorage, time.Now().UnixNano()), Storage: l, + Data: map[string]any{ + "path": "user", + "name": "Example service account user personal token", + "token_type": gitlab.TokenTypeServiceAccount.String(), + "scopes": []string{ + "invalid_scope", + }, + }, + }) + require.Error(t, err) + require.NotNil(t, resp) + var errorMap = countErrByName(err.(*multierror.Error)) + assert.EqualValues(t, 1, errorMap[gitlab.ErrFieldInvalidValue.Error()]) + }) + }) + t.Run("update handler existence check", func(t *testing.T) { ctx := getCtxGitlabClient(t) var b, l, err = getBackend(ctx) diff --git a/path_token_role.go b/path_token_role.go index 1c8f78b..75ff469 100644 --- a/path_token_role.go +++ b/path_token_role.go @@ -95,6 +95,17 @@ func (b *Backend) pathTokenRoleCreate(ctx context.Context, req *logical.Request, if token, err = client.CreatePersonalAccessToken(role.Path, userId, name, expiresAt, role.Scopes); err != nil { return nil, err } + case TokenTypeServiceAccount: + var userId int + groupId, username, err := client.GetRolePathParts(role.Path) + userId, err = client.GetUserIdByUsername(username.(string)) + if err != nil { + return nil, err + } + b.Logger().Debug("Creating service account access token for role", "path", role.Path, "name", name, "expiresAt", expiresAt, "scopes", role.Scopes, "accessLevel", role.AccessLevel) + if token, err = client.CreateServiceAccountPersonalAccessToken(role.Path, groupId.(string), userId, name, expiresAt, role.Scopes); err != nil { + return nil, err + } default: return logical.ErrorResponse("invalid token type"), fmt.Errorf("%s: %w", role.TokenType.String(), ErrUnknownTokenType) } diff --git a/path_token_role_test.go b/path_token_role_test.go index 843ccf5..fc6ae5e 100644 --- a/path_token_role_test.go +++ b/path_token_role_test.go @@ -59,6 +59,8 @@ func TestPathTokenRoles(t *testing.T) { path = "admin-user" case gitlab.TokenTypeGroup: path = "example" + case gitlab.TokenTypeServiceAccount: + path = "example/example-service-account" } // create a role @@ -126,6 +128,8 @@ func TestPathTokenRoles(t *testing.T) { client.personalAccessTokenRevokeError = true case gitlab.TokenTypeGroup: client.groupAccessTokenRevokeError = true + case gitlab.TokenTypeServiceAccount: + client.serviceAccountPersonalAccessTokenRevokeError = true } resp, err = b.HandleRequest(ctx, &logical.Request{ Operation: logical.RevokeOperation, @@ -160,4 +164,9 @@ func TestPathTokenRoles(t *testing.T) { generalTokenCreation(t, gitlab.TokenTypeGroup, gitlab.AccessLevelGuestPermissions, false) generalTokenCreation(t, gitlab.TokenTypeGroup, gitlab.AccessLevelGuestPermissions, true) }) + + t.Run("service account personal access token", func(t *testing.T) { + generalTokenCreation(t, gitlab.TokenTypeServiceAccount, gitlab.AccessLevelUnknown, false) + generalTokenCreation(t, gitlab.TokenTypeServiceAccount, gitlab.AccessLevelUnknown, true) + }) } diff --git a/secret_access_tokens.go b/secret_access_tokens.go index 5ad2df8..7db655d 100644 --- a/secret_access_tokens.go +++ b/secret_access_tokens.go @@ -98,6 +98,8 @@ func (b *Backend) secretAccessTokenRevoke(ctx context.Context, req *logical.Requ err = client.RevokeProjectAccessToken(tokenId, parentId) case TokenTypeGroup: err = client.RevokeGroupAccessToken(tokenId, parentId) + case TokenTypeServiceAccount: + err = client.RevokePersonalAccessToken(tokenId) } if err != nil && !errors.Is(err, ErrAccessTokenNotFound) { diff --git a/type_token_type.go b/type_token_type.go index ec8d45b..c2c38bb 100644 --- a/type_token_type.go +++ b/type_token_type.go @@ -10,9 +10,10 @@ import ( type TokenType string const ( - TokenTypePersonal = TokenType("personal") - TokenTypeProject = TokenType("project") - TokenTypeGroup = TokenType("group") + TokenTypePersonal = TokenType("personal") + TokenTypeServiceAccount = TokenType("service-account") + TokenTypeProject = TokenType("project") + TokenTypeGroup = TokenType("group") TokenTypeUnknown = TokenType("") ) @@ -24,6 +25,7 @@ var ( TokenTypePersonal.String(), TokenTypeProject.String(), TokenTypeGroup.String(), + TokenTypeServiceAccount.String(), } ) diff --git a/type_token_type_test.go b/type_token_type_test.go index 8798b98..6ade51d 100644 --- a/type_token_type_test.go +++ b/type_token_type_test.go @@ -25,6 +25,10 @@ func TestTokenType(t *testing.T) { expected: gitlab.TokenTypeProject, input: gitlab.TokenTypeProject.String(), }, + { + expected: gitlab.TokenTypeServiceAccount, + input: gitlab.TokenTypeServiceAccount.String(), + }, { expected: gitlab.TokenTypeUnknown, input: "unknown", From fdb26c0d33141eaa98ecb68fa69ad9308df6d5cd Mon Sep 17 00:00:00 2001 From: Gavin Schreiber Date: Tue, 30 Jul 2024 10:13:56 +0100 Subject: [PATCH 2/6] ci(deps): trigger build --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8fb1e55..198c0a8 100644 --- a/README.md +++ b/README.md @@ -357,4 +357,4 @@ $ vault secrets list -detailed -format=json | jq '."gitlab/"' ``` ## Info -Running the logging with `debug` level will shows sensitive information in the logs. +Running the logging with `debug` level will shows sensitive information in the logs. \ No newline at end of file From b4ad79a7fb19a2b0512018eb5f43d037547e3384 Mon Sep 17 00:00:00 2001 From: Gavin Schreiber Date: Tue, 30 Jul 2024 13:39:47 +0100 Subject: [PATCH 3/6] test(deps): Fix broken tests and service account revoke token --- entry_token.go | 1 + gitlab_client.go | 24 ++++ gitlab_client_test.go | 4 +- helpers_test.go | 2 +- path_role_test.go | 6 +- secret_access_tokens.go | 3 +- ...abClient_CreateAccessToken_And_Revoke.yaml | 124 ++++++++++++++++++ ..._Personal_token_scopes_invalid_scopes.yaml | 68 ++++++++++ ...nt_Personal_token_scopes_valid_scopes.yaml | 68 ++++++++++ ...rvice_account_no_access_level_defined.yaml | 69 ++++++++++ ...ice_account_with_access_level_defined.yaml | 69 ++++++++++ ...service_account_personal_access_token.yaml | 3 + 12 files changed, 433 insertions(+), 8 deletions(-) create mode 100644 testdata/fixtures/16.11.6/TestPathRoles_Service_Account_Personal_token_scopes_invalid_scopes.yaml create mode 100644 testdata/fixtures/16.11.6/TestPathRoles_Service_Account_Personal_token_scopes_valid_scopes.yaml create mode 100644 testdata/fixtures/16.11.6/TestPathRoles_access_level_service_account_no_access_level_defined.yaml create mode 100644 testdata/fixtures/16.11.6/TestPathRoles_access_level_service_account_with_access_level_defined.yaml create mode 100644 testdata/fixtures/16.11.6/TestPathTokenRoles_service_account_personal_access_token.yaml diff --git a/entry_token.go b/entry_token.go index 6e8d4c6..0c693c0 100644 --- a/entry_token.go +++ b/entry_token.go @@ -35,6 +35,7 @@ func (e EntryToken) SecretResponse() (map[string]any, map[string]any) { map[string]any{ "path": e.Path, "name": e.Name, + "token": e.Token, "user_id": e.UserID, "parent_id": e.ParentID, "token_id": e.TokenID, diff --git a/gitlab_client.go b/gitlab_client.go index 5e655de..47ebad3 100644 --- a/gitlab_client.go +++ b/gitlab_client.go @@ -28,6 +28,7 @@ type Client interface { CreateGroupAccessToken(groupId string, name string, expiresAt time.Time, scopes []string, accessLevel AccessLevel) (*EntryToken, error) CreateProjectAccessToken(projectId string, name string, expiresAt time.Time, scopes []string, accessLevel AccessLevel) (*EntryToken, error) RevokePersonalAccessToken(tokenId int) error + RevokeServiceAccountPersonalAccessToken(tokenId int, tokenValue string) error RevokeProjectAccessToken(tokenId int, projectId string) error RevokeGroupAccessToken(tokenId int, groupId string) error GetUserIdByUsername(username string) (int, error) @@ -283,6 +284,29 @@ func (gc *gitlabClient) RevokePersonalAccessToken(tokenId int) (err error) { return nil } +func (gc *gitlabClient) RevokeServiceAccountPersonalAccessToken(tokenId int, tokenValue string) (err error) { + defer func() { + gc.logger.Debug("Revoke personal access token", "tokenId", tokenId, "error", err) + }() + + u := "personal_access_tokens/self" + req, err := gc.client.NewRequest(http.MethodDelete, u, nil, nil) + if err != nil { + return err + } + req.Header.Set("PRIVATE-TOKEN", tokenValue) + + var resp *g.Response + resp, err = gc.client.Do(req, nil) + if resp != nil && resp.StatusCode == http.StatusNotFound { + return fmt.Errorf("service account personal: %w", ErrAccessTokenNotFound) + } + if err != nil { + return err + } + return nil +} + func (gc *gitlabClient) RevokeProjectAccessToken(tokenId int, projectId string) (err error) { defer func() { gc.logger.Debug("Revoke project access token", "tokenId", tokenId, "error", err) diff --git a/gitlab_client_test.go b/gitlab_client_test.go index 0ac44ec..d85b26e 100644 --- a/gitlab_client_test.go +++ b/gitlab_client_test.go @@ -230,7 +230,7 @@ func TestGitlabClient_CreateAccessToken_And_Revoke(t *testing.T) { groupId, username, _ := client.GetRolePathParts("123/service-account-user") serviceAccountId, err := client.GetUserIdByUsername(username.(string)) entryToken, err = client.CreateServiceAccountPersonalAccessToken( - "example/service-account-user", + "123/service-account-user", groupId.(string), serviceAccountId, "name", @@ -241,7 +241,7 @@ func TestGitlabClient_CreateAccessToken_And_Revoke(t *testing.T) { require.NotNil(t, entryToken) require.EqualValues(t, gitlab.TokenTypeServiceAccount, entryToken.TokenType) require.NotEmpty(t, entryToken.Token) - require.NoError(t, client.RevokePersonalAccessToken(entryToken.TokenID)) + require.NoError(t, client.RevokeServiceAccountPersonalAccessToken(entryToken.TokenID, entryToken.Token)) } func TestGitlabClient_RotateCurrentToken(t *testing.T) { diff --git a/helpers_test.go b/helpers_test.go index 52adcc1..ac79c42 100644 --- a/helpers_test.go +++ b/helpers_test.go @@ -293,7 +293,7 @@ func (i *inMemoryClient) RevokeGroupAccessToken(tokenId int, groupId string) err return nil } -func (i *inMemoryClient) RevokeServiceAccountPersonalAccessToken(tokenId int) error { +func (i *inMemoryClient) RevokeServiceAccountPersonalAccessToken(tokenId int, tokenValue string) error { i.muLock.Lock() defer i.muLock.Unlock() if i.serviceAccountPersonalAccessTokenRevokeError { diff --git a/path_role_test.go b/path_role_test.go index 6a8bbca..9b7e217 100644 --- a/path_role_test.go +++ b/path_role_test.go @@ -208,7 +208,7 @@ func TestPathRoles(t *testing.T) { Operation: logical.CreateOperation, Path: fmt.Sprintf("%s/test", gitlab.PathRoleStorage), Storage: l, Data: map[string]any{ - "path": "user", + "path": "345/user", "name": gitlab.TokenTypeServiceAccount.String(), "token_type": gitlab.TokenTypeServiceAccount.String(), "ttl": gitlab.DefaultAccessTokenMinTTL, @@ -219,7 +219,6 @@ func TestPathRoles(t *testing.T) { require.NoError(t, err) require.NotNil(t, resp) require.NoError(t, resp.Error()) - require.Empty(t, resp.Warnings) }) t.Run("with access level defined", func(t *testing.T) { ctx := getCtxGitlabClient(t) @@ -229,7 +228,7 @@ func TestPathRoles(t *testing.T) { Operation: logical.CreateOperation, Path: fmt.Sprintf("%s/test", gitlab.PathRoleStorage), Storage: l, Data: map[string]any{ - "path": "user", + "path": "345/user", "name": gitlab.TokenTypeServiceAccount.String(), "access_level": gitlab.AccessLevelOwnerPermissions.String(), "token_type": gitlab.TokenTypeServiceAccount.String(), @@ -243,7 +242,6 @@ func TestPathRoles(t *testing.T) { require.Error(t, resp.Error()) }) }) - }) t.Run("create with missing parameters", func(t *testing.T) { diff --git a/secret_access_tokens.go b/secret_access_tokens.go index 7db655d..13bdce4 100644 --- a/secret_access_tokens.go +++ b/secret_access_tokens.go @@ -78,6 +78,7 @@ func (b *Backend) secretAccessTokenRevoke(ctx context.Context, req *logical.Requ var tokenTypeValue = req.Secret.InternalData["token_type"].(string) var gitlabRevokesToken, _ = strconv.ParseBool(req.Secret.InternalData["gitlab_revokes_token"].(string)) var vaultRevokesToken = !gitlabRevokesToken + var tokenValue = req.Secret.InternalData["token"].(string) tokenType, err = TokenTypeParse(tokenTypeValue) if err != nil { // shouldn't be possible to hit due to the guards in the creation of the roles @@ -99,7 +100,7 @@ func (b *Backend) secretAccessTokenRevoke(ctx context.Context, req *logical.Requ case TokenTypeGroup: err = client.RevokeGroupAccessToken(tokenId, parentId) case TokenTypeServiceAccount: - err = client.RevokePersonalAccessToken(tokenId) + err = client.RevokeServiceAccountPersonalAccessToken(tokenId, tokenValue) } if err != nil && !errors.Is(err, ErrAccessTokenNotFound) { diff --git a/testdata/fixtures/16.11.6/TestGitlabClient_CreateAccessToken_And_Revoke.yaml b/testdata/fixtures/16.11.6/TestGitlabClient_CreateAccessToken_And_Revoke.yaml index de3ce44..0ef7294 100644 --- a/testdata/fixtures/16.11.6/TestGitlabClient_CreateAccessToken_And_Revoke.yaml +++ b/testdata/fixtures/16.11.6/TestGitlabClient_CreateAccessToken_And_Revoke.yaml @@ -379,3 +379,127 @@ interactions: status: 204 No Content code: 204 duration: 19.158375ms + - id: 6 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: localhost:8080 + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + Private-Token: + - glpat-secret-random-token + User-Agent: + - go-gitlab + url: http://localhost:8080/api/v4/groups/123/service_accounts/0/personal_access_tokens + method: POST + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [ ] + trailer: { } + content_length: 211 + uncompressed: false + body: '{"id":51,"name":"name","revoked":false,"created_at":"2024-07-11T20:50:32.053Z","scopes":["read_api"],"user_id":1,"last_used_at":null,"active":false,"expires_at":"2024-07-11","token":"glpat-o1dGt6YBxEKPfocUSzBy"}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Connection: + - keep-alive + Content-Length: + - "211" + Content-Type: + - application/json + Date: + - Thu, 11 Jul 2024 20:50:32 GMT + Etag: + - W/"39b5fb35a75057457c1edf9ebfcf93cb" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx + Strict-Transport-Security: + - max-age=63072000 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Gitlab-Meta: + - '{"correlation_id":"01J2HRZPF6V6HHQDW8T8ZVHQ9M","version":"1"}' + X-Request-Id: + - 01J2HRZPF6V6HHQDW8T8ZVHQ9M + X-Runtime: + - "0.019053" + status: 201 Created + code: 201 + duration: 21.664875ms + - id: 7 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: localhost:8080 + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + Private-Token: + - glpat-o1dGt6YBxEKPfocUSzBy + User-Agent: + - go-gitlab + url: http://localhost:8080/api/v4/personal_access_tokens/self + method: DELETE + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 0 + uncompressed: false + body: "" + headers: + Cache-Control: + - no-cache + Connection: + - keep-alive + Date: + - Thu, 11 Jul 2024 20:50:32 GMT + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx + Strict-Transport-Security: + - max-age=63072000 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Gitlab-Meta: + - '{"correlation_id":"01J2HRZPGJN0KEYA02VG79C1MW","version":"1"}' + X-Request-Id: + - 01J2HRZPGJN0KEYA02VG79C1MW + X-Runtime: + - "0.016860" + status: 204 No Content + code: 204 + duration: 19.158375ms \ No newline at end of file diff --git a/testdata/fixtures/16.11.6/TestPathRoles_Service_Account_Personal_token_scopes_invalid_scopes.yaml b/testdata/fixtures/16.11.6/TestPathRoles_Service_Account_Personal_token_scopes_invalid_scopes.yaml new file mode 100644 index 0000000..e465ad1 --- /dev/null +++ b/testdata/fixtures/16.11.6/TestPathRoles_Service_Account_Personal_token_scopes_invalid_scopes.yaml @@ -0,0 +1,68 @@ +--- +version: 2 +interactions: + - id: 0 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: localhost:8080 + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + Private-Token: + - glpat-secret-random-token + User-Agent: + - go-gitlab + url: http://localhost:8080/api/v4/personal_access_tokens/self + method: GET + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: + - chunked + trailer: {} + content_length: -1 + uncompressed: true + body: '{"id":1,"name":"Initial token","revoked":false,"created_at":"2024-07-11T18:53:26.792Z","scopes":["api","read_api","read_user","sudo","admin_mode","create_runner","k8s_proxy","read_repository","write_repository","ai_features","read_service_ping"],"user_id":1,"last_used_at":"2024-07-11T20:49:46.599Z","active":true,"expires_at":"2025-07-11"}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Thu, 11 Jul 2024 20:50:32 GMT + Etag: + - W/"a64ccd47e1869c3ec0c4e62786258eca" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx + Strict-Transport-Security: + - max-age=63072000 + Vary: + - Accept-Encoding + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Gitlab-Meta: + - '{"correlation_id":"01J2HRZQ16FASBMEMW9TDR2Q15","version":"1"}' + X-Request-Id: + - 01J2HRZQ16FASBMEMW9TDR2Q15 + X-Runtime: + - "0.011465" + status: 200 OK + code: 200 + duration: 14.0385ms diff --git a/testdata/fixtures/16.11.6/TestPathRoles_Service_Account_Personal_token_scopes_valid_scopes.yaml b/testdata/fixtures/16.11.6/TestPathRoles_Service_Account_Personal_token_scopes_valid_scopes.yaml new file mode 100644 index 0000000..29e72a0 --- /dev/null +++ b/testdata/fixtures/16.11.6/TestPathRoles_Service_Account_Personal_token_scopes_valid_scopes.yaml @@ -0,0 +1,68 @@ +--- +version: 2 +interactions: + - id: 0 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: localhost:8080 + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + Private-Token: + - glpat-secret-random-token + User-Agent: + - go-gitlab + url: http://localhost:8080/api/v4/personal_access_tokens/self + method: GET + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: + - chunked + trailer: {} + content_length: -1 + uncompressed: true + body: '{"id":1,"name":"Initial token","revoked":false,"created_at":"2024-07-11T18:53:26.792Z","scopes":["api","read_api","read_user","sudo","admin_mode","create_runner","k8s_proxy","read_repository","write_repository","ai_features","read_service_ping"],"user_id":1,"last_used_at":"2024-07-11T20:49:46.599Z","active":true,"expires_at":"2025-07-11"}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Thu, 11 Jul 2024 20:50:32 GMT + Etag: + - W/"a64ccd47e1869c3ec0c4e62786258eca" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx + Strict-Transport-Security: + - max-age=63072000 + Vary: + - Accept-Encoding + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Gitlab-Meta: + - '{"correlation_id":"01J2HRZQ08S061TARRXMNJ9XXS","version":"1"}' + X-Request-Id: + - 01J2HRZQ08S061TARRXMNJ9XXS + X-Runtime: + - "0.010753" + status: 200 OK + code: 200 + duration: 12.959375ms diff --git a/testdata/fixtures/16.11.6/TestPathRoles_access_level_service_account_no_access_level_defined.yaml b/testdata/fixtures/16.11.6/TestPathRoles_access_level_service_account_no_access_level_defined.yaml new file mode 100644 index 0000000..951866a --- /dev/null +++ b/testdata/fixtures/16.11.6/TestPathRoles_access_level_service_account_no_access_level_defined.yaml @@ -0,0 +1,69 @@ +--- +version: 2 +interactions: + - id: 0 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: localhost:8080 + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + Private-Token: + - glpat-secret-random-token + User-Agent: + - go-gitlab + url: http://localhost:8080/api/v4/personal_access_tokens/self + method: GET + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: + - chunked + trailer: {} + content_length: -1 + uncompressed: true + body: '{"id":1,"name":"Initial token","revoked":false,"created_at":"2024-07-11T18:53:26.792Z","scopes":["api","read_api","read_user","sudo","admin_mode","create_runner","k8s_proxy","read_repository","write_repository","ai_features","read_service_ping"],"user_id":1,"last_used_at":"2024-07-11T20:49:46.599Z","active":true,"expires_at":"2025-07-11"}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Thu, 11 Jul 2024 20:50:32 GMT + Etag: + - W/"a64ccd47e1869c3ec0c4e62786258eca" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx + Strict-Transport-Security: + - max-age=63072000 + Vary: + - Accept-Encoding + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Gitlab-Meta: + - '{"correlation_id":"01J2HRZPNQA1ATCGQ5DNJ607B0","version":"1"}' + X-Request-Id: + - 01J2HRZPNQA1ATCGQ5DNJ607B0 + X-Runtime: + - "0.008834" + status: 200 OK + code: 200 + duration: 10.91825ms + diff --git a/testdata/fixtures/16.11.6/TestPathRoles_access_level_service_account_with_access_level_defined.yaml b/testdata/fixtures/16.11.6/TestPathRoles_access_level_service_account_with_access_level_defined.yaml new file mode 100644 index 0000000..80c6214 --- /dev/null +++ b/testdata/fixtures/16.11.6/TestPathRoles_access_level_service_account_with_access_level_defined.yaml @@ -0,0 +1,69 @@ +--- +version: 2 +interactions: + - id: 0 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: localhost:8080 + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + Private-Token: + - glpat-secret-random-token + User-Agent: + - go-gitlab + url: http://localhost:8080/api/v4/personal_access_tokens/self + method: GET + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: + - chunked + trailer: {} + content_length: -1 + uncompressed: true + body: '{"id":1,"name":"Initial token","revoked":false,"created_at":"2024-07-11T18:53:26.792Z","scopes":["api","read_api","read_user","sudo","admin_mode","create_runner","k8s_proxy","read_repository","write_repository","ai_features","read_service_ping"],"user_id":1,"last_used_at":"2024-07-11T20:49:46.599Z","active":true,"expires_at":"2025-07-11"}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Thu, 11 Jul 2024 20:50:32 GMT + Etag: + - W/"a64ccd47e1869c3ec0c4e62786258eca" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx + Strict-Transport-Security: + - max-age=63072000 + Vary: + - Accept-Encoding + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Gitlab-Meta: + - '{"correlation_id":"01J2HRZPPGNS2HTZHAPNX2KPH9","version":"1"}' + X-Request-Id: + - 01J2HRZPPGNS2HTZHAPNX2KPH9 + X-Runtime: + - "0.008769" + status: 200 OK + code: 200 + duration: 10.513166ms + diff --git a/testdata/fixtures/16.11.6/TestPathTokenRoles_service_account_personal_access_token.yaml b/testdata/fixtures/16.11.6/TestPathTokenRoles_service_account_personal_access_token.yaml new file mode 100644 index 0000000..2797c38 --- /dev/null +++ b/testdata/fixtures/16.11.6/TestPathTokenRoles_service_account_personal_access_token.yaml @@ -0,0 +1,3 @@ +--- +version: 2 +interactions: [] From 09da21e4494904533a9cd4e5dad43921d4db454c Mon Sep 17 00:00:00 2001 From: Gavin Schreiber Date: Tue, 30 Jul 2024 13:44:46 +0100 Subject: [PATCH 4/6] test(deps): fix lint issues --- gitlab_client_test.go | 2 +- path_token_role.go | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/gitlab_client_test.go b/gitlab_client_test.go index d85b26e..0ab87be 100644 --- a/gitlab_client_test.go +++ b/gitlab_client_test.go @@ -228,7 +228,7 @@ func TestGitlabClient_CreateAccessToken_And_Revoke(t *testing.T) { require.NoError(t, client.RevokePersonalAccessToken(entryToken.TokenID)) groupId, username, _ := client.GetRolePathParts("123/service-account-user") - serviceAccountId, err := client.GetUserIdByUsername(username.(string)) + serviceAccountId, _ := client.GetUserIdByUsername(username.(string)) entryToken, err = client.CreateServiceAccountPersonalAccessToken( "123/service-account-user", groupId.(string), diff --git a/path_token_role.go b/path_token_role.go index 75ff469..6defcf5 100644 --- a/path_token_role.go +++ b/path_token_role.go @@ -98,6 +98,9 @@ func (b *Backend) pathTokenRoleCreate(ctx context.Context, req *logical.Request, case TokenTypeServiceAccount: var userId int groupId, username, err := client.GetRolePathParts(role.Path) + if err != nil { + return nil, err + } userId, err = client.GetUserIdByUsername(username.(string)) if err != nil { return nil, err From d5d56247d81897de8fc0ce8cf35a57b0c3c4886d Mon Sep 17 00:00:00 2001 From: Gavin Schreiber Date: Tue, 30 Jul 2024 16:32:38 +0100 Subject: [PATCH 5/6] test(deps): update coverage --- gitlab_client.go | 4 +- gitlab_client_test.go | 13 +++- ...abClient_CreateAccessToken_And_Revoke.yaml | 59 +++++++++++++++++++ 3 files changed, 73 insertions(+), 3 deletions(-) diff --git a/gitlab_client.go b/gitlab_client.go index 47ebad3..6efa4b5 100644 --- a/gitlab_client.go +++ b/gitlab_client.go @@ -292,13 +292,13 @@ func (gc *gitlabClient) RevokeServiceAccountPersonalAccessToken(tokenId int, tok u := "personal_access_tokens/self" req, err := gc.client.NewRequest(http.MethodDelete, u, nil, nil) if err != nil { - return err + return fmt.Errorf("service account personal: %w", ErrAccessTokenNotFound) } req.Header.Set("PRIVATE-TOKEN", tokenValue) var resp *g.Response resp, err = gc.client.Do(req, nil) - if resp != nil && resp.StatusCode == http.StatusNotFound { + if resp != nil && resp.StatusCode == http.StatusUnauthorized { return fmt.Errorf("service account personal: %w", ErrAccessTokenNotFound) } if err != nil { diff --git a/gitlab_client_test.go b/gitlab_client_test.go index 0ab87be..697e07a 100644 --- a/gitlab_client_test.go +++ b/gitlab_client_test.go @@ -83,6 +83,12 @@ func TestGitlabClient_InvalidToken(t *testing.T) { entryToken, err = client.CreatePersonalAccessToken("username", 0, "name", time.Now(), []string{"scope"}) require.Error(t, err) require.Nil(t, entryToken) + + groupId, username, _ := client.GetRolePathParts("123/service-account-user") + serviceAccountId, _ := client.GetUserIdByUsername(username.(string)) + entryToken, err = client.CreateServiceAccountPersonalAccessToken("123/username", groupId.(string), serviceAccountId, "name", time.Now(), []string{"scope"}) + require.Error(t, err) + require.Nil(t, entryToken) } func TestGitlabClient_RevokeToken_NotFound(t *testing.T) { @@ -227,7 +233,11 @@ func TestGitlabClient_CreateAccessToken_And_Revoke(t *testing.T) { require.NotEmpty(t, entryToken.Token) require.NoError(t, client.RevokePersonalAccessToken(entryToken.TokenID)) - groupId, username, _ := client.GetRolePathParts("123/service-account-user") + // Test incorrect path fails + groupId, username, err := client.GetRolePathParts("service-account-user") + require.Error(t, err) + + groupId, username, _ = client.GetRolePathParts("123/service-account-user") serviceAccountId, _ := client.GetUserIdByUsername(username.(string)) entryToken, err = client.CreateServiceAccountPersonalAccessToken( "123/service-account-user", @@ -242,6 +252,7 @@ func TestGitlabClient_CreateAccessToken_And_Revoke(t *testing.T) { require.EqualValues(t, gitlab.TokenTypeServiceAccount, entryToken.TokenType) require.NotEmpty(t, entryToken.Token) require.NoError(t, client.RevokeServiceAccountPersonalAccessToken(entryToken.TokenID, entryToken.Token)) + require.Error(t, client.RevokeServiceAccountPersonalAccessToken(entryToken.TokenID, "invalid-token")) } func TestGitlabClient_RotateCurrentToken(t *testing.T) { diff --git a/testdata/fixtures/16.11.6/TestGitlabClient_CreateAccessToken_And_Revoke.yaml b/testdata/fixtures/16.11.6/TestGitlabClient_CreateAccessToken_And_Revoke.yaml index 0ef7294..5bdcebb 100644 --- a/testdata/fixtures/16.11.6/TestGitlabClient_CreateAccessToken_And_Revoke.yaml +++ b/testdata/fixtures/16.11.6/TestGitlabClient_CreateAccessToken_And_Revoke.yaml @@ -502,4 +502,63 @@ interactions: - "0.016860" status: 204 No Content code: 204 + duration: 19.158375ms + - id: 8 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: localhost:8080 + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + Private-Token: + - invalid-token + User-Agent: + - go-gitlab + url: http://localhost:8080/api/v4/personal_access_tokens/self + method: DELETE + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 0 + uncompressed: false + body: "" + headers: + Cache-Control: + - no-cache + Connection: + - keep-alive + Date: + - Thu, 11 Jul 2024 20:50:32 GMT + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx + Strict-Transport-Security: + - max-age=63072000 + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Gitlab-Meta: + - '{"correlation_id":"01J2HRZPGJN0KEYA02VG79C1MW","version":"1"}' + X-Request-Id: + - 01J2HRZPGJN0KEYA02VG79C1MW + X-Runtime: + - "0.016860" + status: 401 Unauthorised + code: 401 duration: 19.158375ms \ No newline at end of file From 099001a35438bd6db9ac58745b205c4effa17dca Mon Sep 17 00:00:00 2001 From: Gavin Schreiber Date: Tue, 30 Jul 2024 23:58:28 +0100 Subject: [PATCH 6/6] test(deps): resolve lint --- gitlab_client_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gitlab_client_test.go b/gitlab_client_test.go index 697e07a..60407f9 100644 --- a/gitlab_client_test.go +++ b/gitlab_client_test.go @@ -234,10 +234,10 @@ func TestGitlabClient_CreateAccessToken_And_Revoke(t *testing.T) { require.NoError(t, client.RevokePersonalAccessToken(entryToken.TokenID)) // Test incorrect path fails - groupId, username, err := client.GetRolePathParts("service-account-user") + _, _, err = client.GetRolePathParts("service-account-user") require.Error(t, err) - groupId, username, _ = client.GetRolePathParts("123/service-account-user") + groupId, username, _ := client.GetRolePathParts("123/service-account-user") serviceAccountId, _ := client.GetUserIdByUsername(username.(string)) entryToken, err = client.CreateServiceAccountPersonalAccessToken( "123/service-account-user",