From 2747f87f813f9a5291c2bd9f7e6444e87636dc4e Mon Sep 17 00:00:00 2001 From: Ilija Matoski Date: Sat, 12 Oct 2024 22:05:39 +0200 Subject: [PATCH] feat: allow us to specify multiple configurations and config per role (#120) --- README.md | 86 +- backend.go | 81 +- backend_test.go | 17 +- defs.go | 1 + entry_config.go | 12 +- entry_role.go | 4 +- entry_token.go | 2 + gitlab_client.go | 31 +- helpers_test.go | 13 +- path_config.go | 36 +- path_config_list.go | 58 + path_config_list_test.go | 117 ++ path_config_rotate.go | 31 +- path_config_rotate_test.go | 2 +- path_config_test.go | 32 +- path_config_token_autorotate_test.go | 29 +- path_role.go | 27 +- path_role_test.go | 34 +- path_token_role.go | 16 +- path_token_role_multiple_config_test.go | 152 +++ secret_access_tokens.go | 27 +- secret_access_tokens_test.go | 14 +- .../TestPathConfigList_empty_list.yaml | 3 + .../TestPathConfigList_multiple_configs.yaml | 198 +++ .../TestPathRoles_invalid_name_template.yaml | 68 + .../TestPathTokenRolesMultipleConfigs.yaml | 1120 +++++++++++++++++ ...dmin_user_pat_gitlab_revokes_token_test.go | 2 +- ...admin_user_pat_vault_revokes_token_test.go | 2 +- with_gitlab_com_user_rotate_token_test.go | 5 +- with_normal_user_gat_test.go | 2 +- with_normal_user_personal_at_fails_test.go | 2 +- with_normal_user_project_at_test.go | 2 +- with_service_account_fail_test.go | 6 +- with_service_account_group_test.go | 8 +- with_service_account_user_test.go | 8 +- 35 files changed, 2052 insertions(+), 196 deletions(-) create mode 100644 path_config_list.go create mode 100644 path_config_list_test.go create mode 100644 path_token_role_multiple_config_test.go create mode 100644 testdata/fixtures/16.11.6/TestPathConfigList_empty_list.yaml create mode 100644 testdata/fixtures/16.11.6/TestPathConfigList_multiple_configs.yaml create mode 100644 testdata/fixtures/16.11.6/TestPathRoles_invalid_name_template.yaml create mode 100644 testdata/fixtures/16.11.6/TestPathTokenRolesMultipleConfigs.yaml diff --git a/README.md b/README.md index e8ce67c..9e88bf7 100644 --- a/README.md +++ b/README.md @@ -31,13 +31,53 @@ To learn specifically about how plugins work, see documentation on [Vault plugin ## GitLab - GitLab CE/EE - Self Managed -- gitlab.com (cannot use personal access token) -- Dedicated Instance (cannot use personal access token) +- gitlab.com (cannot use personal access token, and user service account) +- Dedicated Instance (cannot use personal access token, and user service account) ### Setup Before we can use this plugin we need to create an access token that will have rights to do what we need to. +## Paths + +For a list of the available endpoints you can check bellow or by running the command `vault path-help gitlab` for your version after you've mounted it. + +```shell +$ vault path-help gitlab +## DESCRIPTION + +The Gitlab Access token auth Backend dynamically generates private +and group tokens. + +After mounting this Backend, credentials to manage Gitlab tokens must be configured +with the "^config/(?P\w(([\w-.@]+)?\w)?)$" endpoints. + +## PATHS + +The following paths are supported by this backend. To view help for +any of the paths below, use the help command with any route matching +the path pattern. Note that depending on the policy of your auth token, +you may or may not be able to access certain paths. + + ^config/(?P\w(([\w-.@]+)?\w)?)$ + Configure the Gitlab Access Tokens Backend. + + ^config/(?P\w(([\w-.]+)?\w)?)/rotate$ + Rotate the gitlab token for this configuration. + + ^config?/?$ + Lists existing configs + + ^roles/(?P\w(([\w-.@]+)?\w)?)$ + Create a role with parameters that are used to generate a various access tokens. + + ^roles?/?$ + Lists existing roles + + ^token/(?P\w(([\w-.@]+)?\w)?)$ + Generate an access token based on the specified role +``` + ## Security Model The current authentication model requires providing Vault with a Gitlab Token. @@ -91,6 +131,8 @@ The following data points can be used within your token name template. These are * access_level * scopes * token_type +* role_name +* config_name * gitlab_revokes_token * unix_timestamp_utc @@ -167,15 +209,18 @@ If you use Vault to manage the tokens the minimal TTL you can use is `1h`, by se The command bellow will set up the config backend with a max TTL of 48h. ```shell -$ vault write gitlab/config base_url=https://gitlab.example.com token=gitlab-super-secret-token auto_rotate_token=false auto_rotate_before=48h type=self-managed -$ vault read gitlab/config +$ vault write gitlab/config/default base_url=https://gitlab.example.com token=gitlab-super-secret-token auto_rotate_token=false auto_rotate_before=48h type=self-managed +$ vault read gitlab/config/default Key Value --- ----- auto_rotate_before 48h0m0s auto_rotate_token false -base_url https://gitlab.example.com +base_url http://localhost:8080 +name default +scopes api, read_api, read_user, sudo, admin_mode, create_runner, k8s_proxy, read_repository, write_repository, ai_features, read_service_ping +token_created_at 2024-07-11T18:53:26Z +token_expires_at 2025-07-11T00:00:00Z token_id 1 -token_expires_at 2025-03-29T00:00:00Z token_sha1_hash 9441e6e07d77a2d5601ab5d7cac5868d358d885c type self-managed ``` @@ -183,14 +228,15 @@ type self-managed After initial setup should you wish to change any value you can do so by using the patch command for example ```shell -$ vault patch gitlab/config type=saas auto_rotate_token=true auto_rotate_before=64h token=glpat-secret-admin-token +$ vault patch gitlab/config/default type=saas auto_rotate_token=true auto_rotate_before=64h token=glpat-secret-admin-token Key Value --- ----- auto_rotate_before 64h0m0s auto_rotate_token true -base_url https://gitlab.example.com +base_url http://localhost:8080 +name default scopes api, read_api, read_user, sudo, admin_mode, create_runner, k8s_proxy, read_repository, write_repository, ai_features, read_service_ping -token_created_at 2024-07-11T18:53:26Z +token_created_at 2024-07-11T18:53:46Z token_expires_at 2025-07-11T00:00:00Z token_id 2 token_sha1_hash c6e762667cadb936f0c8439b0d240661a270eba1 @@ -264,16 +310,24 @@ If the original token that has been supplied to the backend is not expired. We c to force a rotation of the main token. This would create a new token with the same expiration as the original token. ```shell -$ vault write -f gitlab/config/rotate +$ vault write -f gitlab/config/default/rotate Key Value --- ----- -auto_rotate_before 48h0m0s -auto_rotate_token false -base_url https://gitlab.example.com -token_expires_at 2025-03-29T00:00:00Z -token_id 110 -token_sha1_hash b8ff3f9e560f29d15f756fc92a3b1d6602aaae55 +lease_id gitlab/config/default/rotate/Ils8dp7PDXSWgb5dF4I2NPPS +lease_duration 768h +lease_renewable false +auto_rotate_before 64h0m0s +auto_rotate_token true +base_url http://localhost:8080 +name default +scopes api, read_api, read_user, sudo, admin_mode, create_runner, k8s_proxy, read_repository, write_repository, ai_features, read_service_ping +token_created_at 2024-07-11T18:53:46Z +token_expires_at 2024-10-12T20:02:11Z +token_id 76 +token_sha1_hash 10e413692b345809f6b4db57c5d38fadaacbf9be +type saas ``` + ## Upgrading ```shell diff --git a/backend.go b/backend.go index 3ef6071..f330d53 100644 --- a/backend.go +++ b/backend.go @@ -1,8 +1,10 @@ package gitlab import ( + "cmp" "context" "errors" + "fmt" "net/http" "strings" "sync" @@ -21,7 +23,7 @@ The Gitlab Access token auth Backend dynamically generates private and group tokens. After mounting this Backend, credentials to manage Gitlab tokens must be configured -with the "config/" endpoints. +with the "^config/(?P\w(([\w-.@]+)?\w)?)$" endpoints. ` ) @@ -29,6 +31,7 @@ with the "config/" endpoints. func Factory(ctx context.Context, conf *logical.BackendConfig) (logical.Backend, error) { var b = &Backend{ roleLocks: locksutil.CreateLocks(), + clients: sync.Map{}, } b.Backend = &framework.Backend{ @@ -53,6 +56,7 @@ func Factory(ctx context.Context, conf *logical.BackendConfig) (logical.Backend, Paths: framework.PathAppend( []*framework.Path{ pathConfig(b), + pathListConfig(b), pathConfigTokenRotate(b), pathListRoles(b), pathRoles(b), @@ -63,7 +67,6 @@ func Factory(ctx context.Context, conf *logical.BackendConfig) (logical.Backend, PeriodicFunc: b.periodicFunc, } - b.SetClient(nil) var err = b.Setup(ctx, conf) return b, err } @@ -72,7 +75,7 @@ type Backend struct { *framework.Backend // The client that we can use to create and revoke the access tokens - client Client + clients sync.Map // Mutex to protect access to gitlab clients and client configs, a change to the gitlab client config // would invalidate the gitlab client, so it will need to be reinitialized @@ -82,27 +85,30 @@ type Backend struct { roleLocks []*locksutil.LockEntry } -func (b *Backend) periodicFunc(ctx context.Context, request *logical.Request) error { +func (b *Backend) periodicFunc(ctx context.Context, req *logical.Request) (err error) { b.Logger().Debug("Periodic action executing") - if !b.WriteSafeReplicationState() { - return nil - } + if b.WriteSafeReplicationState() { + var config *EntryConfig - var config *EntryConfig - var err error - - b.lockClientMutex.Lock() - unlockLockClientMutex := sync.OnceFunc(func() { b.lockClientMutex.Unlock() }) - defer unlockLockClientMutex() - if config, err = getConfig(ctx, request.Storage); err == nil { - unlockLockClientMutex() - if config == nil { - return nil - } - // If we need to autorotate the token, initiate the procedure to autorotate the token - if config.AutoRotateToken { - err = errors.Join(err, b.checkAndRotateConfigToken(ctx, request, config)) + b.lockClientMutex.Lock() + unlockLockClientMutex := sync.OnceFunc(func() { b.lockClientMutex.Unlock() }) + defer unlockLockClientMutex() + + var configs []string + configs, err = req.Storage.List(ctx, fmt.Sprintf("%s/", PathConfigStorage)) + + for _, name := range configs { + if config, err = getConfig(ctx, req.Storage, name); err == nil { + b.Logger().Debug("Trying to rotate the config", "name", name) + unlockLockClientMutex() + if config != nil { + // If we need to autorotate the token, initiate the procedure to autorotate the token + if config.AutoRotateToken { + err = errors.Join(err, b.checkAndRotateConfigToken(ctx, req, config)) + } + } + } } } @@ -112,37 +118,46 @@ func (b *Backend) periodicFunc(ctx context.Context, request *logical.Request) er // Invalidate invalidates the key if required func (b *Backend) Invalidate(ctx context.Context, key string) { b.Logger().Debug("Backend invalidate", "key", key) - if key == PathConfigStorage { - b.Logger().Warn("Gitlab config changed, reinitializing the gitlab client") + if strings.HasPrefix(key, PathConfigStorage) { + parts := strings.SplitN(key, "/", 2) + var name = parts[1] + b.Logger().Warn(fmt.Sprintf("Gitlab config for %s changed, reinitializing the gitlab client", name)) b.lockClientMutex.Lock() defer b.lockClientMutex.Unlock() - b.client = nil + b.clients.Delete(name) } } -func (b *Backend) GetClient() Client { - return b.client +func (b *Backend) GetClient(name string) Client { + if client, ok := b.clients.Load(cmp.Or(name, DefaultConfigName)); ok { + return client.(Client) + } + return nil } -func (b *Backend) SetClient(client Client) { +func (b *Backend) SetClient(client Client, name string) { + name = cmp.Or(name, DefaultConfigName) if client == nil { b.Logger().Debug("Setting a nil client") return } b.Logger().Debug("Setting a new client") - b.client = client + b.clients.Store(name, client) } -func (b *Backend) getClient(ctx context.Context, s logical.Storage) (client Client, err error) { - if b.client != nil && b.client.Valid() { +func (b *Backend) getClient(ctx context.Context, s logical.Storage, name string) (client Client, err error) { + if c, ok := b.clients.Load(cmp.Or(name, DefaultConfigName)); ok { + client = c.(Client) + } + if client != nil && client.Valid() { b.Logger().Debug("Returning existing gitlab client") - return b.client, nil + return client, nil } b.lockClientMutex.RLock() defer b.lockClientMutex.RUnlock() var config *EntryConfig - config, err = getConfig(ctx, s) + config, err = getConfig(ctx, s, name) if err != nil { b.Logger().Error("Failed to retrieve configuration", "error", err.Error()) return nil, err @@ -152,7 +167,7 @@ func (b *Backend) getClient(ctx context.Context, s logical.Storage) (client Clie httpClient, _ = HttpClientFromContext(ctx) if client, _ = GitlabClientFromContext(ctx); client == nil { if client, err = NewGitlabClient(config, httpClient, b.Logger()); err == nil { - b.SetClient(client) + b.SetClient(client, name) } } return client, err diff --git a/backend_test.go b/backend_test.go index 7d600c7..5ae0c24 100644 --- a/backend_test.go +++ b/backend_test.go @@ -1,7 +1,7 @@ package gitlab_test import ( - "reflect" + "fmt" "testing" "github.com/stretchr/testify/require" @@ -16,13 +16,12 @@ func TestBackend(t *testing.T) { b, _, err = getBackend(ctx) require.NoError(t, err) require.NotNil(t, b) - fv := reflect.ValueOf(b).Elem().FieldByName("client") - require.True(t, fv.IsNil()) - b.SetClient(newInMemoryClient(true)) - require.False(t, fv.IsNil()) - b.Invalidate(ctx, gitlab.PathConfigStorage) - require.True(t, fv.IsNil()) - b.SetClient(newInMemoryClient(true)) - require.False(t, fv.IsNil()) + require.Nil(t, b.GetClient(gitlab.DefaultConfigName)) + b.SetClient(newInMemoryClient(true), gitlab.DefaultConfigName) + require.NotNil(t, b.GetClient(gitlab.DefaultConfigName)) + b.Invalidate(ctx, fmt.Sprintf("%s/%s", gitlab.PathConfigStorage, gitlab.DefaultConfigName)) + require.Nil(t, b.GetClient(gitlab.DefaultConfigName)) + b.SetClient(newInMemoryClient(true), gitlab.DefaultConfigName) + require.NotNil(t, b.GetClient(gitlab.DefaultConfigName)) require.EqualValues(t, gitlab.Version, b.PluginVersion().Version) } diff --git a/defs.go b/defs.go index 1c2100e..b9f4898 100644 --- a/defs.go +++ b/defs.go @@ -27,6 +27,7 @@ const ( DefaultAutoRotateBeforeMaxTTL = 730 * time.Hour ctxKeyHttpClient = contextKey("vpsg-ctx-key-http-client") ctxKeyGitlabClient = contextKey("vpsg-ctx-key-gitlab-client") + DefaultConfigName = "default" ) func HttpClientNewContext(ctx context.Context, httpClient *http.Client) context.Context { diff --git a/entry_config.go b/entry_config.go index c17f4cc..6fe32cf 100644 --- a/entry_config.go +++ b/entry_config.go @@ -16,13 +16,14 @@ import ( type EntryConfig struct { TokenId int `json:"token_id" yaml:"token_id" mapstructure:"token_id"` BaseURL string `json:"base_url" structs:"base_url" mapstructure:"base_url"` - Token string `json:"token" structs:"token" mapstructure:"token" validate:"min=10,max=40"` + Token string `json:"token" structs:"token" mapstructure:"token"` AutoRotateToken bool `json:"auto_rotate_token" structs:"auto_rotate_token" mapstructure:"auto_rotate_token"` AutoRotateBefore time.Duration `json:"auto_rotate_before" structs:"auto_rotate_before" mapstructure:"auto_rotate_before"` TokenCreatedAt time.Time `json:"token_created_at" structs:"token_created_at" mapstructure:"token_created_at"` TokenExpiresAt time.Time `json:"token_expires_at" structs:"token_expires_at" mapstructure:"token_expires_at"` Scopes []string `json:"scopes" structs:"scopes" mapstructure:"scopes"` - Type Type `json:"type" structs:"type" mapstructure:"type" validate:"gitlab-type"` + Type Type `json:"type" structs:"type" mapstructure:"type"` + Name string `json:"name" structs:"name" mapstructure:"name"` } func (e *EntryConfig) Merge(data *framework.FieldData) (warnings []string, changes map[string]string, err error) { @@ -167,15 +168,16 @@ func (e *EntryConfig) LogicalResponseData() map[string]any { "token_sha1_hash": fmt.Sprintf("%x", sha1.Sum([]byte(e.Token))), "scopes": strings.Join(e.Scopes, ", "), "type": e.Type.String(), + "name": e.Name, } } -func getConfig(ctx context.Context, s logical.Storage) (cfg *EntryConfig, err error) { +func getConfig(ctx context.Context, s logical.Storage, name string) (cfg *EntryConfig, err error) { if s == nil { return nil, fmt.Errorf("%w: local.Storage", ErrNilValue) } var entry *logical.StorageEntry - if entry, err = s.Get(ctx, PathConfigStorage); err == nil { + if entry, err = s.Get(ctx, fmt.Sprintf("%s/%s", PathConfigStorage, name)); err == nil { if entry == nil { return nil, nil } @@ -187,7 +189,7 @@ func getConfig(ctx context.Context, s logical.Storage) (cfg *EntryConfig, err er func saveConfig(ctx context.Context, config EntryConfig, s logical.Storage) (err error) { var storageEntry *logical.StorageEntry - if storageEntry, err = logical.StorageEntryJSON(PathConfigStorage, config); err == nil { + if storageEntry, err = logical.StorageEntryJSON(fmt.Sprintf("%s/%s", PathConfigStorage, config.Name), config); err == nil { err = s.Put(ctx, storageEntry) } return err diff --git a/entry_role.go b/entry_role.go index 4687e0b..a5b086e 100644 --- a/entry_role.go +++ b/entry_role.go @@ -17,7 +17,7 @@ type EntryRole struct { AccessLevel AccessLevel `json:"access_level" structs:"access_level" mapstructure:"access_level,omitempty"` TokenType TokenType `json:"token_type" structs:"token_type" mapstructure:"token_type"` GitlabRevokesTokens bool `json:"gitlab_revokes_token" structs:"gitlab_revokes_token" mapstructure:"gitlab_revokes_token"` - Config string `json:"config" structs:"config" mapstructure:"config"` + ConfigName string `json:"config_name" structs:"config_name" mapstructure:"config_name"` } func (e EntryRole) LogicalResponseData() map[string]any { @@ -30,7 +30,7 @@ func (e EntryRole) LogicalResponseData() map[string]any { "ttl": int64(e.TTL / time.Second), "token_type": e.TokenType.String(), "gitlab_revokes_token": e.GitlabRevokesTokens, - "config": e.Config, + "config_name": e.ConfigName, } } diff --git a/entry_token.go b/entry_token.go index 0c693c0..9779177 100644 --- a/entry_token.go +++ b/entry_token.go @@ -18,6 +18,7 @@ type EntryToken struct { Scopes []string `json:"scopes"` AccessLevel AccessLevel `json:"access_level"` // not used for personal access tokens RoleName string `json:"role_name"` + ConfigName string `json:"config_name"` GitlabRevokesToken bool `json:"gitlab_revokes_token"` } @@ -43,6 +44,7 @@ func (e EntryToken) SecretResponse() (map[string]any, map[string]any) { "scopes": e.Scopes, "access_level": e.AccessLevel.String(), "role_name": e.RoleName, + "config_name": e.ConfigName, "gitlab_revokes_token": strconv.FormatBool(e.GitlabRevokesToken), } } diff --git a/gitlab_client.go b/gitlab_client.go index 533bde8..41b927d 100644 --- a/gitlab_client.go +++ b/gitlab_client.go @@ -80,23 +80,22 @@ func (gc *gitlabClient) CreateGroupServiceAccountAccessToken(path string, groupI ExpiresAt: (*g.ISOTime)(&expiresAt), Scopes: &scopes, }) - if err != nil { - return nil, err + if err == nil { + et = &EntryToken{ + TokenID: at.ID, + UserID: userId, + ParentID: groupId, + Path: path, + Name: name, + Token: at.Token, + TokenType: TokenTypeGroupServiceAccount, + CreatedAt: at.CreatedAt, + ExpiresAt: (*time.Time)(at.ExpiresAt), + Scopes: scopes, + AccessLevel: AccessLevelUnknown, + } } - et = &EntryToken{ - TokenID: at.ID, - UserID: userId, - ParentID: groupId, - Path: path, - Name: name, - Token: at.Token, - TokenType: TokenTypeGroupServiceAccount, - CreatedAt: at.CreatedAt, - ExpiresAt: (*time.Time)(at.ExpiresAt), - Scopes: scopes, - AccessLevel: AccessLevelUnknown, - } - return et, nil + return et, err } func (gc *gitlabClient) CreateUserServiceAccountAccessToken(username string, userId int, name string, expiresAt time.Time, scopes []string) (et *EntryToken, err error) { diff --git a/helpers_test.go b/helpers_test.go index 197a548..02978d6 100644 --- a/helpers_test.go +++ b/helpers_test.go @@ -103,20 +103,29 @@ func getBackendWithEvents(ctx context.Context) (*gitlab.Backend, logical.Storage return b.(*gitlab.Backend), config.StorageView, events, nil } -func writeBackendConfig(ctx context.Context, b *gitlab.Backend, l logical.Storage, config map[string]any) error { +func writeBackendConfigWithName(ctx context.Context, b *gitlab.Backend, l logical.Storage, config map[string]any, name string) error { var _, err = b.HandleRequest(ctx, &logical.Request{ Operation: logical.UpdateOperation, - Path: gitlab.PathConfigStorage, Storage: l, + Path: fmt.Sprintf("%s/%s", gitlab.PathConfigStorage, cmp.Or(name, gitlab.DefaultConfigName)), Storage: l, Data: config, }) return err } +func writeBackendConfig(ctx context.Context, b *gitlab.Backend, l logical.Storage, config map[string]any) error { + return writeBackendConfigWithName(ctx, b, l, config, gitlab.DefaultConfigName) +} + func getBackendWithEventsAndConfig(ctx context.Context, config map[string]any) (*gitlab.Backend, logical.Storage, *mockEventsSender, error) { var b, storage, events, _ = getBackendWithEvents(ctx) return b, storage, events, writeBackendConfig(ctx, b, storage, config) } +func getBackendWithEventsAndConfigName(ctx context.Context, config map[string]any, name string) (*gitlab.Backend, logical.Storage, *mockEventsSender, error) { + var b, storage, events, _ = getBackendWithEvents(ctx) + return b, storage, events, writeBackendConfigWithName(ctx, b, storage, config, name) +} + func getBackendWithConfig(ctx context.Context, config map[string]any) (*gitlab.Backend, logical.Storage, error) { var b, storage, _, _ = getBackendWithEvents(ctx) return b, storage, writeBackendConfig(ctx, b, storage, config) diff --git a/path_config.go b/path_config.go index 2f3983e..601dccd 100644 --- a/path_config.go +++ b/path_config.go @@ -64,6 +64,14 @@ var ( Name: "Auto Rotate Before", }, }, + "config_name": { + Type: framework.TypeString, + Description: "Config name", + Required: true, + DisplayAttrs: &framework.DisplayAttributes{ + Name: "Config name", + }, + }, } ) @@ -71,17 +79,18 @@ func (b *Backend) pathConfigDelete(ctx context.Context, req *logical.Request, da b.lockClientMutex.Lock() defer b.lockClientMutex.Unlock() var err error + var name = data.Get("config_name").(string) - if config, err := getConfig(ctx, req.Storage); err == nil { + if config, err := getConfig(ctx, req.Storage, name); err == nil { if config == nil { return logical.ErrorResponse(ErrBackendNotConfigured.Error()), nil } - if err = req.Storage.Delete(ctx, PathConfigStorage); err == nil { + if err = req.Storage.Delete(ctx, fmt.Sprintf("%s/%s", PathConfigStorage, name)); err == nil { event(ctx, b.Backend, "config-delete", map[string]string{ - "path": "config", + "path": fmt.Sprintf("%s/%s", PathConfigStorage, name), }) - b.SetClient(nil) + b.SetClient(nil, name) } } @@ -92,8 +101,9 @@ func (b *Backend) pathConfigRead(ctx context.Context, req *logical.Request, data b.lockClientMutex.RLock() defer b.lockClientMutex.RUnlock() + var name = data.Get("config_name").(string) var config *EntryConfig - if config, err = getConfig(ctx, req.Storage); err == nil { + if config, err = getConfig(ctx, req.Storage, name); err == nil { if config == nil { return logical.ErrorResponse(ErrBackendNotConfigured.Error()), nil } @@ -105,10 +115,11 @@ func (b *Backend) pathConfigRead(ctx context.Context, req *logical.Request, data } func (b *Backend) pathConfigPatch(ctx context.Context, req *logical.Request, data *framework.FieldData) (lResp *logical.Response, err error) { + var name = data.Get("config_name").(string) var warnings []string var changes map[string]string var config *EntryConfig - config, err = getConfig(ctx, req.Storage) + config, err = getConfig(ctx, req.Storage, name) if err != nil { return nil, err } @@ -132,7 +143,7 @@ func (b *Backend) pathConfigPatch(ctx context.Context, req *logical.Request, dat if err = saveConfig(ctx, *config, req.Storage); err == nil { lrd := config.LogicalResponseData() event(ctx, b.Backend, "config-patch", changes) - b.SetClient(nil) + b.SetClient(nil, name) b.Logger().Debug("Patched config", "lrd", lrd, "warnings", warnings) lResp = &logical.Response{Data: lrd, Warnings: warnings} } @@ -147,7 +158,7 @@ func (b *Backend) updateConfigClientInfo(ctx context.Context, config *EntryConfi httpClient, _ = HttpClientFromContext(ctx) if client, _ = GitlabClientFromContext(ctx); client == nil { if client, err = NewGitlabClient(config, httpClient, b.Logger()); err == nil { - b.SetClient(client) + b.SetClient(client, config.Name) } } @@ -165,11 +176,13 @@ func (b *Backend) updateConfigClientInfo(ctx context.Context, config *EntryConfi } func (b *Backend) pathConfigWrite(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { + var name = data.Get("config_name").(string) var config = new(EntryConfig) var warnings, err = config.UpdateFromFieldData(data) if err != nil { return nil, err } + config.Name = name if _, err = b.updateConfigClientInfo(ctx, config); err != nil { return nil, err @@ -181,7 +194,7 @@ func (b *Backend) pathConfigWrite(ctx context.Context, req *logical.Request, dat if err = saveConfig(ctx, *config, req.Storage); err == nil { event(ctx, b.Backend, "config-write", map[string]string{ - "path": "config", + "path": fmt.Sprintf("%s/%s", PathConfigStorage, name), "auto_rotate_token": strconv.FormatBool(config.AutoRotateToken), "auto_rotate_before": config.AutoRotateBefore.String(), "base_url": config.BaseURL, @@ -190,9 +203,10 @@ func (b *Backend) pathConfigWrite(ctx context.Context, req *logical.Request, dat "expires_at": config.TokenExpiresAt.Format(time.RFC3339), "scopes": strings.Join(config.Scopes, ", "), "type": config.Type.String(), + "config_name": config.Name, }) - b.SetClient(nil) + b.SetClient(nil, name) lrd := config.LogicalResponseData() b.Logger().Debug("Wrote new config", "lrd", lrd, "warnings", warnings) lResp = &logical.Response{Data: lrd, Warnings: warnings} @@ -205,7 +219,7 @@ func pathConfig(b *Backend) *framework.Path { return &framework.Path{ HelpSynopsis: strings.TrimSpace(pathConfigHelpSynopsis), HelpDescription: strings.TrimSpace(pathConfigHelpDescription), - Pattern: fmt.Sprintf("%s$", PathConfigStorage), + Pattern: fmt.Sprintf("%s/%s", PathConfigStorage, framework.GenericNameWithAtRegex("config_name")), Fields: FieldSchemaConfig, DisplayAttrs: &framework.DisplayAttributes{ OperationPrefix: operationPrefixGitlabAccessTokens, diff --git a/path_config_list.go b/path_config_list.go new file mode 100644 index 0000000..bd02fbd --- /dev/null +++ b/path_config_list.go @@ -0,0 +1,58 @@ +package gitlab + +import ( + "context" + "fmt" + "net/http" + "strings" + + "github.com/hashicorp/vault/sdk/framework" + "github.com/hashicorp/vault/sdk/logical" +) + +const ( + pathListConfigHelpSyn = `Lists existing configs` + pathListConfigHelpDesc = ` +This path allows you to list all available configurations that have been set up within the GitLab Access Tokens Backend. +These configurations typically include credentials, base URLs, and other settings required for managing access tokens +across different GitLab environments.` +) + +func pathListConfig(b *Backend) *framework.Path { + return &framework.Path{ + HelpSynopsis: strings.TrimSpace(pathListConfigHelpSyn), + HelpDescription: strings.TrimSpace(pathListConfigHelpDesc), + Pattern: fmt.Sprintf("%s?/?$", PathConfigStorage), + DisplayAttrs: &framework.DisplayAttributes{ + OperationPrefix: operationPrefixGitlabAccessTokens, + OperationSuffix: "config", + }, + Operations: map[logical.Operation]framework.OperationHandler{ + logical.ListOperation: &framework.PathOperation{ + Callback: b.pathConfigList, + DisplayAttrs: &framework.DisplayAttributes{ + OperationVerb: "list", + }, + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: http.StatusText(http.StatusOK), + Fields: map[string]*framework.FieldSchema{ + "config_name": FieldSchemaRoles["config_name"], + }, + }}, + }, + }, + }, + } +} + +func (b *Backend) pathConfigList(ctx context.Context, req *logical.Request, data *framework.FieldData) (lResp *logical.Response, err error) { + var configs []string + configs, err = req.Storage.List(ctx, fmt.Sprintf("%s/", PathConfigStorage)) + lResp = logical.ErrorResponse("Error listing configs") + if err == nil { + lResp = logical.ListResponse(configs) + } + b.Logger().Debug("Available configs input the system", "configs", configs) + return lResp, err +} diff --git a/path_config_list_test.go b/path_config_list_test.go new file mode 100644 index 0000000..27a5932 --- /dev/null +++ b/path_config_list_test.go @@ -0,0 +1,117 @@ +package gitlab_test + +import ( + "cmp" + "fmt" + "os" + "slices" + "testing" + + "github.com/hashicorp/vault/sdk/logical" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + gitlab "github.com/ilijamt/vault-plugin-secrets-gitlab" +) + +func TestPathConfigList(t *testing.T) { + t.Run("empty list", func(t *testing.T) { + ctx := getCtxGitlabClient(t) + var b, l, err = getBackend(ctx) + require.NoError(t, err) + resp, err := b.HandleRequest(ctx, &logical.Request{ + Operation: logical.ListOperation, + Path: gitlab.PathConfigStorage, Storage: l, + }) + require.NoError(t, err) + require.NotNil(t, resp) + require.NoError(t, resp.Error()) + assert.Empty(t, resp.Data) + }) + + t.Run("multiple configs", func(t *testing.T) { + ctx := getCtxGitlabClient(t) + var b, l, events, err = getBackendWithEventsAndConfigName(ctx, + map[string]any{ + "token": "glpat-secret-random-token", + "base_url": cmp.Or(os.Getenv("GITLAB_URL"), "http://localhost:8080/"), + "type": gitlab.TypeSaaS.String(), + }, + gitlab.DefaultConfigName, + ) + require.NoError(t, err) + require.NotNil(t, events) + require.NotNil(t, b) + require.NotNil(t, l) + + require.NoError(t, + writeBackendConfigWithName(ctx, b, l, + map[string]any{ + "token": "glpat-secret-admin-token", + "base_url": cmp.Or(os.Getenv("GITLAB_URL"), "http://localhost:8080/"), + "type": gitlab.TypeSelfManaged.String(), + }, + "admin", + ), + ) + + require.NoError(t, + writeBackendConfigWithName(ctx, b, l, + map[string]any{ + "token": "glpat-secret-normal-token", + "base_url": cmp.Or(os.Getenv("GITLAB_URL"), "http://localhost:8080/"), + "type": gitlab.TypeDedicated.String(), + }, + "normal", + ), + ) + + resp, err := b.HandleRequest(ctx, &logical.Request{ + Operation: logical.ListOperation, + Path: gitlab.PathConfigStorage, Storage: l, + }) + require.NoError(t, err) + require.NotNil(t, resp) + require.NoError(t, resp.Error()) + require.NotNil(t, resp.Data["keys"]) + keysResponse := resp.Data["keys"].([]string) + slices.Sort(keysResponse) + keysExpected := []string{gitlab.DefaultConfigName, "admin", "normal"} + slices.Sort(keysExpected) + require.EqualValues(t, keysExpected, keysResponse) + require.Len(t, keysResponse, 3) + + events.expectEvents(t, []expectedEvent{ + {eventType: "gitlab/config-write"}, + {eventType: "gitlab/config-write"}, + {eventType: "gitlab/config-write"}, + }) + + resp, err = b.HandleRequest(ctx, &logical.Request{ + Operation: logical.ReadOperation, + Path: fmt.Sprintf("%s/%s", gitlab.PathConfigStorage, gitlab.DefaultConfigName), Storage: l, + }) + require.NoError(t, err) + require.NotNil(t, resp) + require.NotEmpty(t, resp.Data) + require.EqualValues(t, gitlab.TypeSaaS.String(), resp.Data["type"]) + + resp, err = b.HandleRequest(ctx, &logical.Request{ + Operation: logical.ReadOperation, + Path: fmt.Sprintf("%s/normal", gitlab.PathConfigStorage), Storage: l, + }) + require.NoError(t, err) + require.NotNil(t, resp) + require.NotEmpty(t, resp.Data) + require.EqualValues(t, gitlab.TypeDedicated.String(), resp.Data["type"]) + + resp, err = b.HandleRequest(ctx, &logical.Request{ + Operation: logical.ReadOperation, + Path: fmt.Sprintf("%s/admin", gitlab.PathConfigStorage), Storage: l, + }) + require.NoError(t, err) + require.NotNil(t, resp) + require.NotEmpty(t, resp.Data) + require.EqualValues(t, gitlab.TypeSelfManaged.String(), resp.Data["type"]) + }) +} diff --git a/path_config_rotate.go b/path_config_rotate.go index 18b47f8..9c3a071 100644 --- a/path_config_rotate.go +++ b/path_config_rotate.go @@ -1,6 +1,7 @@ package gitlab import ( + "cmp" "context" "fmt" "strconv" @@ -11,11 +12,19 @@ import ( "github.com/hashicorp/vault/sdk/logical" ) +const pathConfigRotateHelpSynopsis = `Rotate the gitlab token for this configuration.` + +const pathConfigRotateHelpDescription = ` +This endpoint allows you to rotate the GitLab token associated with your current configuration. When you invoke this +operation, Vault securely generates a new token and replaces the existing one without revealing the new token to you. +The newly generated token is securely stored within Vault's internal storage, ensuring that only Vault has +access to it for future use when interacting with the GitLab API.'` + func pathConfigTokenRotate(b *Backend) *framework.Path { return &framework.Path{ - HelpSynopsis: strings.TrimSpace(pathConfigHelpSynopsis), - HelpDescription: strings.TrimSpace(pathConfigHelpDescription), - Pattern: fmt.Sprintf("%s/rotate$", PathConfigStorage), + HelpSynopsis: strings.TrimSpace(pathConfigRotateHelpSynopsis), + HelpDescription: strings.TrimSpace(pathConfigRotateHelpDescription), + Pattern: fmt.Sprintf("%s/%s/rotate$", PathConfigStorage, framework.GenericNameRegex("config_name")), Fields: FieldSchemaConfig, DisplayAttrs: &framework.DisplayAttributes{ OperationPrefix: operationPrefixGitlabAccessTokens, @@ -39,18 +48,24 @@ func (b *Backend) checkAndRotateConfigToken(ctx context.Context, request *logica return nil } - _, err = b.pathConfigTokenRotate(ctx, request, &framework.FieldData{}) + _, err = b.pathConfigTokenRotate(ctx, request, &framework.FieldData{ + Raw: map[string]interface{}{ + "config_name": cmp.Or(config.Name, TypeConfigDefault), + }, + Schema: FieldSchemaConfig, + }) return err } func (b *Backend) pathConfigTokenRotate(ctx context.Context, request *logical.Request, data *framework.FieldData) (*logical.Response, error) { + var name = data.Get("config_name").(string) b.Logger().Debug("Running pathConfigTokenRotate") var config *EntryConfig var client Client var err error b.lockClientMutex.RLock() - if config, err = getConfig(ctx, request.Storage); err != nil { + if config, err = getConfig(ctx, request.Storage, name); err != nil { b.lockClientMutex.RUnlock() b.Logger().Error("Failed to fetch configuration", "error", err.Error()) return nil, err @@ -62,7 +77,7 @@ func (b *Backend) pathConfigTokenRotate(ctx context.Context, request *logical.Re return logical.ErrorResponse(ErrBackendNotConfigured.Error()), nil } - if client, err = b.getClient(ctx, request.Storage); err != nil { + if client, err = b.getClient(ctx, request.Storage, name); err != nil { return nil, err } @@ -91,7 +106,7 @@ func (b *Backend) pathConfigTokenRotate(ctx context.Context, request *logical.Re } event(ctx, b.Backend, "config-token-rotate", map[string]string{ - "path": "config", + "path": fmt.Sprintf("%s/%s", PathConfigStorage, name), "expires_at": entryToken.ExpiresAt.Format(time.RFC3339), "created_at": entryToken.CreatedAt.Format(time.RFC3339), "scopes": strings.Join(entryToken.Scopes, ", "), @@ -99,6 +114,6 @@ func (b *Backend) pathConfigTokenRotate(ctx context.Context, request *logical.Re "name": entryToken.Name, }) - b.SetClient(nil) + b.SetClient(nil, name) return config.Response(), nil } diff --git a/path_config_rotate_test.go b/path_config_rotate_test.go index ea42412..c2de920 100644 --- a/path_config_rotate_test.go +++ b/path_config_rotate_test.go @@ -17,7 +17,7 @@ func TestPathConfigRotate(t *testing.T) { require.NoError(t, err) resp, err := b.HandleRequest(ctx, &logical.Request{ Operation: logical.UpdateOperation, - Path: fmt.Sprintf("%s/rotate", gitlab.PathConfigStorage), Storage: l, + Path: fmt.Sprintf("%s/%s/rotate", gitlab.PathConfigStorage, gitlab.DefaultConfigName), Storage: l, }) require.NoError(t, err) require.NotNil(t, resp) diff --git a/path_config_test.go b/path_config_test.go index 5c5aeaa..0156798 100644 --- a/path_config_test.go +++ b/path_config_test.go @@ -2,6 +2,7 @@ package gitlab_test import ( "context" + "fmt" "testing" "github.com/hashicorp/go-multierror" @@ -19,7 +20,7 @@ func TestPathConfig(t *testing.T) { require.NoError(t, err) resp, err := b.HandleRequest(ctx, &logical.Request{ Operation: logical.ReadOperation, - Path: gitlab.PathConfigStorage, Storage: l, + Path: fmt.Sprintf("%s/%s", gitlab.PathConfigStorage, gitlab.DefaultConfigName), Storage: l, }) require.NoError(t, err) require.NotNil(t, resp) @@ -34,7 +35,7 @@ func TestPathConfig(t *testing.T) { resp, err := b.HandleRequest(ctx, &logical.Request{ Operation: logical.DeleteOperation, - Path: gitlab.PathConfigStorage, Storage: l, + Path: fmt.Sprintf("%s/%s", gitlab.PathConfigStorage, gitlab.DefaultConfigName), Storage: l, }) require.NoError(t, err) require.NotNil(t, resp) @@ -52,7 +53,7 @@ func TestPathConfig(t *testing.T) { resp, err := b.HandleRequest(ctx, &logical.Request{ Operation: logical.UpdateOperation, - Path: gitlab.PathConfigStorage, Storage: l, + Path: fmt.Sprintf("%s/%s", gitlab.PathConfigStorage, gitlab.DefaultConfigName), Storage: l, Data: map[string]any{ "token": "glpat-secret-random-token", "base_url": url, @@ -66,7 +67,7 @@ func TestPathConfig(t *testing.T) { resp, err = b.HandleRequest(ctx, &logical.Request{ Operation: logical.ReadOperation, - Path: gitlab.PathConfigStorage, Storage: l, + Path: fmt.Sprintf("%s/%s", gitlab.PathConfigStorage, gitlab.DefaultConfigName), Storage: l, }) require.NoError(t, err) @@ -78,7 +79,7 @@ func TestPathConfig(t *testing.T) { resp, err = b.HandleRequest(ctx, &logical.Request{ Operation: logical.DeleteOperation, - Path: gitlab.PathConfigStorage, Storage: l, + Path: fmt.Sprintf("%s/%s", gitlab.PathConfigStorage, gitlab.DefaultConfigName), Storage: l, }) require.NoError(t, err) require.Nil(t, resp) @@ -86,7 +87,7 @@ func TestPathConfig(t *testing.T) { resp, err = b.HandleRequest(ctx, &logical.Request{ Operation: logical.ReadOperation, - Path: gitlab.PathConfigStorage, Storage: l, + Path: fmt.Sprintf("%s/%s", gitlab.PathConfigStorage, gitlab.DefaultConfigName), Storage: l, }) require.NoError(t, err) require.NotNil(t, resp) @@ -107,7 +108,7 @@ func TestPathConfig(t *testing.T) { resp, err := b.HandleRequest(ctx, &logical.Request{ Operation: logical.UpdateOperation, - Path: gitlab.PathConfigStorage, Storage: l, + Path: fmt.Sprintf("%s/%s", gitlab.PathConfigStorage, gitlab.DefaultConfigName), Storage: l, Data: map[string]any{ "token": "invalid-token", "base_url": url, @@ -128,7 +129,7 @@ func TestPathConfig(t *testing.T) { resp, err := b.HandleRequest(ctx, &logical.Request{ Operation: logical.UpdateOperation, - Path: gitlab.PathConfigStorage, Storage: l, + Path: fmt.Sprintf("%s/%s", gitlab.PathConfigStorage, gitlab.DefaultConfigName), Storage: l, Data: map[string]any{}, }) @@ -149,7 +150,7 @@ func TestPathConfig(t *testing.T) { resp, err := b.HandleRequest(ctx, &logical.Request{ Operation: logical.PatchOperation, - Path: gitlab.PathConfigStorage, Storage: nil, + Path: fmt.Sprintf("%s/%s", gitlab.PathConfigStorage, gitlab.DefaultConfigName), Storage: nil, Data: map[string]any{ "token": "glpat-secret-random-token", "base_url": url, @@ -170,7 +171,7 @@ func TestPathConfig(t *testing.T) { resp, err := b.HandleRequest(ctx, &logical.Request{ Operation: logical.PatchOperation, - Path: gitlab.PathConfigStorage, Storage: l, + Path: fmt.Sprintf("%s/%s", gitlab.PathConfigStorage, gitlab.DefaultConfigName), Storage: l, Data: map[string]any{ "token": "glpat-secret-random-token", "base_url": url, @@ -186,13 +187,14 @@ func TestPathConfig(t *testing.T) { t.Run("patch a config", func(t *testing.T) { httpClient, url := getClient(t) ctx := gitlab.HttpClientNewContext(context.Background(), httpClient) + var path = fmt.Sprintf("%s/%s", gitlab.PathConfigStorage, gitlab.DefaultConfigName) b, l, events, err := getBackendWithEvents(ctx) require.NoError(t, err) resp, err := b.HandleRequest(ctx, &logical.Request{ Operation: logical.UpdateOperation, - Path: gitlab.PathConfigStorage, Storage: l, + Path: path, Storage: l, Data: map[string]any{ "token": "glpat-secret-random-token", "base_url": url, @@ -206,7 +208,7 @@ func TestPathConfig(t *testing.T) { resp, err = b.HandleRequest(ctx, &logical.Request{ Operation: logical.ReadOperation, - Path: gitlab.PathConfigStorage, Storage: l, + Path: path, Storage: l, }) require.NoError(t, err) @@ -215,11 +217,11 @@ func TestPathConfig(t *testing.T) { tokenOriginalSha1Hash := resp.Data["token_sha1_hash"].(string) require.NotEmpty(t, tokenOriginalSha1Hash) require.Equal(t, gitlab.TypeSelfManaged.String(), resp.Data["type"]) - require.NotNil(t, b.GetClient().GitlabClient()) + require.NotNil(t, b.GetClient(gitlab.DefaultConfigName).GitlabClient()) resp, err = b.HandleRequest(ctx, &logical.Request{ Operation: logical.PatchOperation, - Path: gitlab.PathConfigStorage, Storage: l, + Path: path, Storage: l, Data: map[string]interface{}{ "type": gitlab.TypeSaaS.String(), "token": "glpat-secret-admin-token", @@ -233,7 +235,7 @@ func TestPathConfig(t *testing.T) { require.NotEqual(t, tokenOriginalSha1Hash, tokenNewSha1Hash) require.Equal(t, gitlab.TypeSaaS.String(), resp.Data["type"]) - require.NotNil(t, b.GetClient().GitlabClient()) + require.NotNil(t, b.GetClient(gitlab.DefaultConfigName).GitlabClient()) events.expectEvents(t, []expectedEvent{ {eventType: "gitlab/config-write"}, diff --git a/path_config_token_autorotate_test.go b/path_config_token_autorotate_test.go index f31a5a4..1439db4 100644 --- a/path_config_token_autorotate_test.go +++ b/path_config_token_autorotate_test.go @@ -1,6 +1,7 @@ package gitlab_test import ( + "fmt" "testing" "time" @@ -18,7 +19,7 @@ func TestPathConfig_AutoRotate(t *testing.T) { require.NoError(t, err) resp, err := b.HandleRequest(ctx, &logical.Request{ Operation: logical.UpdateOperation, - Path: gitlab.PathConfigStorage, Storage: l, + Path: fmt.Sprintf("%s/%s", gitlab.PathConfigStorage, gitlab.DefaultConfigName), Storage: l, Data: map[string]any{ "token": "glpat-secret-random-token", "base_url": url, @@ -36,7 +37,7 @@ func TestPathConfig_AutoRotate(t *testing.T) { require.NoError(t, err) resp, err := b.HandleRequest(ctx, &logical.Request{ Operation: logical.UpdateOperation, - Path: gitlab.PathConfigStorage, Storage: l, + Path: fmt.Sprintf("%s/%s", gitlab.PathConfigStorage, gitlab.DefaultConfigName), Storage: l, Data: map[string]any{ "token": "glpat-secret-random-token", "base_url": url, @@ -55,7 +56,7 @@ func TestPathConfig_AutoRotate(t *testing.T) { require.NoError(t, err) resp, err := b.HandleRequest(ctx, &logical.Request{ Operation: logical.UpdateOperation, - Path: gitlab.PathConfigStorage, Storage: l, + Path: fmt.Sprintf("%s/%s", gitlab.PathConfigStorage, gitlab.DefaultConfigName), Storage: l, Data: map[string]any{ "token": "glpat-secret-random-token", "base_url": url, @@ -73,7 +74,7 @@ func TestPathConfig_AutoRotate(t *testing.T) { require.NoError(t, err) resp, err := b.HandleRequest(ctx, &logical.Request{ Operation: logical.UpdateOperation, - Path: gitlab.PathConfigStorage, Storage: l, + Path: fmt.Sprintf("%s/%s", gitlab.PathConfigStorage, gitlab.DefaultConfigName), Storage: l, Data: map[string]any{ "token": "glpat-secret-random-token", "base_url": url, @@ -92,7 +93,7 @@ func TestPathConfig_AutoRotate(t *testing.T) { require.NoError(t, err) resp, err := b.HandleRequest(ctx, &logical.Request{ Operation: logical.UpdateOperation, - Path: gitlab.PathConfigStorage, Storage: l, + Path: fmt.Sprintf("%s/%s", gitlab.PathConfigStorage, gitlab.DefaultConfigName), Storage: l, Data: map[string]any{ "token": "glpat-secret-random-token", "base_url": url, @@ -110,7 +111,7 @@ func TestPathConfig_AutoRotate(t *testing.T) { require.NoError(t, err) resp, err := b.HandleRequest(ctx, &logical.Request{ Operation: logical.UpdateOperation, - Path: gitlab.PathConfigStorage, Storage: l, + Path: fmt.Sprintf("%s/%s", gitlab.PathConfigStorage, gitlab.DefaultConfigName), Storage: l, Data: map[string]any{ "token": "glpat-secret-random-token", "base_url": url, @@ -128,7 +129,7 @@ func TestPathConfig_AutoRotate(t *testing.T) { require.NoError(t, err) resp, err := b.HandleRequest(ctx, &logical.Request{ Operation: logical.UpdateOperation, - Path: gitlab.PathConfigStorage, Storage: l, + Path: fmt.Sprintf("%s/%s", gitlab.PathConfigStorage, gitlab.DefaultConfigName), Storage: l, Data: map[string]any{ "token": "glpat-secret-random-token", "base_url": url, @@ -162,7 +163,7 @@ func TestPathConfig_AutoRotateToken(t *testing.T) { }) require.NoError(t, err) - b.SetClient(newInMemoryClient(true)) + b.SetClient(newInMemoryClient(true), gitlab.DefaultConfigName) err = b.PeriodicFunc(ctx, &logical.Request{Storage: l}) require.NoError(t, err) }) @@ -181,11 +182,11 @@ func TestPathConfig_AutoRotateToken(t *testing.T) { require.NoError(t, err) client.rotateMainToken.Token = "new token" - b.SetClient(client) + b.SetClient(client, gitlab.DefaultConfigName) resp, err := b.HandleRequest(ctx, &logical.Request{ Operation: logical.ReadOperation, - Path: gitlab.PathConfigStorage, Storage: l, + Path: fmt.Sprintf("%s/%s", gitlab.PathConfigStorage, gitlab.DefaultConfigName), Storage: l, }) require.NoError(t, err) require.NotNil(t, resp) @@ -199,7 +200,7 @@ func TestPathConfig_AutoRotateToken(t *testing.T) { resp, err = b.HandleRequest(ctx, &logical.Request{ Operation: logical.ReadOperation, - Path: gitlab.PathConfigStorage, Storage: l, + Path: fmt.Sprintf("%s/%s", gitlab.PathConfigStorage, gitlab.DefaultConfigName), Storage: l, }) require.NoError(t, err) require.NotNil(t, resp) @@ -233,11 +234,11 @@ func TestPathConfig_AutoRotateToken(t *testing.T) { var expiresAt = time.Now().Add(100 * 24 * time.Hour) client.mainTokenInfo.ExpiresAt = &expiresAt - b.SetClient(client) + b.SetClient(client, gitlab.DefaultConfigName) resp, err := b.HandleRequest(ctx, &logical.Request{ Operation: logical.ReadOperation, - Path: gitlab.PathConfigStorage, Storage: l, + Path: fmt.Sprintf("%s/%s", gitlab.PathConfigStorage, gitlab.DefaultConfigName), Storage: l, }) require.NoError(t, err) require.NotNil(t, resp) @@ -251,7 +252,7 @@ func TestPathConfig_AutoRotateToken(t *testing.T) { resp, err = b.HandleRequest(ctx, &logical.Request{ Operation: logical.ReadOperation, - Path: gitlab.PathConfigStorage, Storage: l, + Path: fmt.Sprintf("%s/%s", gitlab.PathConfigStorage, gitlab.DefaultConfigName), Storage: l, }) require.NoError(t, err) require.NotNil(t, resp) diff --git a/path_role.go b/path_role.go index be57641..35398f8 100644 --- a/path_role.go +++ b/path_role.go @@ -18,7 +18,7 @@ import ( const ( PathRoleStorage = "roles" - TypeConfigDefault = "default" + TypeConfigDefault = DefaultConfigName ) var ( @@ -91,7 +91,7 @@ var ( Name: "Gitlab revokes token.", }, }, - "config": { + "config_name": { Type: framework.TypeString, Default: TypeConfigDefault, Required: false, @@ -210,13 +210,13 @@ func (b *Backend) pathRolesWrite(ctx context.Context, req *logical.Request, data var warnings []string var tokenType TokenType var accessLevel AccessLevel - var configValue string + var configName = cmp.Or(data.Get("config_name").(string), TypeConfigDefault) b.lockClientMutex.RLock() defer b.lockClientMutex.RUnlock() - config, err = getConfig(ctx, req.Storage) + config, err = getConfig(ctx, req.Storage, configName) if err != nil { - return logical.ErrorResponse("missing configuration for gitlab"), err + return logical.ErrorResponse(fmt.Sprintf("missing %s configuration for gitlab", configName)), err } if config == nil { @@ -225,7 +225,6 @@ func (b *Backend) pathRolesWrite(ctx context.Context, req *logical.Request, data tokenType, _ = TokenTypeParse(data.Get("token_type").(string)) accessLevel, _ = AccessLevelParse(data.Get("access_level").(string)) - configValue = cmp.Or(data.Get("config").(string), TypeConfigDefault) var role = EntryRole{ RoleName: roleName, @@ -236,7 +235,7 @@ func (b *Backend) pathRolesWrite(ctx context.Context, req *logical.Request, data AccessLevel: accessLevel, TokenType: tokenType, GitlabRevokesTokens: data.Get("gitlab_revokes_token").(bool), - Config: configValue, + ConfigName: configName, } // validate name of the entry role @@ -249,7 +248,7 @@ func (b *Backend) pathRolesWrite(ctx context.Context, req *logical.Request, data err = multierror.Append(err, fmt.Errorf("token_type='%s', should be one of %v: %w", data.Get("token_type").(string), validTokenTypes, ErrFieldInvalidValue)) } - var skipFields = []string{"config"} + var skipFields = []string{"config_name"} // validate access level var validAccessLevels []string @@ -429,8 +428,14 @@ func pathRoles(b *Backend) *framework.Path { } const ( - pathRolesHelpSyn = `Create a role with parameters that are used to generate a project, group or personal access token.` - pathRolesHelpDesc = `This path allows you to create a role whose parameters will be used to generate a project, group or personal access access token.` + pathRolesHelpSyn = `Create a role with parameters that are used to generate a various access tokens.` + pathRolesHelpDesc = ` +This path allows you to create a role with predefined parameters that will be used to generate tokens for different +access types in GitLab. The role defines the configuration for generating project, group, personal access tokens, +user service accounts, or group service accounts.` pathListRolesHelpSyn = `Lists existing roles` - pathListRolesHelpDesc = `This path allows you to list all available roles.` + pathListRolesHelpDesc = ` +This path allows you to list all available roles that have been created within the GitLab Access Tokens Backend. +Each role defines a set of parameters, such as token permissions, scopes, and expiration settings, which are used +when generating access tokens.` ) diff --git a/path_role_test.go b/path_role_test.go index 18ac9bb..22744e7 100644 --- a/path_role_test.go +++ b/path_role_test.go @@ -197,7 +197,7 @@ func TestPathRoles(t *testing.T) { require.NotNil(t, resp) require.NoError(t, resp.Error()) require.Empty(t, resp.Warnings) - require.EqualValues(t, resp.Data["config"], gitlab.TypeConfigDefault) + require.EqualValues(t, resp.Data["config_name"], gitlab.TypeConfigDefault) }) }) @@ -220,6 +220,28 @@ func TestPathRoles(t *testing.T) { assert.EqualValues(t, 2, errorMap[gitlab.ErrFieldInvalidValue.Error()]) }) + t.Run("invalid name template", 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": "{{ . } invalid template", + "token_type": gitlab.TokenTypePersonal.String(), + "ttl": gitlab.DefaultAccessTokenMinTTL, + "scopes": gitlab.ValidPersonalTokenScopes, + "gitlab_revokes_token": false, + }, + }) + require.Error(t, err) + require.NotNil(t, resp) + require.Error(t, resp.Error()) + require.ErrorContains(t, resp.Error(), "invalid template") + }) + t.Run("Project token scopes", func(t *testing.T) { t.Run("valid scopes", func(t *testing.T) { ctx := getCtxGitlabClient(t) @@ -239,7 +261,7 @@ func TestPathRoles(t *testing.T) { }) require.NoError(t, err) require.NotNil(t, resp) - require.EqualValues(t, resp.Data["config"], gitlab.TypeConfigDefault) + require.EqualValues(t, resp.Data["config_name"], gitlab.TypeConfigDefault) }) t.Run("invalid scopes", func(t *testing.T) { @@ -284,7 +306,7 @@ func TestPathRoles(t *testing.T) { }) require.NoError(t, err) require.NotNil(t, resp) - require.EqualValues(t, resp.Data["config"], gitlab.TypeConfigDefault) + require.EqualValues(t, resp.Data["config_name"], gitlab.TypeConfigDefault) }) t.Run("invalid scopes", func(t *testing.T) { @@ -329,7 +351,7 @@ func TestPathRoles(t *testing.T) { }) require.NoError(t, err) require.NotNil(t, resp) - require.EqualValues(t, resp.Data["config"], gitlab.TypeConfigDefault) + require.EqualValues(t, resp.Data["config_name"], gitlab.TypeConfigDefault) }) t.Run("invalid scopes", func(t *testing.T) { @@ -383,7 +405,7 @@ func TestPathRoles(t *testing.T) { func() { resp, err := b.HandleRequest(ctx, &logical.Request{ Operation: logical.UpdateOperation, - Path: gitlab.PathConfigStorage, Storage: l, + Path: fmt.Sprintf("%s/%s", gitlab.PathConfigStorage, gitlab.DefaultConfigName), Storage: l, Data: defaultConfig, }) require.NoError(t, err) @@ -412,7 +434,7 @@ func TestPathRoles(t *testing.T) { require.NotNil(t, resp) require.NoError(t, resp.Error()) require.Empty(t, resp.Warnings) - require.EqualValues(t, resp.Data["config"], gitlab.TypeConfigDefault) + require.EqualValues(t, resp.Data["config_name"], gitlab.TypeConfigDefault) // read a role resp, err = b.HandleRequest(ctx, &logical.Request{ diff --git a/path_token_role.go b/path_token_role.go index 21990e0..107fcc1 100644 --- a/path_token_role.go +++ b/path_token_role.go @@ -16,8 +16,11 @@ import ( const ( pathTokenRolesHelpSyn = `Generate an access token based on the specified role` - pathTokenRolesHelpDesc = `This path allows you to generate an access token based on a predefined role. You must create a role beforehand in /roles/ path, -whose parameters are used to generate an access token based on a predefined role.` + pathTokenRolesHelpDesc = ` +This path allows you to generate an access token based on a predefined role. The role must be created beforehand in +the ^roles/(?P\w(([\w-.@]+)?\w)?)$ path, where its parameters, such as token permissions, scopes, and +expiration, are defined. When you request an access token through this path, Vault will use the predefined +role's parameters to create a new access token.` PathTokenRoleStorage = "token" ) @@ -36,11 +39,7 @@ func (b *Backend) pathTokenRoleCreate(ctx context.Context, req *logical.Request, var resp *logical.Response var err error var role *EntryRole - var roleName string - - if roleName = data.Get("role_name").(string); roleName == "" { - return logical.ErrorResponse("missing role name"), nil - } + var roleName = data.Get("role_name").(string) lock := locksutil.LockForKey(b.roleLocks, roleName) lock.RLock() @@ -73,7 +72,7 @@ func (b *Backend) pathTokenRoleCreate(ctx context.Context, req *logical.Request, _, expiresAt, _ = calculateGitlabTTL(role.TTL, startTime) - client, err = b.getClient(ctx, req.Storage) + client, err = b.getClient(ctx, req.Storage, role.ConfigName) if err != nil { return nil, err } @@ -118,6 +117,7 @@ func (b *Backend) pathTokenRoleCreate(ctx context.Context, req *logical.Request, return nil, cmp.Or(err, fmt.Errorf("%w: token is nil", ErrNilValue)) } + token.ConfigName = cmp.Or(role.ConfigName, DefaultConfigName) token.RoleName = role.RoleName token.GitlabRevokesToken = role.GitlabRevokesTokens diff --git a/path_token_role_multiple_config_test.go b/path_token_role_multiple_config_test.go new file mode 100644 index 0000000..efb65b8 --- /dev/null +++ b/path_token_role_multiple_config_test.go @@ -0,0 +1,152 @@ +package gitlab_test + +import ( + "context" + "fmt" + "testing" + + "github.com/hashicorp/vault/sdk/logical" + "github.com/stretchr/testify/require" + g "github.com/xanzy/go-gitlab" + + gitlab "github.com/ilijamt/vault-plugin-secrets-gitlab" +) + +func TestPathTokenRolesMultipleConfigs(t *testing.T) { + httpClient, gitlabUrl := getClient(t) + ctx := gitlab.HttpClientNewContext(context.Background(), httpClient) + + b, l, events, err := getBackendWithEvents(ctx) + require.NoError(t, err) + require.NoError(t, err) + require.NotNil(t, events) + require.NotNil(t, b) + require.NotNil(t, l) + + var configs = map[string]string{"root": "glpat-secret-random-token", "admin": "glpat-secret-admin-token", "normal": "glpat-secret-normal-token"} + for name, token := range configs { + require.NoError(t, + writeBackendConfigWithName(ctx, b, l, + map[string]any{ + "token": token, + "base_url": gitlabUrl, + "type": gitlab.TypeSelfManaged.String(), + }, + name, + ), + ) + } + + type roleData struct { + rn, path string + tt gitlab.TokenType + al gitlab.AccessLevel + scopes []string + } + var roles = map[string][]roleData{ + "root": { + {rn: "root-root", path: "root", tt: gitlab.TokenTypePersonal, scopes: gitlab.ValidPersonalTokenScopes}, + {rn: "root-normal-user", path: "normal-user", tt: gitlab.TokenTypePersonal, scopes: gitlab.ValidPersonalTokenScopes}, + }, + "admin": { + {rn: "admin-example-example", path: "example/example", tt: gitlab.TokenTypeProject, al: gitlab.AccessLevelGuestPermissions, scopes: []string{gitlab.TokenScopeApi.String()}}, + }, + "normal": { + {rn: "normal-example", path: "example", tt: gitlab.TokenTypeGroup, al: gitlab.AccessLevelGuestPermissions, scopes: []string{gitlab.TokenScopeApi.String()}}, + }, + } + + for cfg, rds := range roles { + for _, rd := range rds { + var data = map[string]any{ + "name": fmt.Sprintf("%s-{{ .role_name }}-{{ .config_name }}-{{ .token_type }}", rd.path), + "token_type": rd.tt.String(), "path": rd.path, "config_name": cfg, "ttl": gitlab.DefaultAccessTokenMinTTL, + } + + switch rd.tt { + case gitlab.TokenTypePersonal: + data["access_level"] = rd.al.String() + data["scopes"] = rd.scopes + case gitlab.TokenTypeGroup: + data["access_level"] = rd.al.String() + data["scopes"] = rd.scopes + case gitlab.TokenTypeProject: + data["access_level"] = rd.al.String() + data["scopes"] = rd.scopes + } + + resp, err := b.HandleRequest(ctx, &logical.Request{ + Operation: logical.CreateOperation, + Path: fmt.Sprintf("%s/%s", gitlab.PathRoleStorage, rd.rn), Storage: l, + Data: data, + }) + require.NoError(t, err) + require.NotNil(t, resp) + require.NoError(t, resp.Error()) + require.Empty(t, resp.Warnings) + require.EqualValues(t, cfg, resp.Data["config_name"]) + + resp, err = b.HandleRequest(ctx, &logical.Request{ + Operation: logical.ReadOperation, Storage: l, + Path: fmt.Sprintf("%s/%s", gitlab.PathTokenRoleStorage, rd.rn), + }) + require.NoError(t, err) + require.NotNil(t, resp) + require.NotNil(t, resp.Secret) + require.NoError(t, resp.Error()) + + var token = resp.Data["token"].(string) + require.NotEmpty(t, token) + var secret = resp.Secret + require.NotNil(t, secret) + + // verify token that it works + var c *g.Client + c, err = g.NewClient(token, g.WithHTTPClient(httpClient), g.WithBaseURL(gitlabUrl)) + require.NoError(t, err) + require.NotNil(t, c) + + pat, r, err := c.PersonalAccessTokens.GetSinglePersonalAccessToken() + require.NoError(t, err) + require.NotNil(t, r) + require.NotNil(t, pat) + + resp, err = b.HandleRequest(ctx, &logical.Request{ + Operation: logical.RevokeOperation, + Path: "/", + Storage: l, + Secret: secret, + }) + require.NoError(t, err) + require.Nil(t, resp) + + } + } + + resp, err := b.HandleRequest(ctx, &logical.Request{ + Operation: logical.ListOperation, + Path: gitlab.PathRoleStorage, Storage: l, + }) + require.NoError(t, err) + require.NotNil(t, resp) + require.NoError(t, resp.Error()) + require.NotEmpty(t, resp.Data) + + events.expectEvents(t, []expectedEvent{ + {eventType: "gitlab/config-write"}, + {eventType: "gitlab/config-write"}, + {eventType: "gitlab/config-write"}, + {eventType: "gitlab/role-write"}, + {eventType: "gitlab/token-write"}, + {eventType: "gitlab/token-revoke"}, + {eventType: "gitlab/role-write"}, + {eventType: "gitlab/token-write"}, + {eventType: "gitlab/token-revoke"}, + {eventType: "gitlab/role-write"}, + {eventType: "gitlab/token-write"}, + {eventType: "gitlab/token-revoke"}, + {eventType: "gitlab/role-write"}, + {eventType: "gitlab/token-write"}, + {eventType: "gitlab/token-revoke"}, + }) +} diff --git a/secret_access_tokens.go b/secret_access_tokens.go index f1f5d4e..a02f79c 100644 --- a/secret_access_tokens.go +++ b/secret_access_tokens.go @@ -52,14 +52,10 @@ func secretAccessTokens(b *Backend) *framework.Secret { } func (b *Backend) secretAccessTokenRevoke(ctx context.Context, req *logical.Request, _ *framework.FieldData) (*logical.Response, error) { - var config *EntryConfig var err error - config, err = getConfig(ctx, req.Storage) - if err != nil { - return nil, err - } - if config == nil { - return logical.ErrorResponse(ErrBackendNotConfigured.Error()), nil + + if req.Storage == nil { + return nil, fmt.Errorf("storage: %w", ErrNilValue) } var secret = req.Secret @@ -67,22 +63,33 @@ func (b *Backend) secretAccessTokenRevoke(ctx context.Context, req *logical.Requ return nil, fmt.Errorf("secret: %w", ErrNilValue) } + var configName = DefaultConfigName + if val, ok := req.Secret.InternalData["config_name"]; ok { + configName = val.(string) + } + + // var config *EntryConfig + // config, err = getConfig(ctx, req.Storage, configName) + // if err != nil { + // return nil, err + // } + var tokenId int tokenId, err = convertToInt(req.Secret.InternalData["token_id"]) if err != nil { return nil, fmt.Errorf("token_id: %w", err) } + var gitlabRevokesToken, _ = strconv.ParseBool(req.Secret.InternalData["gitlab_revokes_token"].(string)) + var vaultRevokesToken = !gitlabRevokesToken var parentId = req.Secret.InternalData["parent_id"].(string) var tokenType TokenType var tokenTypeValue = req.Secret.InternalData["token_type"].(string) - var gitlabRevokesToken, _ = strconv.ParseBool(req.Secret.InternalData["gitlab_revokes_token"].(string)) - var vaultRevokesToken = !gitlabRevokesToken tokenType, _ = TokenTypeParse(tokenTypeValue) if vaultRevokesToken { var client Client - client, err = b.getClient(ctx, req.Storage) + client, err = b.getClient(ctx, req.Storage, configName) if err != nil { return nil, fmt.Errorf("revoke token cannot get client: %w", err) } diff --git a/secret_access_tokens_test.go b/secret_access_tokens_test.go index a115ee9..320641a 100644 --- a/secret_access_tokens_test.go +++ b/secret_access_tokens_test.go @@ -2,6 +2,7 @@ package gitlab_test import ( "context" + "fmt" "testing" "github.com/hashicorp/vault/sdk/logical" @@ -26,20 +27,11 @@ func TestSecretAccessTokenRevokeToken(t *testing.T) { events.expectEvents(t, []expectedEvent{}) }) - t.Run("backend not configured", func(t *testing.T) { - events.resetEvents(t) - resp, err := b.Secret(gitlab.SecretAccessTokenType).HandleRevoke(ctx, &logical.Request{Storage: l}) - require.NoError(t, err) - require.NotNil(t, resp) - require.ErrorContains(t, resp.Error(), gitlab.ErrBackendNotConfigured.Error()) - events.expectEvents(t, []expectedEvent{}) - }) - t.Run("nil secret", func(t *testing.T) { events.resetEvents(t) resp, err := b.HandleRequest(ctx, &logical.Request{ Operation: logical.UpdateOperation, - Path: gitlab.PathConfigStorage, Storage: l, + Path: fmt.Sprintf("%s/%s", gitlab.PathConfigStorage, gitlab.DefaultConfigName), Storage: l, Data: map[string]any{ "token": "glpat-secret-random-token", "base_url": url, @@ -69,7 +61,7 @@ func TestSecretAccessTokenRevokeToken(t *testing.T) { events.resetEvents(t) resp, err := b.HandleRequest(ctx, &logical.Request{ Operation: logical.UpdateOperation, - Path: gitlab.PathConfigStorage, Storage: l, + Path: fmt.Sprintf("%s/%s", gitlab.PathConfigStorage, gitlab.DefaultConfigName), Storage: l, Data: map[string]any{ "token": "glpat-secret-random-token", "base_url": url, diff --git a/testdata/fixtures/16.11.6/TestPathConfigList_empty_list.yaml b/testdata/fixtures/16.11.6/TestPathConfigList_empty_list.yaml new file mode 100644 index 0000000..2797c38 --- /dev/null +++ b/testdata/fixtures/16.11.6/TestPathConfigList_empty_list.yaml @@ -0,0 +1,3 @@ +--- +version: 2 +interactions: [] diff --git a/testdata/fixtures/16.11.6/TestPathConfigList_multiple_configs.yaml b/testdata/fixtures/16.11.6/TestPathConfigList_multiple_configs.yaml new file mode 100644 index 0000000..ee4c79d --- /dev/null +++ b/testdata/fixtures/16.11.6/TestPathConfigList_multiple_configs.yaml @@ -0,0 +1,198 @@ +--- +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: + - REPLACED-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-10-12T16:49:35.175Z","active":true,"expires_at":"2025-07-11"}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Sat, 12 Oct 2024 16:49:35 GMT + Etag: + - W/"99f9feb16bf70d50ab5be806605fdb66" + 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":"01JA0T5BBGA18T24N90K9BNJFH","version":"1"}' + X-Request-Id: + - 01JA0T5BBGA18T24N90K9BNJFH + X-Runtime: + - "0.101018" + status: 200 OK + code: 200 + duration: 112.323042ms + - id: 1 + 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: + - REPLACED-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":2,"name":"Initial token","revoked":false,"created_at":"2024-07-11T18:53:46.924Z","scopes":["api","read_api","read_user","sudo","admin_mode","create_runner","k8s_proxy","read_repository","write_repository","ai_features","read_service_ping"],"user_id":2,"last_used_at":"2024-10-12T16:49:35.322Z","active":true,"expires_at":"2025-07-11"}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Sat, 12 Oct 2024 16:49:35 GMT + Etag: + - W/"94180e5a4364d99a3347ed7e29ce0322" + 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":"01JA0T5BJE68NQEBGMS0NCFX1N","version":"1"}' + X-Request-Id: + - 01JA0T5BJE68NQEBGMS0NCFX1N + X-Runtime: + - "0.016684" + status: 200 OK + code: 200 + duration: 20.2595ms + - id: 2 + 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: + - REPLACED-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":3,"name":"Initial token","revoked":false,"created_at":"2024-07-11T18:54:07.334Z","scopes":["api","read_api","read_user","create_runner","k8s_proxy","read_repository","write_repository","ai_features","read_service_ping"],"user_id":3,"last_used_at":"2024-10-12T16:49:35.451Z","active":true,"expires_at":"2025-07-11"}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Sat, 12 Oct 2024 16:49:35 GMT + Etag: + - W/"5f0a9ca1c9939def28b759b58bc0319c" + 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":"01JA0T5BKRFT4WZN1QHVBQEDGG","version":"1"}' + X-Request-Id: + - 01JA0T5BKRFT4WZN1QHVBQEDGG + X-Runtime: + - "0.200017" + status: 200 OK + code: 200 + duration: 202.894ms diff --git a/testdata/fixtures/16.11.6/TestPathRoles_invalid_name_template.yaml b/testdata/fixtures/16.11.6/TestPathRoles_invalid_name_template.yaml new file mode 100644 index 0000000..4b3b45e --- /dev/null +++ b/testdata/fixtures/16.11.6/TestPathRoles_invalid_name_template.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: + - REPLACED-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-10-12T19:23:31.602Z","active":true,"expires_at":"2025-07-11"}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Sat, 12 Oct 2024 19:23:31 GMT + Etag: + - W/"8dcc0f233bd2c0bad3d1d91b4107aaa3" + 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":"01JA12Z7BMDQEFQ6RKX08MRRKR","version":"1"}' + X-Request-Id: + - 01JA12Z7BMDQEFQ6RKX08MRRKR + X-Runtime: + - "0.038380" + status: 200 OK + code: 200 + duration: 54.425791ms diff --git a/testdata/fixtures/16.11.6/TestPathTokenRolesMultipleConfigs.yaml b/testdata/fixtures/16.11.6/TestPathTokenRolesMultipleConfigs.yaml new file mode 100644 index 0000000..91d2cc7 --- /dev/null +++ b/testdata/fixtures/16.11.6/TestPathTokenRolesMultipleConfigs.yaml @@ -0,0 +1,1120 @@ +--- +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: + - REPLACED-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-10-12T19:05:53.006Z","active":true,"expires_at":"2025-07-11"}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Sat, 12 Oct 2024 19:12:25 GMT + Etag: + - W/"ed7206e8c7a9cef870195c3d98c08e53" + 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":"01JA12AWG3QR188MTS4B5PP41V","version":"1"}' + X-Request-Id: + - 01JA12AWG3QR188MTS4B5PP41V + X-Runtime: + - "0.038699" + status: 200 OK + code: 200 + duration: 47.88725ms + - id: 1 + 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: + - REPLACED-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":2,"name":"Initial token","revoked":false,"created_at":"2024-07-11T18:53:46.924Z","scopes":["api","read_api","read_user","sudo","admin_mode","create_runner","k8s_proxy","read_repository","write_repository","ai_features","read_service_ping"],"user_id":2,"last_used_at":"2024-10-12T19:05:53.088Z","active":true,"expires_at":"2025-07-11"}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Sat, 12 Oct 2024 19:12:25 GMT + Etag: + - W/"c3ee87e2987e931f72590d1f1a4bd027" + 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":"01JA12AWK1MPGR4XS63KXM5M9Z","version":"1"}' + X-Request-Id: + - 01JA12AWK1MPGR4XS63KXM5M9Z + X-Runtime: + - "0.015030" + status: 200 OK + code: 200 + duration: 18.155542ms + - id: 2 + 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: + - REPLACED-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":3,"name":"Initial token","revoked":false,"created_at":"2024-07-11T18:54:07.334Z","scopes":["api","read_api","read_user","create_runner","k8s_proxy","read_repository","write_repository","ai_features","read_service_ping"],"user_id":3,"last_used_at":"2024-10-12T19:05:53.129Z","active":true,"expires_at":"2025-07-11"}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Sat, 12 Oct 2024 19:12:25 GMT + Etag: + - W/"7bd53fda9f3877943bf5bf9c30c1020f" + 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":"01JA12AWM7XAENZTJV73HA4RN3","version":"1"}' + X-Request-Id: + - 01JA12AWM7XAENZTJV73HA4RN3 + X-Runtime: + - "0.036234" + status: 200 OK + code: 200 + duration: 39.445625ms + - id: 3 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 107 + transfer_encoding: [] + trailer: {} + host: localhost:8080 + remote_addr: "" + request_uri: "" + body: '{"name":"example-normal-example-normal-group","scopes":["api"],"access_level":10,"expires_at":"2024-10-14"}' + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + Private-Token: + - REPLACED-TOKEN + User-Agent: + - go-gitlab + url: http://localhost:8080/api/v4/groups/example/access_tokens + method: POST + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 255 + uncompressed: false + body: '{"id":72,"name":"example-normal-example-normal-group","revoked":false,"created_at":"2024-10-12T19:12:25.389Z","scopes":["api"],"user_id":19,"last_used_at":null,"active":true,"expires_at":"2024-10-14","access_level":10,"token":"glpat-9pTpy3YsyhiggoshfJsh"}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Connection: + - keep-alive + Content-Length: + - "255" + Content-Type: + - application/json + Date: + - Sat, 12 Oct 2024 19:12:25 GMT + Etag: + - W/"b3b54fd5718a47be21869b531371df1a" + 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":"01JA12AWPRFZDP2MKXJPEVZS70","version":"1"}' + X-Request-Id: + - 01JA12AWPRFZDP2MKXJPEVZS70 + X-Runtime: + - "0.089114" + status: 201 Created + code: 201 + duration: 92.119834ms + - id: 4 + 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: + - REPLACED-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: [] + trailer: {} + content_length: 222 + uncompressed: false + body: '{"id":72,"name":"example-normal-example-normal-group","revoked":false,"created_at":"2024-10-12T19:12:25.389Z","scopes":["api"],"user_id":19,"last_used_at":"2024-10-12T19:12:25.516Z","active":true,"expires_at":"2024-10-14"}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Connection: + - keep-alive + Content-Length: + - "222" + Content-Type: + - application/json + Date: + - Sat, 12 Oct 2024 19:12:25 GMT + Etag: + - W/"079f4ea8444710dff2cb1f7c5196effc" + 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":"01JA12AWWK4E0MPEY7NRYX83R6","version":"1"}' + X-Request-Id: + - 01JA12AWWK4E0MPEY7NRYX83R6 + X-Runtime: + - "0.036049" + status: 200 OK + code: 200 + duration: 39.067083ms + - id: 5 + 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: + - REPLACED-TOKEN + User-Agent: + - go-gitlab + url: http://localhost:8080/api/v4/groups/example/access_tokens/72 + 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: + - Sat, 12 Oct 2024 19:12:25 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":"01JA12AWZ10QBH852K8PBVFXHZ","version":"1"}' + X-Request-Id: + - 01JA12AWZ10QBH852K8PBVFXHZ + X-Runtime: + - "0.034073" + status: 204 No Content + code: 204 + duration: 37.120208ms + - 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: + - REPLACED-TOKEN + User-Agent: + - go-gitlab + url: http://localhost:8080/api/v4/users?username=root + 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,"username":"root","name":"Administrator","state":"active","locked":false,"avatar_url":"https://www.gravatar.com/avatar/258d8dc916db8cea2cafb6c3cd0cb0246efe061421dbd83ec3a350428cabda4f?s=80\u0026d=identicon","web_url":"http://1b25ef517b98/root","created_at":"2024-07-11T18:51:40.925Z","bio":"","location":"","public_email":null,"skype":"","linkedin":"","twitter":"","discord":"","website_url":"","organization":"","job_title":"","pronouns":null,"bot":false,"work_information":null,"local_time":null,"last_sign_in_at":"2024-10-11T07:48:06.781Z","confirmed_at":"2024-07-11T18:51:40.831Z","last_activity_on":"2024-10-12","email":"admin@example.com","theme_id":3,"color_scheme_id":1,"projects_limit":100000,"current_sign_in_at":"2024-10-12T18:17:51.538Z","identities":[],"can_create_group":true,"can_create_project":true,"two_factor_enabled":false,"external":false,"private_profile":false,"commit_email":"admin@example.com","is_admin":true,"note":null,"namespace_id":1,"created_by":null,"email_reset_offered_at":null}]' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Sat, 12 Oct 2024 19:12:25 GMT + Etag: + - W/"bf64871792e690538e2fb955f43b6737" + Link: + - ; rel="first", ; rel="last" + 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":"01JA12AX1ENQ7RHJ6D9ZCWRADK","version":"1"}' + X-Next-Page: + - "" + X-Page: + - "1" + X-Per-Page: + - "20" + X-Prev-Page: + - "" + X-Request-Id: + - 01JA12AX1ENQ7RHJ6D9ZCWRADK + X-Runtime: + - "0.036547" + X-Total: + - "1" + X-Total-Pages: + - "1" + status: 200 OK + code: 200 + duration: 39.040583ms + - id: 7 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 128 + transfer_encoding: [] + trailer: {} + host: localhost:8080 + remote_addr: "" + request_uri: "" + body: '{"name":"root-root-root-root-personal","expires_at":"2024-10-14","scopes":["read_service_ping","read_user","sudo","admin_mode"]}' + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + Private-Token: + - REPLACED-TOKEN + User-Agent: + - go-gitlab + url: http://localhost:8080/api/v4/users/1/personal_access_tokens + method: POST + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 275 + uncompressed: false + body: '{"id":73,"name":"root-root-root-root-personal","revoked":false,"created_at":"2024-10-12T19:12:25.739Z","scopes":["read_service_ping","read_user","sudo","admin_mode"],"user_id":1,"last_used_at":null,"active":true,"expires_at":"2024-10-14","token":"glpat-AKXyJZSb56y-eyxy86is"}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Connection: + - keep-alive + Content-Length: + - "275" + Content-Type: + - application/json + Date: + - Sat, 12 Oct 2024 19:12:25 GMT + Etag: + - W/"859aa48da31588ac2c0e4fb03cc228b6" + 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":"01JA12AX3YSC82BMQMAATM8VHZ","version":"1"}' + X-Request-Id: + - 01JA12AX3YSC82BMQMAATM8VHZ + X-Runtime: + - "0.020281" + status: 201 Created + code: 201 + duration: 22.479167ms + - 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: + - REPLACED-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":73,"name":"root-root-root-root-personal","revoked":false,"created_at":"2024-10-12T19:12:25.739Z","scopes":["read_service_ping","read_user","sudo","admin_mode"],"user_id":1,"last_used_at":"2024-10-12T19:12:25.781Z","active":true,"expires_at":"2024-10-14"}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Sat, 12 Oct 2024 19:12:25 GMT + Etag: + - W/"3d48cc5a78b8e08c23b1e842025ca239" + 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":"01JA12AX5CJHFTNGZH84CE9CBC","version":"1"}' + X-Request-Id: + - 01JA12AX5CJHFTNGZH84CE9CBC + X-Runtime: + - "0.012879" + status: 200 OK + code: 200 + duration: 15.404042ms + - id: 9 + 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: + - REPLACED-TOKEN + User-Agent: + - go-gitlab + url: http://localhost:8080/api/v4/personal_access_tokens/73 + 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: + - Sat, 12 Oct 2024 19:12:25 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":"01JA12AX6DDBZFDHGF9R4R8ZG8","version":"1"}' + X-Request-Id: + - 01JA12AX6DDBZFDHGF9R4R8ZG8 + X-Runtime: + - "0.016645" + status: 204 No Content + code: 204 + duration: 19.562583ms + - id: 10 + 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: + - REPLACED-TOKEN + User-Agent: + - go-gitlab + url: http://localhost:8080/api/v4/users?username=normal-user + method: GET + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: + - chunked + trailer: {} + content_length: -1 + uncompressed: true + body: '[{"id":3,"username":"normal-user","name":"Normal User","state":"active","locked":false,"avatar_url":"https://www.gravatar.com/avatar/30ee3a8ed91c220db688a3bde115c203763b4281374c40835f69168786a590af?s=80\u0026d=identicon","web_url":"http://1b25ef517b98/normal-user","created_at":"2024-07-11T18:53:06.485Z","bio":"","location":"","public_email":null,"skype":"","linkedin":"","twitter":"","discord":"","website_url":"","organization":"","job_title":"","pronouns":null,"bot":false,"work_information":null,"followers":0,"following":0,"is_followed":false,"local_time":null,"last_sign_in_at":"2024-10-12T18:37:42.685Z","confirmed_at":"2024-07-11T18:53:06.412Z","last_activity_on":"2024-10-12","email":"normal@local","theme_id":3,"color_scheme_id":1,"projects_limit":100000,"current_sign_in_at":"2024-10-12T18:37:42.685Z","identities":[],"can_create_group":true,"can_create_project":true,"two_factor_enabled":false,"external":false,"private_profile":false,"commit_email":"normal@local","is_admin":false,"note":null,"namespace_id":3,"created_by":null,"email_reset_offered_at":null}]' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Sat, 12 Oct 2024 19:12:25 GMT + Etag: + - W/"55c793b0242c05c133f6f876a663f26d" + Link: + - ; rel="first", ; rel="last" + 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":"01JA12AX7N1VVFJCT8RF5EPW82","version":"1"}' + X-Next-Page: + - "" + X-Page: + - "1" + X-Per-Page: + - "20" + X-Prev-Page: + - "" + X-Request-Id: + - 01JA12AX7N1VVFJCT8RF5EPW82 + X-Runtime: + - "0.028469" + X-Total: + - "1" + X-Total-Pages: + - "1" + status: 200 OK + code: 200 + duration: 30.835417ms + - id: 11 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 142 + transfer_encoding: [] + trailer: {} + host: localhost:8080 + remote_addr: "" + request_uri: "" + body: '{"name":"normal-user-root-normal-user-root-personal","expires_at":"2024-10-14","scopes":["read_service_ping","read_user","sudo","admin_mode"]}' + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + Private-Token: + - REPLACED-TOKEN + User-Agent: + - go-gitlab + url: http://localhost:8080/api/v4/users/3/personal_access_tokens + method: POST + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 289 + uncompressed: false + body: '{"id":74,"name":"normal-user-root-normal-user-root-personal","revoked":false,"created_at":"2024-10-12T19:12:25.921Z","scopes":["read_service_ping","read_user","sudo","admin_mode"],"user_id":3,"last_used_at":null,"active":true,"expires_at":"2024-10-14","token":"glpat-FJCtKdzsUMy6kMJ3NxKs"}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Connection: + - keep-alive + Content-Length: + - "289" + Content-Type: + - application/json + Date: + - Sat, 12 Oct 2024 19:12:25 GMT + Etag: + - W/"c0ee44101ff406a8afff275b429186ee" + 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":"01JA12AX9NW6V427ZY71V401YG","version":"1"}' + X-Request-Id: + - 01JA12AX9NW6V427ZY71V401YG + X-Runtime: + - "0.018384" + status: 201 Created + code: 201 + duration: 20.53525ms + - id: 12 + 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: + - REPLACED-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":74,"name":"normal-user-root-normal-user-root-personal","revoked":false,"created_at":"2024-10-12T19:12:25.921Z","scopes":["read_service_ping","read_user","sudo","admin_mode"],"user_id":3,"last_used_at":"2024-10-12T19:12:25.958Z","active":true,"expires_at":"2024-10-14"}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Sat, 12 Oct 2024 19:12:25 GMT + Etag: + - W/"a24611f4835c20df302ce67aaa7ef210" + 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":"01JA12AXAZFW8B328X6QH8RKXZ","version":"1"}' + X-Request-Id: + - 01JA12AXAZFW8B328X6QH8RKXZ + X-Runtime: + - "0.012025" + status: 200 OK + code: 200 + duration: 14.415375ms + - id: 13 + 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: + - REPLACED-TOKEN + User-Agent: + - go-gitlab + url: http://localhost:8080/api/v4/personal_access_tokens/74 + 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: + - Sat, 12 Oct 2024 19:12:25 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":"01JA12AXBX96H50NH7CNP2BVGY","version":"1"}' + X-Request-Id: + - 01JA12AXBX96H50NH7CNP2BVGY + X-Runtime: + - "0.015371" + status: 204 No Content + code: 204 + duration: 17.49875ms + - id: 14 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 123 + transfer_encoding: [] + trailer: {} + host: localhost:8080 + remote_addr: "" + request_uri: "" + body: '{"name":"example/example-admin-example-example-admin-project","scopes":["api"],"access_level":10,"expires_at":"2024-10-14"}' + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + Private-Token: + - REPLACED-TOKEN + User-Agent: + - go-gitlab + url: http://localhost:8080/api/v4/projects/example%2Fexample/access_tokens + method: POST + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 271 + uncompressed: false + body: '{"id":75,"name":"example/example-admin-example-example-admin-project","revoked":false,"created_at":"2024-10-12T19:12:26.112Z","scopes":["api"],"user_id":20,"last_used_at":null,"active":true,"expires_at":"2024-10-14","access_level":10,"token":"glpat-jwWmrS3Ge3qzhzszjvz3"}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Connection: + - keep-alive + Content-Length: + - "271" + Content-Type: + - application/json + Date: + - Sat, 12 Oct 2024 19:12:26 GMT + Etag: + - W/"b382b8ab3f856dd3763fcd39011f6f3b" + 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":"01JA12AXD3NQ1JYB4NDS2E014T","version":"1"}' + X-Request-Id: + - 01JA12AXD3NQ1JYB4NDS2E014T + X-Runtime: + - "0.097230" + status: 201 Created + code: 201 + duration: 99.548583ms + - id: 15 + 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: + - REPLACED-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: [] + trailer: {} + content_length: 238 + uncompressed: false + body: '{"id":75,"name":"example/example-admin-example-example-admin-project","revoked":false,"created_at":"2024-10-12T19:12:26.112Z","scopes":["api"],"user_id":20,"last_used_at":"2024-10-12T19:12:26.231Z","active":true,"expires_at":"2024-10-14"}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Connection: + - keep-alive + Content-Length: + - "238" + Content-Type: + - application/json + Date: + - Sat, 12 Oct 2024 19:12:26 GMT + Etag: + - W/"9654a9ca4ab123bf74b194cebcf1bd4b" + 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":"01JA12AXKD2JPC710Y2MCZ2JVH","version":"1"}' + X-Request-Id: + - 01JA12AXKD2JPC710Y2MCZ2JVH + X-Runtime: + - "0.021420" + status: 200 OK + code: 200 + duration: 24.396958ms + - id: 16 + 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: + - REPLACED-TOKEN + User-Agent: + - go-gitlab + url: http://localhost:8080/api/v4/projects/example%2Fexample/access_tokens/75 + 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: + - Sat, 12 Oct 2024 19:12:26 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":"01JA12AXMZ53AKZ2WD191TTJHR","version":"1"}' + X-Request-Id: + - 01JA12AXMZ53AKZ2WD191TTJHR + X-Runtime: + - "0.032242" + status: 204 No Content + code: 204 + duration: 34.72825ms diff --git a/with_admin_user_pat_gitlab_revokes_token_test.go b/with_admin_user_pat_gitlab_revokes_token_test.go index f51ebb4..d643607 100644 --- a/with_admin_user_pat_gitlab_revokes_token_test.go +++ b/with_admin_user_pat_gitlab_revokes_token_test.go @@ -25,7 +25,7 @@ func TestWithAdminUser_PAT_AdminUser_GitlabRevokesToken(t *testing.T) { resp, err := b.HandleRequest(ctx, &logical.Request{ Operation: logical.UpdateOperation, - Path: gitlab.PathConfigStorage, Storage: l, + Path: fmt.Sprintf("%s/%s", gitlab.PathConfigStorage, gitlab.DefaultConfigName), Storage: l, Data: map[string]any{ "token": "glpat-secret-admin-token", "base_url": url, diff --git a/with_admin_user_pat_vault_revokes_token_test.go b/with_admin_user_pat_vault_revokes_token_test.go index 348befb..6d68e78 100644 --- a/with_admin_user_pat_vault_revokes_token_test.go +++ b/with_admin_user_pat_vault_revokes_token_test.go @@ -25,7 +25,7 @@ func TestWithAdminUser_PAT_AdminUser_VaultRevokesToken(t *testing.T) { resp, err := b.HandleRequest(ctx, &logical.Request{ Operation: logical.UpdateOperation, - Path: gitlab.PathConfigStorage, Storage: l, + Path: fmt.Sprintf("%s/%s", gitlab.PathConfigStorage, gitlab.DefaultConfigName), Storage: l, Data: map[string]any{ "token": "glpat-secret-admin-token", "base_url": url, diff --git a/with_gitlab_com_user_rotate_token_test.go b/with_gitlab_com_user_rotate_token_test.go index b8a9a56..9ef2d9d 100644 --- a/with_gitlab_com_user_rotate_token_test.go +++ b/with_gitlab_com_user_rotate_token_test.go @@ -22,7 +22,8 @@ func TestWithGitlabUser_RotateToken(t *testing.T) { resp, err := b.HandleRequest(ctx, &logical.Request{ Operation: logical.UpdateOperation, - Path: gitlab.PathConfigStorage, Storage: l, + Path: fmt.Sprintf("%s/%s", gitlab.PathConfigStorage, gitlab.DefaultConfigName), + Storage: l, Data: map[string]any{ "token": gitlabComPersonalAccessToken, "base_url": gitlabComUrl, @@ -43,7 +44,7 @@ func TestWithGitlabUser_RotateToken(t *testing.T) { { resp, err := b.HandleRequest(ctx, &logical.Request{ Operation: logical.UpdateOperation, - Path: fmt.Sprintf("%s/rotate", gitlab.PathConfigStorage), Storage: l, + Path: fmt.Sprintf("%s/%s/rotate", gitlab.PathConfigStorage, gitlab.DefaultConfigName), Storage: l, Data: map[string]any{}, }) require.NoError(t, err) diff --git a/with_normal_user_gat_test.go b/with_normal_user_gat_test.go index e7f2fbf..16c5a03 100644 --- a/with_normal_user_gat_test.go +++ b/with_normal_user_gat_test.go @@ -25,7 +25,7 @@ func TestWithNormalUser_GAT(t *testing.T) { resp, err := b.HandleRequest(ctx, &logical.Request{ Operation: logical.UpdateOperation, - Path: gitlab.PathConfigStorage, Storage: l, + Path: fmt.Sprintf("%s/%s", gitlab.PathConfigStorage, gitlab.DefaultConfigName), Storage: l, Data: map[string]any{ "token": "glpat-secret-normal-token", "base_url": url, diff --git a/with_normal_user_personal_at_fails_test.go b/with_normal_user_personal_at_fails_test.go index 7e1bfb0..4e64f1b 100644 --- a/with_normal_user_personal_at_fails_test.go +++ b/with_normal_user_personal_at_fails_test.go @@ -23,7 +23,7 @@ func TestWithNormalUser_PersonalAT_Fails(t *testing.T) { resp, err := b.HandleRequest(ctx, &logical.Request{ Operation: logical.UpdateOperation, - Path: gitlab.PathConfigStorage, Storage: l, + Path: fmt.Sprintf("%s/%s", gitlab.PathConfigStorage, gitlab.DefaultConfigName), Storage: l, Data: map[string]any{ "token": "glpat-secret-normal-token", "base_url": url, diff --git a/with_normal_user_project_at_test.go b/with_normal_user_project_at_test.go index 5375fbd..a447417 100644 --- a/with_normal_user_project_at_test.go +++ b/with_normal_user_project_at_test.go @@ -25,7 +25,7 @@ func TestWithNormalUser_ProjectAT(t *testing.T) { resp, err := b.HandleRequest(ctx, &logical.Request{ Operation: logical.UpdateOperation, - Path: gitlab.PathConfigStorage, Storage: l, + Path: fmt.Sprintf("%s/%s", gitlab.PathConfigStorage, gitlab.DefaultConfigName), Storage: l, Data: map[string]any{ "token": "glpat-secret-normal-token", "base_url": url, diff --git a/with_service_account_fail_test.go b/with_service_account_fail_test.go index bd6c10f..8341794 100644 --- a/with_service_account_fail_test.go +++ b/with_service_account_fail_test.go @@ -25,7 +25,7 @@ func TestWithServiceAccountUserFail(t *testing.T) { resp, err := b.HandleRequest(ctx, &logical.Request{ Operation: logical.UpdateOperation, - Path: gitlab.PathConfigStorage, Storage: l, + Path: fmt.Sprintf("%s/%s", gitlab.PathConfigStorage, gitlab.DefaultConfigName), Storage: l, Data: map[string]any{ "token": gitlabServiceAccountToken, "base_url": gitlabServiceAccountUrl, @@ -40,8 +40,8 @@ func TestWithServiceAccountUserFail(t *testing.T) { require.NoError(t, resp.Error()) require.NotEmpty(t, events) - require.NotNil(t, b.GetClient()) - var gClient = b.GetClient().GitlabClient() + require.NotNil(t, b.GetClient(gitlab.DefaultConfigName)) + var gClient = b.GetClient(gitlab.DefaultConfigName).GitlabClient() require.NotNil(t, gClient) usr, _, err := gClient.Users.CreateServiceAccountUser() diff --git a/with_service_account_group_test.go b/with_service_account_group_test.go index 784f9d1..5699367 100644 --- a/with_service_account_group_test.go +++ b/with_service_account_group_test.go @@ -23,7 +23,7 @@ func TestWithServiceAccountGroup(t *testing.T) { resp, err := b.HandleRequest(ctx, &logical.Request{ Operation: logical.UpdateOperation, - Path: gitlab.PathConfigStorage, Storage: l, + Path: fmt.Sprintf("%s/%s", gitlab.PathConfigStorage, gitlab.DefaultConfigName), Storage: l, Data: map[string]any{ "token": gitlabServiceAccountToken, "base_url": gitlabServiceAccountUrl, @@ -38,8 +38,8 @@ func TestWithServiceAccountGroup(t *testing.T) { require.NoError(t, resp.Error()) require.NotEmpty(t, events) - require.NotNil(t, b.GetClient()) - var gClient = b.GetClient().GitlabClient() + require.NotNil(t, b.GetClient(gitlab.DefaultConfigName)) + var gClient = b.GetClient(gitlab.DefaultConfigName).GitlabClient() require.NotNil(t, gClient) // Create a group service account @@ -69,7 +69,7 @@ func TestWithServiceAccountGroup(t *testing.T) { require.NotNil(t, resp) require.NoError(t, resp.Error()) require.Empty(t, resp.Warnings) - require.EqualValues(t, resp.Data["config"], gitlab.TypeConfigDefault) + require.EqualValues(t, resp.Data["config_name"], gitlab.TypeConfigDefault) // Get a new token for the service account resp, err = b.HandleRequest(ctx, &logical.Request{ diff --git a/with_service_account_user_test.go b/with_service_account_user_test.go index 23c1209..a84335b 100644 --- a/with_service_account_user_test.go +++ b/with_service_account_user_test.go @@ -22,7 +22,7 @@ func TestWithServiceAccountUser(t *testing.T) { resp, err := b.HandleRequest(ctx, &logical.Request{ Operation: logical.UpdateOperation, - Path: gitlab.PathConfigStorage, Storage: l, + Path: fmt.Sprintf("%s/%s", gitlab.PathConfigStorage, gitlab.DefaultConfigName), Storage: l, Data: map[string]any{ "token": gitlabServiceAccountToken, "base_url": gitlabServiceAccountUrl, @@ -37,8 +37,8 @@ func TestWithServiceAccountUser(t *testing.T) { require.NoError(t, resp.Error()) require.NotEmpty(t, events) - require.NotNil(t, b.GetClient()) - var gClient = b.GetClient().GitlabClient() + require.NotNil(t, b.GetClient(gitlab.DefaultConfigName)) + var gClient = b.GetClient(gitlab.DefaultConfigName).GitlabClient() require.NotNil(t, gClient) // Create a service account user @@ -67,7 +67,7 @@ func TestWithServiceAccountUser(t *testing.T) { require.NotNil(t, resp) require.NoError(t, resp.Error()) require.Empty(t, resp.Warnings) - require.EqualValues(t, resp.Data["config"], gitlab.TypeConfigDefault) + require.EqualValues(t, resp.Data["config_name"], gitlab.TypeConfigDefault) // Get a new token for the service account resp, err = b.HandleRequest(ctx, &logical.Request{