diff --git a/backend.go b/backend.go index c1bad21..d15f5e2 100644 --- a/backend.go +++ b/backend.go @@ -12,6 +12,10 @@ import ( "github.com/hashicorp/vault/sdk/framework" "github.com/hashicorp/vault/sdk/helper/locksutil" "github.com/hashicorp/vault/sdk/logical" + + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/flags" + g "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/gitlab" + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/utils" ) const ( @@ -27,14 +31,14 @@ with the "^config/(?P\w(([\w-.@]+)?\w)?)$" endpoints. ` ) -func Factory(flags Flags) logical.Factory { +func Factory(flags flags.Flags) logical.Factory { return func(ctx context.Context, config *logical.BackendConfig) (logical.Backend, error) { return factory(ctx, config, flags) } } // Factory returns expected new Backend as logical.Backend -func factory(ctx context.Context, conf *logical.BackendConfig, flags Flags) (logical.Backend, error) { +func factory(ctx context.Context, conf *logical.BackendConfig, flags flags.Flags) (logical.Backend, error) { var b = &Backend{ roleLocks: locksutil.CreateLocks(), clients: sync.Map{}, @@ -82,7 +86,7 @@ func factory(ctx context.Context, conf *logical.BackendConfig, flags Flags) (log type Backend struct { *framework.Backend - flags Flags + flags flags.Flags // The client that we can use to create and revoke the access tokens clients sync.Map @@ -180,8 +184,8 @@ func (b *Backend) getClient(ctx context.Context, s logical.Storage, name string) } var httpClient *http.Client - httpClient, _ = HttpClientFromContext(ctx) - if client, _ = ClientFromContext(ctx); client == nil { + httpClient, _ = utils.HttpClientFromContext(ctx) + if client, _ = g.ClientFromContext(ctx); client == nil { if client, err = NewGitlabClient(config, httpClient, b.Logger()); err == nil { b.SetClient(client, name) } diff --git a/cmd/vault-plugin-secrets-gitlab/main.go b/cmd/vault-plugin-secrets-gitlab/main.go index c820e71..186713a 100644 --- a/cmd/vault-plugin-secrets-gitlab/main.go +++ b/cmd/vault-plugin-secrets-gitlab/main.go @@ -8,6 +8,7 @@ import ( "github.com/hashicorp/vault/sdk/plugin" gat "github.com/ilijamt/vault-plugin-secrets-gitlab" + f "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/flags" ) var ( @@ -17,7 +18,7 @@ var ( func main() { apiClientMeta := &api.PluginAPIClientMeta{} flags := apiClientMeta.FlagSet() - pf := &gat.Flags{} + pf := &f.Flags{} pf.FlagSet(flags) fatalIfError(flags.Parse(os.Args[1:])) diff --git a/defs.go b/defs.go index 61b701a..f2d116f 100644 --- a/defs.go +++ b/defs.go @@ -1,22 +1,9 @@ package gitlab import ( - "context" - "errors" - "net/http" "time" ) -var ( - ErrNilValue = errors.New("nil value") - ErrInvalidValue = errors.New("invalid value") - ErrFieldRequired = errors.New("required field") - ErrFieldInvalidValue = errors.New("invalid value for field") - ErrBackendNotConfigured = errors.New("backend not configured") -) - -type contextKey string - const ( DefaultConfigFieldAccessTokenMaxTTL = 7 * 24 * time.Hour DefaultConfigFieldAccessTokenRotate = DefaultAutoRotateBeforeMinTTL @@ -25,44 +12,5 @@ const ( DefaultAccessTokenMaxPossibleTTL = 365 * 24 * time.Hour DefaultAutoRotateBeforeMinTTL = 24 * time.Hour DefaultAutoRotateBeforeMaxTTL = 730 * time.Hour - ctxKeyHttpClient = contextKey("vpsg-ctx-key-http-client") - ctxKeyGitlabClient = contextKey("vpsg-ctx-key-gitlab-client") - ctxKeyTimeNow = contextKey("vpsg-ctx-key-time-now") DefaultConfigName = "default" ) - -func WithStaticTime(ctx context.Context, t time.Time) context.Context { - return context.WithValue(ctx, ctxKeyTimeNow, t) -} - -func TimeFromContext(ctx context.Context) time.Time { - t, ok := ctx.Value(ctxKeyTimeNow).(time.Time) - if !ok { - return time.Now() - } - return t -} - -func HttpClientNewContext(ctx context.Context, httpClient *http.Client) context.Context { - return context.WithValue(ctx, ctxKeyHttpClient, httpClient) -} - -func HttpClientFromContext(ctx context.Context) (*http.Client, bool) { - u, ok := ctx.Value(ctxKeyHttpClient).(*http.Client) - if !ok { - u = nil - } - return u, ok -} - -func ClientNewContext(ctx context.Context, client Client) context.Context { - return context.WithValue(ctx, ctxKeyGitlabClient, client) -} - -func ClientFromContext(ctx context.Context) (Client, bool) { - u, ok := ctx.Value(ctxKeyGitlabClient).(Client) - if !ok { - u = nil - } - return u, ok -} diff --git a/entry_config.go b/entry_config.go index b9e18e3..2fbe057 100644 --- a/entry_config.go +++ b/entry_config.go @@ -11,6 +11,10 @@ import ( "github.com/hashicorp/go-multierror" "github.com/hashicorp/vault/sdk/framework" "github.com/hashicorp/vault/sdk/logical" + + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/errs" + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/gitlab" + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/utils" ) type EntryConfig struct { @@ -22,7 +26,7 @@ type EntryConfig struct { 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"` + Type gitlab.Type `json:"type" structs:"type" mapstructure:"type"` Name string `json:"name" structs:"name" mapstructure:"name"` GitlabVersion string `json:"gitlab_version" structs:"gitlab_version" mapstructure:"gitlab_version"` GitlabRevision string `json:"gitlab_revision" structs:"gitlab_revision" mapstructure:"gitlab_revision"` @@ -32,7 +36,7 @@ type EntryConfig struct { func (e *EntryConfig) Merge(data *framework.FieldData) (warnings []string, changes map[string]string, err error) { var er error if data == nil { - return warnings, changes, multierror.Append(fmt.Errorf("data: %w", ErrNilValue)) + return warnings, changes, multierror.Append(fmt.Errorf("data: %w", errs.ErrNilValue)) } if err = data.Validate(); err != nil { @@ -47,8 +51,8 @@ func (e *EntryConfig) Merge(data *framework.FieldData) (warnings []string, chang } if typ, ok := data.GetOk("type"); ok { - var pType Type - if pType, er = TypeParse(typ.(string)); er != nil { + var pType gitlab.Type + if pType, er = gitlab.TypeParse(typ.(string)); er != nil { err = multierror.Append(err, er) } else { e.Type = pType @@ -81,11 +85,11 @@ func (e *EntryConfig) Merge(data *framework.FieldData) (warnings []string, chang func (e *EntryConfig) updateAutoRotateBefore(data *framework.FieldData) (warnings []string, err *multierror.Error) { if val, ok := data.GetOk("auto_rotate_before"); ok { - atr, _ := convertToInt(val) + atr, _ := utils.ConvertToInt(val) if atr > int(DefaultAutoRotateBeforeMaxTTL.Seconds()) { - err = multierror.Append(err, fmt.Errorf("auto_rotate_token can not be bigger than %s: %w", DefaultAutoRotateBeforeMaxTTL, ErrInvalidValue)) + err = multierror.Append(err, fmt.Errorf("auto_rotate_token can not be bigger than %s: %w", DefaultAutoRotateBeforeMaxTTL, errs.ErrInvalidValue)) } else if atr <= int(DefaultAutoRotateBeforeMinTTL.Seconds())-1 { - err = multierror.Append(err, fmt.Errorf("auto_rotate_token can not be less than %s: %w", DefaultAutoRotateBeforeMinTTL, ErrInvalidValue)) + err = multierror.Append(err, fmt.Errorf("auto_rotate_token can not be less than %s: %w", DefaultAutoRotateBeforeMinTTL, errs.ErrInvalidValue)) } else { e.AutoRotateBefore = time.Duration(atr) * time.Second } @@ -98,7 +102,7 @@ func (e *EntryConfig) updateAutoRotateBefore(data *framework.FieldData) (warning func (e *EntryConfig) UpdateFromFieldData(data *framework.FieldData) (warnings []string, err error) { if data == nil { - return warnings, multierror.Append(fmt.Errorf("data: %w", ErrNilValue)) + return warnings, multierror.Append(fmt.Errorf("data: %w", errs.ErrNilValue)) } if err = data.Validate(); err != nil { @@ -111,21 +115,21 @@ func (e *EntryConfig) UpdateFromFieldData(data *framework.FieldData) (warnings [ if token, ok := data.GetOk("token"); ok && len(token.(string)) > 0 { e.Token = token.(string) } else { - err = multierror.Append(err, fmt.Errorf("token: %w", ErrFieldRequired)) + err = multierror.Append(err, fmt.Errorf("token: %w", errs.ErrFieldRequired)) } if typ, ok := data.GetOk("type"); ok { - if e.Type, er = TypeParse(typ.(string)); er != nil { + if e.Type, er = gitlab.TypeParse(typ.(string)); er != nil { err = multierror.Append(err, er) } } else { - err = multierror.Append(err, fmt.Errorf("gitlab type: %w", ErrFieldRequired)) + err = multierror.Append(err, fmt.Errorf("gitlab type: %w", errs.ErrFieldRequired)) } if baseUrl, ok := data.GetOk("base_url"); ok && len(baseUrl.(string)) > 0 { e.BaseURL = baseUrl.(string) } else { - err = multierror.Append(err, fmt.Errorf("base_url: %w", ErrFieldRequired)) + err = multierror.Append(err, fmt.Errorf("base_url: %w", errs.ErrFieldRequired)) } { @@ -173,7 +177,7 @@ func (e *EntryConfig) LogicalResponseData(includeToken bool) (data map[string]an 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) + return nil, fmt.Errorf("%w: local.Storage", errs.ErrNilValue) } var entry *logical.StorageEntry if entry, err = s.Get(ctx, fmt.Sprintf("%s/%s", PathConfigStorage, name)); err == nil { diff --git a/entry_config_merge_test.go b/entry_config_merge_test.go index 8c7f32c..2beee73 100644 --- a/entry_config_merge_test.go +++ b/entry_config_merge_test.go @@ -12,6 +12,8 @@ import ( "github.com/stretchr/testify/require" gitlab "github.com/ilijamt/vault-plugin-secrets-gitlab" + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/errs" + gitlab2 "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/gitlab" ) func TestEntryConfigMerge(t *testing.T) { @@ -20,7 +22,7 @@ func TestEntryConfigMerge(t *testing.T) { warnings, changes, err := e.Merge(nil) require.Empty(t, warnings) require.Empty(t, changes) - require.ErrorIs(t, err, gitlab.ErrNilValue) + require.ErrorIs(t, err, errs.ErrNilValue) }) t.Run("unconvertible data type", func(t *testing.T) { @@ -46,10 +48,10 @@ func TestEntryConfigMerge(t *testing.T) { }{ { name: "update type only", - originalConfig: &gitlab.EntryConfig{Type: gitlab.TypeSelfManaged}, - expectedConfig: &gitlab.EntryConfig{Type: gitlab.TypeSaaS}, - raw: map[string]interface{}{"type": gitlab.TypeSaaS}, - changes: map[string]string{"type": gitlab.TypeSaaS.String()}, + originalConfig: &gitlab.EntryConfig{Type: gitlab2.TypeSelfManaged}, + expectedConfig: &gitlab.EntryConfig{Type: gitlab2.TypeSaaS}, + raw: map[string]interface{}{"type": gitlab2.TypeSaaS}, + changes: map[string]string{"type": gitlab2.TypeSaaS.String()}, }, { name: "auto rotate token set to false", @@ -67,12 +69,12 @@ func TestEntryConfigMerge(t *testing.T) { }, { name: "update type with invalid type", - originalConfig: &gitlab.EntryConfig{Type: gitlab.TypeSelfManaged}, - expectedConfig: &gitlab.EntryConfig{Type: gitlab.TypeSelfManaged}, + originalConfig: &gitlab.EntryConfig{Type: gitlab2.TypeSelfManaged}, + expectedConfig: &gitlab.EntryConfig{Type: gitlab2.TypeSelfManaged}, raw: map[string]interface{}{"type": "test"}, err: true, errMap: map[string]int{ - gitlab.ErrUnknownType.Error(): 1, + gitlab2.ErrUnknownType.Error(): 1, }, }, { @@ -95,7 +97,7 @@ func TestEntryConfigMerge(t *testing.T) { expectedConfig: &gitlab.EntryConfig{AutoRotateBefore: gitlab.DefaultAutoRotateBeforeMinTTL + time.Hour}, raw: map[string]interface{}{"auto_rotate_before": "1h"}, err: true, - errMap: map[string]int{gitlab.ErrInvalidValue.Error(): 1}, + errMap: map[string]int{errs.ErrInvalidValue.Error(): 1}, }, { name: "auto rotate before invalid value higher than min", @@ -103,7 +105,7 @@ func TestEntryConfigMerge(t *testing.T) { expectedConfig: &gitlab.EntryConfig{AutoRotateBefore: gitlab.DefaultAutoRotateBeforeMinTTL + time.Hour}, raw: map[string]interface{}{"auto_rotate_before": (gitlab.DefaultAutoRotateBeforeMaxTTL + time.Hour).String()}, err: true, - errMap: map[string]int{gitlab.ErrInvalidValue.Error(): 1}, + errMap: map[string]int{errs.ErrInvalidValue.Error(): 1}, }, { name: "auto rotate with a valid value", diff --git a/entry_config_update_form_field_data_test.go b/entry_config_update_form_field_data_test.go index 412106c..f617c3b 100644 --- a/entry_config_update_form_field_data_test.go +++ b/entry_config_update_form_field_data_test.go @@ -11,13 +11,15 @@ import ( "github.com/stretchr/testify/require" gitlab "github.com/ilijamt/vault-plugin-secrets-gitlab" + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/errs" + gitlab2 "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/gitlab" ) func TestEntryConfigUpdateFromFieldData(t *testing.T) { t.Run("nil data", func(t *testing.T) { e := new(gitlab.EntryConfig) _, err := e.UpdateFromFieldData(nil) - require.ErrorIs(t, err, gitlab.ErrNilValue) + require.ErrorIs(t, err, errs.ErrNilValue) }) var tests = []struct { @@ -34,7 +36,7 @@ func TestEntryConfigUpdateFromFieldData(t *testing.T) { err: true, warnings: []string{"auto_rotate_token not specified setting to 24h0m0s"}, errMap: map[string]int{ - gitlab.ErrFieldRequired.Error(): 3, + errs.ErrFieldRequired.Error(): 3, }, }, { @@ -47,8 +49,8 @@ func TestEntryConfigUpdateFromFieldData(t *testing.T) { warnings: []string{"auto_rotate_token not specified setting to 24h0m0s"}, err: true, errMap: map[string]int{ - gitlab.ErrFieldRequired.Error(): 1, - gitlab.ErrUnknownType.Error(): 1, + errs.ErrFieldRequired.Error(): 1, + gitlab2.ErrUnknownType.Error(): 1, }, }, { @@ -64,7 +66,7 @@ func TestEntryConfigUpdateFromFieldData(t *testing.T) { name: "valid config", expectedConfig: &gitlab.EntryConfig{ Token: "token", - Type: gitlab.TypeSelfManaged, + Type: gitlab2.TypeSelfManaged, AutoRotateToken: false, AutoRotateBefore: gitlab.DefaultAutoRotateBeforeMinTTL, BaseURL: "https://gitlab.com", @@ -72,7 +74,7 @@ func TestEntryConfigUpdateFromFieldData(t *testing.T) { warnings: []string{"auto_rotate_token not specified setting to 24h0m0s"}, raw: map[string]interface{}{ "token": "token", - "type": gitlab.TypeSelfManaged.String(), + "type": gitlab2.TypeSelfManaged.String(), "base_url": "https://gitlab.com", }, }, diff --git a/entry_role.go b/entry_role.go index a5b086e..c9feaa3 100644 --- a/entry_role.go +++ b/entry_role.go @@ -6,18 +6,28 @@ import ( "time" "github.com/hashicorp/vault/sdk/logical" + + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/token" ) type EntryRole struct { - RoleName string `json:"role_name" structs:"role_name" mapstructure:"role_name"` - TTL time.Duration `json:"ttl" structs:"ttl" mapstructure:"ttl"` - Path string `json:"path" structs:"path" mapstructure:"path"` - Name string `json:"name" structs:"name" mapstructure:"name"` - Scopes []string `json:"scopes" structs:"scopes" mapstructure:"scopes"` - 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"` - ConfigName string `json:"config_name" structs:"config_name" mapstructure:"config_name"` + RoleName string `json:"role_name" structs:"role_name" mapstructure:"role_name"` + TTL time.Duration `json:"ttl" structs:"ttl" mapstructure:"ttl"` + Path string `json:"path" structs:"path" mapstructure:"path"` + Name string `json:"name" structs:"name" mapstructure:"name"` + Scopes []string `json:"scopes" structs:"scopes" mapstructure:"scopes"` + AccessLevel token.AccessLevel `json:"access_level" structs:"access_level" mapstructure:"access_level,omitempty"` + TokenType token.Type `json:"token_type" structs:"token_type" mapstructure:"token_type"` + GitlabRevokesTokens bool `json:"gitlab_revokes_token" structs:"gitlab_revokes_token" mapstructure:"gitlab_revokes_token"` + ConfigName string `json:"config_name" structs:"config_name" mapstructure:"config_name"` +} + +func (e EntryRole) IsNil() bool { + return false +} + +func (e EntryRole) GetName() string { + return e.Name } func (e EntryRole) LogicalResponseData() map[string]any { diff --git a/flags.go b/flags.go deleted file mode 100644 index d2ebb64..0000000 --- a/flags.go +++ /dev/null @@ -1,17 +0,0 @@ -package gitlab - -import ( - "flag" -) - -type Flags struct { - ShowConfigToken bool `json:"show_config_token" mapstructure:"show_config_token"` - AllowRuntimeFlagsChange bool `json:"allow_runtime_flags_change" mapstructure:"allow_runtime_flags_change"` -} - -// FlagSet returns the flag set for configuring the TLS connection -func (f *Flags) FlagSet(fs *flag.FlagSet) *flag.FlagSet { - fs.BoolVar(&f.ShowConfigToken, "show-config-token", false, "Display the token value when reading it's config the configuration endpoint.") - fs.BoolVar(&f.AllowRuntimeFlagsChange, "allow-runtime-flags-change", false, "Allows you to change the flags dynamically at runtime.") - return fs -} diff --git a/gitlab_client.go b/gitlab_client.go index 55f098a..913295a 100644 --- a/gitlab_client.go +++ b/gitlab_client.go @@ -14,6 +14,11 @@ import ( "github.com/hashicorp/vault/sdk/helper/logging" g "gitlab.com/gitlab-org/api/client-go" "golang.org/x/time/rate" + + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/errs" + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/models" + t "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/token" + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/utils" ) var ( @@ -21,33 +26,6 @@ var ( ErrRoleNotFound = errors.New("role not found") ) -type Client interface { - GitlabClient(ctx context.Context) *g.Client - Valid(ctx context.Context) bool - Metadata(ctx context.Context) (*g.Metadata, error) - CurrentTokenInfo(ctx context.Context) (*TokenConfig, error) - RotateCurrentToken(ctx context.Context) (newToken *TokenConfig, oldToken *TokenConfig, err error) - CreatePersonalAccessToken(ctx context.Context, username string, userId int, name string, expiresAt time.Time, scopes []string) (*TokenPersonal, error) - CreateGroupAccessToken(ctx context.Context, groupId string, name string, expiresAt time.Time, scopes []string, accessLevel AccessLevel) (*TokenGroup, error) - CreateProjectAccessToken(ctx context.Context, projectId string, name string, expiresAt time.Time, scopes []string, accessLevel AccessLevel) (*TokenProject, error) - RevokePersonalAccessToken(ctx context.Context, tokenId int) error - RevokeProjectAccessToken(ctx context.Context, tokenId int, projectId string) error - RevokeGroupAccessToken(ctx context.Context, tokenId int, groupId string) error - GetUserIdByUsername(ctx context.Context, username string) (int, error) - GetGroupIdByPath(ctx context.Context, path string) (int, error) - GetProjectIdByPath(ctx context.Context, path string) (int, error) - CreateGroupServiceAccountAccessToken(ctx context.Context, group string, groupId string, userId int, name string, expiresAt time.Time, scopes []string) (*TokenGroupServiceAccount, error) - CreateUserServiceAccountAccessToken(ctx context.Context, username string, userId int, name string, expiresAt time.Time, scopes []string) (*TokenUserServiceAccount, error) - RevokeUserServiceAccountAccessToken(ctx context.Context, token string) error - RevokeGroupServiceAccountAccessToken(ctx context.Context, token string) error - CreatePipelineProjectTriggerAccessToken(ctx context.Context, path, name string, projectId int, description string, expiresAt *time.Time) (*TokenPipelineProjectTrigger, error) - RevokePipelineProjectTriggerAccessToken(ctx context.Context, projectId int, tokenId int) error - CreateProjectDeployToken(ctx context.Context, path string, projectId int, name string, expiresAt *time.Time, scopes []string) (et *TokenProjectDeploy, err error) - RevokeProjectDeployToken(ctx context.Context, projectId, deployTokenId int) (err error) - CreateGroupDeployToken(ctx context.Context, path string, groupId int, name string, expiresAt *time.Time, scopes []string) (et *TokenGroupDeploy, err error) - RevokeGroupDeployToken(ctx context.Context, groupId, deployTokenId int) (err error) -} - type gitlabClient struct { client *g.Client httpClient *http.Client @@ -69,7 +47,7 @@ func (gc *gitlabClient) GetProjectIdByPath(ctx context.Context, path string) (pr return projectId, err } -func (gc *gitlabClient) CreateGroupDeployToken(ctx context.Context, path string, groupId int, name string, expiresAt *time.Time, scopes []string) (et *TokenGroupDeploy, err error) { +func (gc *gitlabClient) CreateGroupDeployToken(ctx context.Context, path string, groupId int, name string, expiresAt *time.Time, scopes []string) (et *models.TokenGroupDeploy, err error) { var dt *g.DeployToken defer func() { gc.logger.Debug("Create group deploy token", "groupId", groupId, "name", name, "path", path, "expiresAt", expiresAt, "scopes", scopes, "error", err) @@ -84,15 +62,15 @@ func (gc *gitlabClient) CreateGroupDeployToken(ctx context.Context, path string, }, g.WithContext(ctx), ); err == nil { - et = &TokenGroupDeploy{ - TokenWithScopes: TokenWithScopes{ - Token: Token{ + et = &models.TokenGroupDeploy{ + TokenWithScopes: models.TokenWithScopes{ + Token: models.Token{ TokenID: dt.ID, ParentID: strconv.Itoa(groupId), Path: path, Name: name, Token: dt.Token, - TokenType: TokenTypeGroupDeploy, + TokenType: t.TypeGroupDeploy, CreatedAt: g.Ptr(time.Now()), }, Scopes: scopes, @@ -103,7 +81,7 @@ func (gc *gitlabClient) CreateGroupDeployToken(ctx context.Context, path string, return et, err } -func (gc *gitlabClient) CreateProjectDeployToken(ctx context.Context, path string, projectId int, name string, expiresAt *time.Time, scopes []string) (et *TokenProjectDeploy, err error) { +func (gc *gitlabClient) CreateProjectDeployToken(ctx context.Context, path string, projectId int, name string, expiresAt *time.Time, scopes []string) (et *models.TokenProjectDeploy, err error) { var dt *g.DeployToken defer func() { gc.logger.Debug("Create project deploy token", "projectId", projectId, "name", name, "path", path, "expiresAt", expiresAt, "scopes", scopes, "error", err) @@ -117,15 +95,15 @@ func (gc *gitlabClient) CreateProjectDeployToken(ctx context.Context, path strin }, g.WithContext(ctx), ); err == nil { - et = &TokenProjectDeploy{ - TokenWithScopes: TokenWithScopes{ - Token: Token{ + et = &models.TokenProjectDeploy{ + TokenWithScopes: models.TokenWithScopes{ + Token: models.Token{ TokenID: dt.ID, ParentID: strconv.Itoa(projectId), Path: path, Name: name, Token: dt.Token, - TokenType: TokenTypeProjectDeploy, + TokenType: t.TypeProjectDeploy, CreatedAt: g.Ptr(time.Now()), }, Scopes: scopes, @@ -163,7 +141,7 @@ func (gc *gitlabClient) Metadata(ctx context.Context) (metadata *g.Metadata, err return metadata, err } -func (gc *gitlabClient) CreatePipelineProjectTriggerAccessToken(ctx context.Context, path, name string, projectId int, description string, expiresAt *time.Time) (et *TokenPipelineProjectTrigger, err error) { +func (gc *gitlabClient) CreatePipelineProjectTriggerAccessToken(ctx context.Context, path, name string, projectId int, description string, expiresAt *time.Time) (et *models.TokenPipelineProjectTrigger, err error) { var pt *g.PipelineTrigger defer func() { gc.logger.Debug("Create a pipeline project trigger access token", "path", path, "name", name, "projectId", description, "description", "error", err) @@ -174,14 +152,14 @@ func (gc *gitlabClient) CreatePipelineProjectTriggerAccessToken(ctx context.Cont &g.AddPipelineTriggerOptions{Description: &description}, g.WithContext(ctx), ); err == nil { - et = &TokenPipelineProjectTrigger{ - Token: Token{ + et = &models.TokenPipelineProjectTrigger{ + Token: models.Token{ TokenID: pt.ID, ParentID: strconv.Itoa(projectId), Path: path, Name: name, Token: pt.Token, - TokenType: TokenTypePipelineProjectTrigger, + TokenType: t.TypePipelineProjectTrigger, CreatedAt: g.Ptr(time.Now()), ExpiresAt: expiresAt, }, @@ -214,7 +192,7 @@ func (gc *gitlabClient) GetGroupIdByPath(ctx context.Context, path string) (grou return 0, fmt.Errorf("%v", err) } if len(groups) == 0 { - return 0, fmt.Errorf("path '%s' not found: %w", path, ErrInvalidValue) + return 0, fmt.Errorf("path '%s' not found: %w", path, errs.ErrInvalidValue) } groupId = groups[0].ID return groupId, nil @@ -225,7 +203,7 @@ func (gc *gitlabClient) GitlabClient(ctx context.Context) *g.Client { return gc.client } -func (gc *gitlabClient) CreateGroupServiceAccountAccessToken(ctx context.Context, path string, groupId string, userId int, name string, expiresAt time.Time, scopes []string) (et *TokenGroupServiceAccount, err error) { +func (gc *gitlabClient) CreateGroupServiceAccountAccessToken(ctx context.Context, path string, groupId string, userId int, name string, expiresAt time.Time, scopes []string) (et *models.TokenGroupServiceAccount, err error) { var at *g.PersonalAccessToken defer func() { gc.logger.Debug("Create group service access token", "pat", at, "et", et, "path", path, "groupId", groupId, "userId", userId, "name", name, "expiresAt", expiresAt, "scopes", scopes, "error", err) @@ -236,15 +214,15 @@ func (gc *gitlabClient) CreateGroupServiceAccountAccessToken(ctx context.Context Scopes: &scopes, }, g.WithContext(ctx)) if err == nil { - et = &TokenGroupServiceAccount{ - TokenWithScopes: TokenWithScopes{ - Token: Token{ + et = &models.TokenGroupServiceAccount{ + TokenWithScopes: models.TokenWithScopes{ + Token: models.Token{ TokenID: at.ID, ParentID: groupId, Path: path, Name: name, Token: at.Token, - TokenType: TokenTypeGroupServiceAccount, + TokenType: t.TypeGroupServiceAccount, CreatedAt: at.CreatedAt, ExpiresAt: (*time.Time)(at.ExpiresAt), }, @@ -256,22 +234,22 @@ func (gc *gitlabClient) CreateGroupServiceAccountAccessToken(ctx context.Context return et, err } -func (gc *gitlabClient) CreateUserServiceAccountAccessToken(ctx context.Context, username string, userId int, name string, expiresAt time.Time, scopes []string) (et *TokenUserServiceAccount, err error) { +func (gc *gitlabClient) CreateUserServiceAccountAccessToken(ctx context.Context, username string, userId int, name string, expiresAt time.Time, scopes []string) (et *models.TokenUserServiceAccount, err error) { defer func() { gc.logger.Debug("Create user service access token", "et", et, "username", username, "userId", userId, "name", name, "expiresAt", expiresAt, "scopes", scopes, "error", err) }() - var etp *TokenPersonal + var etp *models.TokenPersonal etp, err = gc.CreatePersonalAccessToken(ctx, username, userId, name, expiresAt, scopes) if err == nil && etp != nil { - et = &TokenUserServiceAccount{ - TokenWithScopes: TokenWithScopes{ - Token: Token{ + et = &models.TokenUserServiceAccount{ + TokenWithScopes: models.TokenWithScopes{ + Token: models.Token{ TokenID: etp.TokenID, ParentID: etp.ParentID, Path: etp.Path, Name: etp.Name, Token: etp.Token.Token, - TokenType: TokenTypeUserServiceAccount, + TokenType: t.TypeUserServiceAccount, CreatedAt: etp.CreatedAt, ExpiresAt: etp.ExpiresAt, }, @@ -285,7 +263,7 @@ func (gc *gitlabClient) CreateUserServiceAccountAccessToken(ctx context.Context, func (gc *gitlabClient) RevokeUserServiceAccountAccessToken(ctx context.Context, token string) (err error) { defer func() { gc.logger.Debug("Revoke user service account token", "token", token, "error", err) }() if token == "" { - err = fmt.Errorf("%w: empty token", ErrNilValue) + err = fmt.Errorf("%w: empty token", errs.ErrNilValue) return err } @@ -303,7 +281,7 @@ func (gc *gitlabClient) RevokeUserServiceAccountAccessToken(ctx context.Context, func (gc *gitlabClient) RevokeGroupServiceAccountAccessToken(ctx context.Context, token string) (err error) { defer func() { gc.logger.Debug("Revoke group service account token", "token", token, "error", err) }() if token == "" { - err = fmt.Errorf("%w: empty token", ErrNilValue) + err = fmt.Errorf("%w: empty token", errs.ErrNilValue) return err } @@ -318,17 +296,17 @@ func (gc *gitlabClient) RevokeGroupServiceAccountAccessToken(ctx context.Context return err } -func (gc *gitlabClient) CurrentTokenInfo(ctx context.Context) (et *TokenConfig, err error) { +func (gc *gitlabClient) CurrentTokenInfo(ctx context.Context) (et *models.TokenConfig, err error) { var pat *g.PersonalAccessToken defer func() { gc.logger.Debug("Current token info", "token", et, "error", err) }() if pat, _, err = gc.client.PersonalAccessTokens.GetSinglePersonalAccessToken(g.WithContext(ctx)); err == nil { - et = &TokenConfig{ - TokenWithScopes: TokenWithScopes{ - Token: Token{ + et = &models.TokenConfig{ + TokenWithScopes: models.TokenWithScopes{ + Token: models.Token{ TokenID: pat.ID, Name: pat.Name, Token: pat.Token, - TokenType: TokenTypePersonal, + TokenType: t.TypePersonal, CreatedAt: pat.CreatedAt, ExpiresAt: (*time.Time)(pat.ExpiresAt), }, @@ -347,7 +325,7 @@ func (gc *gitlabClient) CurrentTokenInfo(ctx context.Context) (et *TokenConfig, return et, err } -func (gc *gitlabClient) RotateCurrentToken(ctx context.Context) (token *TokenConfig, currentEntryToken *TokenConfig, err error) { +func (gc *gitlabClient) RotateCurrentToken(ctx context.Context) (token *models.TokenConfig, currentEntryToken *models.TokenConfig, err error) { var expiresAt time.Time defer func() { gc.logger.Debug("Rotate current token", "token", token, "currentEntryToken", currentEntryToken, "expiresAt", expiresAt, "error", err) @@ -366,7 +344,7 @@ func (gc *gitlabClient) RotateCurrentToken(ctx context.Context) (token *TokenCon var pat *g.PersonalAccessToken var durationTTL = currentEntryToken.ExpiresAt.Sub(*currentEntryToken.CreatedAt) - _, expiresAt, _ = calculateGitlabTTL(durationTTL, TimeFromContext(ctx)) + _, expiresAt, _ = utils.CalculateGitlabTTL(durationTTL, utils.TimeFromContext(ctx)) pat, _, err = gc.client.PersonalAccessTokens.RotatePersonalAccessToken( currentEntryToken.TokenID, &g.RotatePersonalAccessTokenOptions{ExpiresAt: (*g.ISOTime)(&expiresAt)}, @@ -376,15 +354,15 @@ func (gc *gitlabClient) RotateCurrentToken(ctx context.Context) (token *TokenCon return nil, nil, err } - token = &TokenConfig{ - TokenWithScopes: TokenWithScopes{ - Token: Token{ + token = &models.TokenConfig{ + TokenWithScopes: models.TokenWithScopes{ + Token: models.Token{ TokenID: pat.ID, ParentID: "", Path: usr.Username, Name: pat.Name, Token: pat.Token, - TokenType: TokenTypePersonal, + TokenType: t.TypePersonal, CreatedAt: pat.CreatedAt, ExpiresAt: (*time.Time)(pat.ExpiresAt), }, @@ -421,13 +399,13 @@ func (gc *gitlabClient) GetUserIdByUsername(ctx context.Context, username string return 0, fmt.Errorf("%v", err) } if len(u) == 0 { - return 0, fmt.Errorf("username '%s' not found: %w", username, ErrInvalidValue) + return 0, fmt.Errorf("username '%s' not found: %w", username, errs.ErrInvalidValue) } userId = u[0].ID return userId, nil } -func (gc *gitlabClient) CreatePersonalAccessToken(ctx context.Context, username string, userId int, name string, expiresAt time.Time, scopes []string) (et *TokenPersonal, err error) { +func (gc *gitlabClient) CreatePersonalAccessToken(ctx context.Context, username string, userId int, name string, expiresAt time.Time, scopes []string) (et *models.TokenPersonal, err error) { var at *g.PersonalAccessToken defer func() { gc.logger.Debug("Create personal access token", "pat", at, "et", et, "username", username, "userId", userId, "name", name, "expiresAt", expiresAt, "scopes", scopes, "error", err) @@ -437,14 +415,14 @@ func (gc *gitlabClient) CreatePersonalAccessToken(ctx context.Context, username ExpiresAt: (*g.ISOTime)(&expiresAt), Scopes: &scopes, }, g.WithContext(ctx)); err == nil { - et = &TokenPersonal{ - TokenWithScopes: TokenWithScopes{ - Token: Token{ + et = &models.TokenPersonal{ + TokenWithScopes: models.TokenWithScopes{ + Token: models.Token{ TokenID: at.ID, Path: username, Name: name, Token: at.Token, - TokenType: TokenTypePersonal, + TokenType: t.TypePersonal, CreatedAt: at.CreatedAt, ExpiresAt: (*time.Time)(at.ExpiresAt), }, @@ -456,7 +434,7 @@ func (gc *gitlabClient) CreatePersonalAccessToken(ctx context.Context, username return et, err } -func (gc *gitlabClient) CreateGroupAccessToken(ctx context.Context, groupId string, name string, expiresAt time.Time, scopes []string, accessLevel AccessLevel) (et *TokenGroup, err error) { +func (gc *gitlabClient) CreateGroupAccessToken(ctx context.Context, groupId string, name string, expiresAt time.Time, scopes []string, accessLevel t.AccessLevel) (et *models.TokenGroup, err error) { var at *g.GroupAccessToken defer func() { gc.logger.Debug("Create group access token", "gat", at, "et", et, "groupId", groupId, "name", name, "expiresAt", expiresAt, "scopes", scopes, "accessLevel", accessLevel, "error", err) @@ -469,15 +447,15 @@ func (gc *gitlabClient) CreateGroupAccessToken(ctx context.Context, groupId stri ExpiresAt: (*g.ISOTime)(&expiresAt), AccessLevel: al, }, g.WithContext(ctx)); err == nil { - et = &TokenGroup{ - TokenWithScopesAndAccessLevel: TokenWithScopesAndAccessLevel{ - Token: Token{ + et = &models.TokenGroup{ + TokenWithScopesAndAccessLevel: models.TokenWithScopesAndAccessLevel{ + Token: models.Token{ TokenID: at.ID, ParentID: groupId, Path: groupId, Name: name, Token: at.Token, - TokenType: TokenTypeGroup, + TokenType: t.TypeGroup, CreatedAt: at.CreatedAt, ExpiresAt: (*time.Time)(at.ExpiresAt), }, @@ -489,7 +467,7 @@ func (gc *gitlabClient) CreateGroupAccessToken(ctx context.Context, groupId stri return et, err } -func (gc *gitlabClient) CreateProjectAccessToken(ctx context.Context, projectId string, name string, expiresAt time.Time, scopes []string, accessLevel AccessLevel) (et *TokenProject, err error) { +func (gc *gitlabClient) CreateProjectAccessToken(ctx context.Context, projectId string, name string, expiresAt time.Time, scopes []string, accessLevel t.AccessLevel) (et *models.TokenProject, err error) { var at *g.ProjectAccessToken defer func() { gc.logger.Debug("Create project access token", "gat", at, "et", et, "projectId", projectId, "name", name, "expiresAt", expiresAt, "scopes", scopes, "accessLevel", accessLevel, "error", err) @@ -502,15 +480,15 @@ func (gc *gitlabClient) CreateProjectAccessToken(ctx context.Context, projectId ExpiresAt: (*g.ISOTime)(&expiresAt), AccessLevel: al, }, g.WithContext(ctx)); err == nil { - et = &TokenProject{ - TokenWithScopesAndAccessLevel: TokenWithScopesAndAccessLevel{ - Token: Token{ + et = &models.TokenProject{ + TokenWithScopesAndAccessLevel: models.TokenWithScopesAndAccessLevel{ + Token: models.Token{ TokenID: at.ID, ParentID: projectId, Path: projectId, Name: name, Token: at.Token, - TokenType: TokenTypeProject, + TokenType: t.TypeProject, CreatedAt: at.CreatedAt, ExpiresAt: (*time.Time)(at.ExpiresAt), }, @@ -575,11 +553,11 @@ var _ Client = new(gitlabClient) func newGitlabClient(config *EntryConfig, httpClient *http.Client) (gc *g.Client, err error) { if strings.TrimSpace(config.BaseURL) == "" { - err = errors.Join(err, fmt.Errorf("gitlab base url: %w", ErrInvalidValue)) + err = errors.Join(err, fmt.Errorf("gitlab base url: %w", errs.ErrInvalidValue)) } if strings.TrimSpace(config.Token) == "" { - err = errors.Join(err, fmt.Errorf("gitlab token: %w", ErrInvalidValue)) + err = errors.Join(err, fmt.Errorf("gitlab token: %w", errs.ErrInvalidValue)) } if err != nil { @@ -600,7 +578,7 @@ func newGitlabClient(config *EntryConfig, httpClient *http.Client) (gc *g.Client func NewGitlabClient(config *EntryConfig, httpClient *http.Client, logger hclog.Logger) (client Client, err error) { if config == nil { - return nil, fmt.Errorf("configure the backend first, config: %w", ErrNilValue) + return nil, fmt.Errorf("configure the backend first, config: %w", errs.ErrNilValue) } if logger == nil { diff --git a/gitlab_client_iface.go b/gitlab_client_iface.go new file mode 100644 index 0000000..76adf0e --- /dev/null +++ b/gitlab_client_iface.go @@ -0,0 +1,38 @@ +package gitlab + +import ( + "context" + "time" + + g "gitlab.com/gitlab-org/api/client-go" + + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/models" + t "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/token" +) + +type Client interface { + GitlabClient(ctx context.Context) *g.Client + Valid(ctx context.Context) bool + Metadata(ctx context.Context) (*g.Metadata, error) + CurrentTokenInfo(ctx context.Context) (*models.TokenConfig, error) + RotateCurrentToken(ctx context.Context) (newToken *models.TokenConfig, oldToken *models.TokenConfig, err error) + CreatePersonalAccessToken(ctx context.Context, username string, userId int, name string, expiresAt time.Time, scopes []string) (*models.TokenPersonal, error) + CreateGroupAccessToken(ctx context.Context, groupId string, name string, expiresAt time.Time, scopes []string, accessLevel t.AccessLevel) (*models.TokenGroup, error) + CreateProjectAccessToken(ctx context.Context, projectId string, name string, expiresAt time.Time, scopes []string, accessLevel t.AccessLevel) (*models.TokenProject, error) + RevokePersonalAccessToken(ctx context.Context, tokenId int) error + RevokeProjectAccessToken(ctx context.Context, tokenId int, projectId string) error + RevokeGroupAccessToken(ctx context.Context, tokenId int, groupId string) error + GetUserIdByUsername(ctx context.Context, username string) (int, error) + GetGroupIdByPath(ctx context.Context, path string) (int, error) + GetProjectIdByPath(ctx context.Context, path string) (int, error) + CreateGroupServiceAccountAccessToken(ctx context.Context, group string, groupId string, userId int, name string, expiresAt time.Time, scopes []string) (*models.TokenGroupServiceAccount, error) + CreateUserServiceAccountAccessToken(ctx context.Context, username string, userId int, name string, expiresAt time.Time, scopes []string) (*models.TokenUserServiceAccount, error) + RevokeUserServiceAccountAccessToken(ctx context.Context, token string) error + RevokeGroupServiceAccountAccessToken(ctx context.Context, token string) error + CreatePipelineProjectTriggerAccessToken(ctx context.Context, path, name string, projectId int, description string, expiresAt *time.Time) (*models.TokenPipelineProjectTrigger, error) + RevokePipelineProjectTriggerAccessToken(ctx context.Context, projectId int, tokenId int) error + CreateProjectDeployToken(ctx context.Context, path string, projectId int, name string, expiresAt *time.Time, scopes []string) (et *models.TokenProjectDeploy, err error) + RevokeProjectDeployToken(ctx context.Context, projectId, deployTokenId int) (err error) + CreateGroupDeployToken(ctx context.Context, path string, groupId int, name string, expiresAt *time.Time, scopes []string) (et *models.TokenGroupDeploy, err error) + RevokeGroupDeployToken(ctx context.Context, groupId, deployTokenId int) (err error) +} diff --git a/gitlab_client_test.go b/gitlab_client_test.go index a49ec3c..365d64a 100644 --- a/gitlab_client_test.go +++ b/gitlab_client_test.go @@ -13,24 +13,26 @@ import ( "github.com/stretchr/testify/require" gitlab "github.com/ilijamt/vault-plugin-secrets-gitlab" + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/errs" + token2 "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/token" ) func TestGitlabClient(t *testing.T) { t.Run("nil config", func(t *testing.T) { client, err := gitlab.NewGitlabClient(nil, nil, nil) require.Nil(t, client) - require.ErrorIs(t, err, gitlab.ErrNilValue) + require.ErrorIs(t, err, errs.ErrNilValue) }) t.Run("no token", func(t *testing.T) { var client, err = gitlab.NewGitlabClient(&gitlab.EntryConfig{}, nil, nil) - require.ErrorIs(t, err, gitlab.ErrInvalidValue) + require.ErrorIs(t, err, errs.ErrInvalidValue) require.Nil(t, client) }) t.Run("no base url", func(t *testing.T) { var client, err = gitlab.NewGitlabClient(&gitlab.EntryConfig{}, nil, nil) - require.ErrorIs(t, err, gitlab.ErrInvalidValue) + require.ErrorIs(t, err, errs.ErrInvalidValue) require.Nil(t, client) }) @@ -51,8 +53,8 @@ func TestGitlabClient(t *testing.T) { }, &http.Client{}, nil) require.NoError(t, err) require.NotNil(t, client) - require.ErrorIs(t, client.RevokeGroupServiceAccountAccessToken(ctx, ""), gitlab.ErrNilValue) - require.ErrorIs(t, client.RevokeUserServiceAccountAccessToken(ctx, ""), gitlab.ErrNilValue) + require.ErrorIs(t, client.RevokeGroupServiceAccountAccessToken(ctx, ""), errs.ErrNilValue) + require.ErrorIs(t, client.RevokeUserServiceAccountAccessToken(ctx, ""), errs.ErrNilValue) }) } @@ -87,11 +89,11 @@ func TestGitlabClient_InvalidToken(t *testing.T) { _, err = client.GetUserIdByUsername(ctx, "username") require.Error(t, err) - gatToken, err := client.CreateGroupAccessToken(ctx, "groupId", "name", timeExpiresAt, []string{"scope"}, gitlab.AccessLevelUnknown) + gatToken, err := client.CreateGroupAccessToken(ctx, "groupId", "name", timeExpiresAt, []string{"scope"}, token2.AccessLevelUnknown) require.Error(t, err) require.Nil(t, gatToken) - prjAtToken, err := client.CreateProjectAccessToken(ctx, "projectId", "name", timeExpiresAt, []string{"scope"}, gitlab.AccessLevelUnknown) + prjAtToken, err := client.CreateProjectAccessToken(ctx, "projectId", "name", timeExpiresAt, []string{"scope"}, token2.AccessLevelUnknown) require.Error(t, err) require.Nil(t, prjAtToken) @@ -138,7 +140,7 @@ func TestGitlabClient_GetGroupIdByPath(t *testing.T) { require.EqualValues(t, 3, groupId) _, err = client.GetGroupIdByPath(ctx, "nonexistent") - require.ErrorIs(t, err, gitlab.ErrInvalidValue) + require.ErrorIs(t, err, errs.ErrInvalidValue) } func TestGitlabClient_GetUserIdByUsername(t *testing.T) { @@ -173,11 +175,11 @@ func TestGitlabClient_GetUserIdByUsernameDoesNotMatch(t *testing.T) { require.True(t, client.Valid(ctx)) userId, err := client.GetUserIdByUsername(ctx, "ilijamt") - require.ErrorIs(t, err, gitlab.ErrInvalidValue) + require.ErrorIs(t, err, errs.ErrInvalidValue) require.NotEqualValues(t, 1, userId) userId, err = client.GetUserIdByUsername(ctx, "demo") - require.ErrorIs(t, err, gitlab.ErrInvalidValue) + require.ErrorIs(t, err, errs.ErrInvalidValue) require.NotEqualValues(t, 1, userId) } @@ -215,7 +217,7 @@ func TestGitlabClient_CurrentTokenInfo(t *testing.T) { token, err := client.CurrentTokenInfo(ctx) require.NoError(t, err) require.NotNil(t, token) - assert.EqualValues(t, gitlab.TokenTypePersonal, token.TokenType) + assert.EqualValues(t, token2.TypePersonal, token.TokenType) } func TestGitlabClient_Metadata(t *testing.T) { @@ -255,12 +257,12 @@ func TestGitlabClient_CreateAccessToken_And_Revoke(t *testing.T) { "example", "name", timeExpiresAt, - []string{gitlab.TokenScopeReadApi.String()}, - gitlab.AccessLevelGuestPermissions, + []string{token2.ScopeReadApi.String()}, + token2.AccessLevelGuestPermissions, ) require.NoError(t, err) require.NotNil(t, gatToken) - require.EqualValues(t, gitlab.TokenTypeGroup, gatToken.TokenType) + require.EqualValues(t, token2.TypeGroup, gatToken.TokenType) require.NotEmpty(t, gatToken.Token) require.NoError(t, client.RevokeGroupAccessToken(ctx, gatToken.TokenID, "example")) @@ -269,12 +271,12 @@ func TestGitlabClient_CreateAccessToken_And_Revoke(t *testing.T) { "example/example", "name", timeExpiresAt, - []string{gitlab.TokenScopeReadApi.String()}, - gitlab.AccessLevelDeveloperPermissions, + []string{token2.ScopeReadApi.String()}, + token2.AccessLevelDeveloperPermissions, ) require.NoError(t, err) require.NotNil(t, prjatToken) - require.EqualValues(t, gitlab.TokenTypeProject, prjatToken.TokenType) + require.EqualValues(t, token2.TypeProject, prjatToken.TokenType) require.NotEmpty(t, prjatToken.Token) require.NoError(t, client.RevokeProjectAccessToken(ctx, prjatToken.TokenID, "example/example")) @@ -284,11 +286,11 @@ func TestGitlabClient_CreateAccessToken_And_Revoke(t *testing.T) { 1, "name", timeExpiresAt, - []string{gitlab.TokenScopeReadApi.String()}, + []string{token2.ScopeReadApi.String()}, ) require.NoError(t, err) require.NotNil(t, patToken) - require.EqualValues(t, gitlab.TokenTypePersonal, patToken.TokenType) + require.EqualValues(t, token2.TypePersonal, patToken.TokenType) require.NotEmpty(t, patToken.Token) require.NoError(t, client.RevokePersonalAccessToken(ctx, patToken.TokenID)) } diff --git a/gitlab_type.go b/gitlab_type.go deleted file mode 100644 index c8c998e..0000000 --- a/gitlab_type.go +++ /dev/null @@ -1,41 +0,0 @@ -package gitlab - -import ( - "errors" - "fmt" - "slices" -) - -type Type string - -const ( - TypeSaaS Type = "saas" - TypeDedicated Type = "dedicated" - TypeSelfManaged Type = "self-managed" - TypeUnknown = Type("") -) - -var ( - ErrUnknownType = errors.New("unknown gitlab type") - - validGitlabTypes = []string{ - TypeSaaS.String(), - TypeSelfManaged.String(), - TypeDedicated.String(), - } -) - -func (i Type) String() string { - return string(i) -} - -func (i Type) Value() string { - return i.String() -} - -func TypeParse(value string) (Type, error) { - if slices.Contains(validGitlabTypes, value) { - return Type(value), nil - } - return TypeUnknown, fmt.Errorf("failed to parse '%s': %w", value, ErrUnknownType) -} diff --git a/helpers_test.go b/helpers_test.go index 32c26c0..f093a4d 100644 --- a/helpers_test.go +++ b/helpers_test.go @@ -27,6 +27,10 @@ import ( g "gitlab.com/gitlab-org/api/client-go" gitlab "github.com/ilijamt/vault-plugin-secrets-gitlab" + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/flags" + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/models" + t "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/token" + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/utils" ) var _ gitlab.Client = new(inMemoryClient) @@ -93,10 +97,10 @@ func (m *mockEventsSender) expectEvents(t *testing.T, expectedEvents []expectedE } func getBackendWithEvents(ctx context.Context) (*gitlab.Backend, logical.Storage, *mockEventsSender, error) { - return getBackendWithFlagsWithEvents(ctx, gitlab.Flags{}) + return getBackendWithFlagsWithEvents(ctx, flags.Flags{}) } -func getBackendWithFlagsWithEvents(ctx context.Context, flags gitlab.Flags) (*gitlab.Backend, logical.Storage, *mockEventsSender, error) { +func getBackendWithFlagsWithEvents(ctx context.Context, flags flags.Flags) (*gitlab.Backend, logical.Storage, *mockEventsSender, error) { events := &mockEventsSender{} config := &logical.BackendConfig{ Logger: logging.NewVaultLoggerWithWriter(io.Discard, log.NoLevel), @@ -151,19 +155,19 @@ func newInMemoryClient(valid bool) *inMemoryClient { return &inMemoryClient{ users: make([]string, 0), valid: valid, - accessTokens: make(map[string]gitlab.IToken), + accessTokens: make(map[string]t.Token), - mainTokenInfo: gitlab.TokenConfig{ - TokenWithScopes: gitlab.TokenWithScopes{ - Token: gitlab.Token{ + mainTokenInfo: models.TokenConfig{ + TokenWithScopes: models.TokenWithScopes{ + Token: models.Token{ CreatedAt: g.Ptr(time.Now()), ExpiresAt: g.Ptr(time.Now()), }, }, }, - rotateMainToken: gitlab.TokenConfig{ - TokenWithScopes: gitlab.TokenWithScopes{ - Token: gitlab.Token{ + rotateMainToken: models.TokenConfig{ + TokenWithScopes: models.TokenWithScopes{ + Token: models.Token{ CreatedAt: g.Ptr(time.Now()), ExpiresAt: g.Ptr(time.Now()), }, @@ -202,10 +206,10 @@ type inMemoryClient struct { calledRotateMainToken int calledValid int - mainTokenInfo gitlab.TokenConfig - rotateMainToken gitlab.TokenConfig + mainTokenInfo models.TokenConfig + rotateMainToken models.TokenConfig - accessTokens map[string]gitlab.IToken + accessTokens map[string]t.Token valueGetProjectIdByPath int } @@ -217,7 +221,7 @@ func (i *inMemoryClient) GetProjectIdByPath(ctx context.Context, path string) (i return i.valueGetProjectIdByPath, nil } -func (i *inMemoryClient) CreateProjectDeployToken(ctx context.Context, path string, projectId int, name string, expiresAt *time.Time, scopes []string) (et *gitlab.TokenProjectDeploy, err error) { +func (i *inMemoryClient) CreateProjectDeployToken(ctx context.Context, path string, projectId int, name string, expiresAt *time.Time, scopes []string) (et *models.TokenProjectDeploy, err error) { i.muLock.Lock() defer i.muLock.Unlock() if i.createProjectDeployTokenError { @@ -225,16 +229,16 @@ func (i *inMemoryClient) CreateProjectDeployToken(ctx context.Context, path stri } i.internalCounter++ var tokenId = i.internalCounter - key := fmt.Sprintf("%s_%v_%v", gitlab.TokenTypeProjectDeploy.String(), projectId, tokenId) - var entryToken = &gitlab.TokenProjectDeploy{ - TokenWithScopes: gitlab.TokenWithScopes{ - Token: gitlab.Token{ + key := fmt.Sprintf("%s_%v_%v", t.TypeProjectDeploy.String(), projectId, tokenId) + var entryToken = &models.TokenProjectDeploy{ + TokenWithScopes: models.TokenWithScopes{ + Token: models.Token{ TokenID: tokenId, ParentID: strconv.Itoa(projectId), Path: path, Name: name, Token: fmt.Sprintf("glpat-%s", uuid.New().String()), - TokenType: gitlab.TokenTypeProjectDeploy, + TokenType: t.TypeProjectDeploy, ExpiresAt: expiresAt, CreatedAt: g.Ptr(time.Now())}, Scopes: scopes, @@ -245,7 +249,7 @@ func (i *inMemoryClient) CreateProjectDeployToken(ctx context.Context, path stri return entryToken, nil } -func (i *inMemoryClient) CreateGroupDeployToken(ctx context.Context, path string, groupId int, name string, expiresAt *time.Time, scopes []string) (et *gitlab.TokenGroupDeploy, err error) { +func (i *inMemoryClient) CreateGroupDeployToken(ctx context.Context, path string, groupId int, name string, expiresAt *time.Time, scopes []string) (et *models.TokenGroupDeploy, err error) { i.muLock.Lock() defer i.muLock.Unlock() if i.createGroupDeployTokenError { @@ -253,16 +257,16 @@ func (i *inMemoryClient) CreateGroupDeployToken(ctx context.Context, path string } i.internalCounter++ var tokenId = i.internalCounter - key := fmt.Sprintf("%s_%v_%v", gitlab.TokenTypeGroupDeploy.String(), groupId, tokenId) - var entryToken = &gitlab.TokenGroupDeploy{ - TokenWithScopes: gitlab.TokenWithScopes{ - Token: gitlab.Token{ + key := fmt.Sprintf("%s_%v_%v", t.TypeGroupDeploy.String(), groupId, tokenId) + var entryToken = &models.TokenGroupDeploy{ + TokenWithScopes: models.TokenWithScopes{ + Token: models.Token{ TokenID: tokenId, ParentID: strconv.Itoa(groupId), Path: path, Name: name, Token: fmt.Sprintf("glpat-%s", uuid.New().String()), - TokenType: gitlab.TokenTypeGroupDeploy, + TokenType: t.TypeGroupDeploy, ExpiresAt: expiresAt, CreatedAt: g.Ptr(time.Now()), }, @@ -280,7 +284,7 @@ func (i *inMemoryClient) RevokeProjectDeployToken(ctx context.Context, projectId if i.revokeProjectDeployTokenError { return errors.New("revoke project deploy token error") } - key := fmt.Sprintf("%s_%v_%v", gitlab.TokenTypeProjectDeploy.String(), projectId, deployTokenId) + key := fmt.Sprintf("%s_%v_%v", t.TypeProjectDeploy.String(), projectId, deployTokenId) delete(i.accessTokens, key) return nil } @@ -291,7 +295,7 @@ func (i *inMemoryClient) RevokeGroupDeployToken(ctx context.Context, groupId, de if i.revokeGroupDeployTokenError { return errors.New("revoke group deploy token error") } - key := fmt.Sprintf("%s_%v_%v", gitlab.TokenTypeGroupDeploy.String(), groupId, deployTokenId) + key := fmt.Sprintf("%s_%v_%v", t.TypeGroupDeploy.String(), groupId, deployTokenId) delete(i.accessTokens, key) return nil } @@ -307,7 +311,7 @@ func (i *inMemoryClient) Metadata(ctx context.Context) (*g.Metadata, error) { }, nil } -func (i *inMemoryClient) CreatePipelineProjectTriggerAccessToken(ctx context.Context, path, name string, projectId int, description string, expiresAt *time.Time) (et *gitlab.TokenPipelineProjectTrigger, err error) { +func (i *inMemoryClient) CreatePipelineProjectTriggerAccessToken(ctx context.Context, path, name string, projectId int, description string, expiresAt *time.Time) (et *models.TokenPipelineProjectTrigger, err error) { i.muLock.Lock() defer i.muLock.Unlock() if i.createPipelineProjectTriggerAccessTokenError { @@ -315,15 +319,15 @@ func (i *inMemoryClient) CreatePipelineProjectTriggerAccessToken(ctx context.Con } i.internalCounter++ var tokenId = i.internalCounter - key := fmt.Sprintf("%s_%v_%v", gitlab.TokenTypePipelineProjectTrigger.String(), projectId, tokenId) - var entryToken = &gitlab.TokenPipelineProjectTrigger{ - Token: gitlab.Token{ + key := fmt.Sprintf("%s_%v_%v", t.TypePipelineProjectTrigger.String(), projectId, tokenId) + var entryToken = &models.TokenPipelineProjectTrigger{ + Token: models.Token{ TokenID: tokenId, ParentID: strconv.Itoa(projectId), Path: strconv.Itoa(projectId), Name: name, Token: fmt.Sprintf("glptt-%s", uuid.New().String()), - TokenType: gitlab.TokenTypePipelineProjectTrigger, + TokenType: t.TypePipelineProjectTrigger, ExpiresAt: expiresAt, CreatedAt: g.Ptr(time.Now()), }, @@ -338,7 +342,7 @@ func (i *inMemoryClient) RevokePipelineProjectTriggerAccessToken(ctx context.Con if i.revokePipelineProjectTriggerAccessTokenError { return fmt.Errorf("RevokePipelineProjectTriggerAccessToken") } - key := fmt.Sprintf("%s_%v_%v", gitlab.TokenTypePipelineProjectTrigger.String(), projectId, tokenId) + key := fmt.Sprintf("%s_%v_%v", t.TypePipelineProjectTrigger.String(), projectId, tokenId) delete(i.accessTokens, key) return nil } @@ -356,7 +360,7 @@ func (i *inMemoryClient) GitlabClient(ctx context.Context) *g.Client { return nil } -func (i *inMemoryClient) CreateGroupServiceAccountAccessToken(ctx context.Context, path string, groupId string, userId int, name string, expiresAt time.Time, scopes []string) (*gitlab.TokenGroupServiceAccount, error) { +func (i *inMemoryClient) CreateGroupServiceAccountAccessToken(ctx context.Context, path string, groupId string, userId int, name string, expiresAt time.Time, scopes []string) (*models.TokenGroupServiceAccount, error) { i.muLock.Lock() defer i.muLock.Unlock() if i.createGroupServiceAccountAccessTokenError { @@ -365,23 +369,23 @@ func (i *inMemoryClient) CreateGroupServiceAccountAccessToken(ctx context.Contex return nil, nil } -func (i *inMemoryClient) CreateUserServiceAccountAccessToken(ctx context.Context, username string, userId int, name string, expiresAt time.Time, scopes []string) (*gitlab.TokenUserServiceAccount, error) { +func (i *inMemoryClient) CreateUserServiceAccountAccessToken(ctx context.Context, username string, userId int, name string, expiresAt time.Time, scopes []string) (*models.TokenUserServiceAccount, error) { i.muLock.Lock() if i.createUserServiceAccountAccessTokenError { i.muLock.Unlock() return nil, fmt.Errorf("CreateUserServiceAccountAccessToken") } i.muLock.Unlock() - var t *gitlab.TokenUserServiceAccount + var tok *models.TokenUserServiceAccount var err error - var cpat *gitlab.TokenPersonal + var cpat *models.TokenPersonal if cpat, err = i.CreatePersonalAccessToken(ctx, username, userId, name, expiresAt, scopes); err != nil && cpat != nil { - t = &gitlab.TokenUserServiceAccount{ - TokenWithScopes: gitlab.TokenWithScopes{ - Token: gitlab.Token{ + tok = &models.TokenUserServiceAccount{ + TokenWithScopes: models.TokenWithScopes{ + Token: models.Token{ CreatedAt: cpat.CreatedAt, ExpiresAt: cpat.ExpiresAt, - TokenType: gitlab.TokenTypeUserServiceAccount, + TokenType: t.TypeUserServiceAccount, Token: cpat.Token.Token, TokenID: cpat.TokenID, ParentID: cpat.ParentID, @@ -393,7 +397,7 @@ func (i *inMemoryClient) CreateUserServiceAccountAccessToken(ctx context.Context } } - return t, err + return tok, err } func (i *inMemoryClient) RevokeUserServiceAccountAccessToken(ctx context.Context, token string) error { @@ -402,7 +406,7 @@ func (i *inMemoryClient) RevokeUserServiceAccountAccessToken(ctx context.Context if i.revokeUserServiceAccountPersonalAccessTokenError { return errors.New("RevokeServiceAccountPersonalAccessToken") } - delete(i.accessTokens, fmt.Sprintf("%s_%v", gitlab.TokenTypeUserServiceAccount.String(), token)) + delete(i.accessTokens, fmt.Sprintf("%s_%v", t.TypeUserServiceAccount.String(), token)) return nil } @@ -412,18 +416,18 @@ func (i *inMemoryClient) RevokeGroupServiceAccountAccessToken(ctx context.Contex if i.revokeGroupServiceAccountPersonalAccessTokenError { return errors.New("RevokeServiceAccountPersonalAccessToken") } - delete(i.accessTokens, fmt.Sprintf("%s_%v", gitlab.TokenTypeGroupServiceAccount.String(), token)) + delete(i.accessTokens, fmt.Sprintf("%s_%v", t.TypeGroupServiceAccount.String(), token)) return nil } -func (i *inMemoryClient) CurrentTokenInfo(ctx context.Context) (*gitlab.TokenConfig, error) { +func (i *inMemoryClient) CurrentTokenInfo(ctx context.Context) (*models.TokenConfig, error) { i.muLock.Lock() defer i.muLock.Unlock() i.calledMainToken++ return &i.mainTokenInfo, nil } -func (i *inMemoryClient) RotateCurrentToken(ctx context.Context) (*gitlab.TokenConfig, *gitlab.TokenConfig, error) { +func (i *inMemoryClient) RotateCurrentToken(ctx context.Context) (*models.TokenConfig, *models.TokenConfig, error) { i.muLock.Lock() defer i.muLock.Unlock() i.calledRotateMainToken++ @@ -437,7 +441,7 @@ func (i *inMemoryClient) Valid(ctx context.Context) bool { return i.valid } -func (i *inMemoryClient) CreatePersonalAccessToken(ctx context.Context, username string, userId int, name string, expiresAt time.Time, scopes []string) (*gitlab.TokenPersonal, error) { +func (i *inMemoryClient) CreatePersonalAccessToken(ctx context.Context, username string, userId int, name string, expiresAt time.Time, scopes []string) (*models.TokenPersonal, error) { i.muLock.Lock() defer i.muLock.Unlock() if i.personalAccessTokenCreateError { @@ -445,15 +449,15 @@ func (i *inMemoryClient) CreatePersonalAccessToken(ctx context.Context, username } i.internalCounter++ var tokenId = i.internalCounter - var entryToken = &gitlab.TokenPersonal{ - TokenWithScopes: gitlab.TokenWithScopes{ - Token: gitlab.Token{ + var entryToken = &models.TokenPersonal{ + TokenWithScopes: models.TokenWithScopes{ + Token: models.Token{ TokenID: tokenId, ParentID: "", Path: username, Name: name, Token: fmt.Sprintf("glpat-%s", uuid.New().String()), - TokenType: gitlab.TokenTypePersonal, + TokenType: t.TypePersonal, CreatedAt: g.Ptr(time.Now()), ExpiresAt: &expiresAt, }, @@ -461,11 +465,11 @@ func (i *inMemoryClient) CreatePersonalAccessToken(ctx context.Context, username }, UserID: userId, } - i.accessTokens[fmt.Sprintf("%s_%v", gitlab.TokenTypePersonal.String(), tokenId)] = entryToken + i.accessTokens[fmt.Sprintf("%s_%v", t.TypePersonal.String(), tokenId)] = entryToken return entryToken, nil } -func (i *inMemoryClient) CreateGroupAccessToken(ctx context.Context, groupId string, name string, expiresAt time.Time, scopes []string, accessLevel gitlab.AccessLevel) (*gitlab.TokenGroup, error) { +func (i *inMemoryClient) CreateGroupAccessToken(ctx context.Context, groupId string, name string, expiresAt time.Time, scopes []string, accessLevel t.AccessLevel) (*models.TokenGroup, error) { i.muLock.Lock() defer i.muLock.Unlock() if i.groupAccessTokenCreateError { @@ -473,15 +477,15 @@ func (i *inMemoryClient) CreateGroupAccessToken(ctx context.Context, groupId str } i.internalCounter++ var tokenId = i.internalCounter - var entryToken = &gitlab.TokenGroup{ - TokenWithScopesAndAccessLevel: gitlab.TokenWithScopesAndAccessLevel{ - Token: gitlab.Token{ + var entryToken = &models.TokenGroup{ + TokenWithScopesAndAccessLevel: models.TokenWithScopesAndAccessLevel{ + Token: models.Token{ TokenID: tokenId, ParentID: groupId, Path: groupId, Name: name, Token: fmt.Sprintf("glgat-%s", uuid.New().String()), - TokenType: gitlab.TokenTypeGroup, + TokenType: t.TypeGroup, CreatedAt: g.Ptr(time.Now()), ExpiresAt: &expiresAt, }, @@ -489,11 +493,11 @@ func (i *inMemoryClient) CreateGroupAccessToken(ctx context.Context, groupId str AccessLevel: accessLevel, }, } - i.accessTokens[fmt.Sprintf("%s_%v", gitlab.TokenTypeGroup.String(), tokenId)] = entryToken + i.accessTokens[fmt.Sprintf("%s_%v", t.TypeGroup.String(), tokenId)] = entryToken return entryToken, nil } -func (i *inMemoryClient) CreateProjectAccessToken(ctx context.Context, projectId string, name string, expiresAt time.Time, scopes []string, accessLevel gitlab.AccessLevel) (*gitlab.TokenProject, error) { +func (i *inMemoryClient) CreateProjectAccessToken(ctx context.Context, projectId string, name string, expiresAt time.Time, scopes []string, accessLevel t.AccessLevel) (*models.TokenProject, error) { i.muLock.Lock() defer i.muLock.Unlock() if i.projectAccessTokenCreateError { @@ -501,11 +505,11 @@ func (i *inMemoryClient) CreateProjectAccessToken(ctx context.Context, projectId } i.internalCounter++ var tokenId = i.internalCounter - var entryToken = &gitlab.TokenProject{ - TokenWithScopesAndAccessLevel: gitlab.TokenWithScopesAndAccessLevel{ - Token: gitlab.Token{ + var entryToken = &models.TokenProject{ + TokenWithScopesAndAccessLevel: models.TokenWithScopesAndAccessLevel{ + Token: models.Token{ Token: fmt.Sprintf("glpat-%s", uuid.New().String()), - TokenType: gitlab.TokenTypeProject, + TokenType: t.TypeProject, CreatedAt: g.Ptr(time.Now()), ExpiresAt: &expiresAt, TokenID: tokenId, @@ -517,7 +521,7 @@ func (i *inMemoryClient) CreateProjectAccessToken(ctx context.Context, projectId AccessLevel: accessLevel, }, } - i.accessTokens[fmt.Sprintf("%s_%v", gitlab.TokenTypeProject.String(), tokenId)] = entryToken + i.accessTokens[fmt.Sprintf("%s_%v", t.TypeProject.String(), tokenId)] = entryToken return entryToken, nil } @@ -527,7 +531,7 @@ func (i *inMemoryClient) RevokePersonalAccessToken(ctx context.Context, tokenId if i.personalAccessTokenRevokeError { return fmt.Errorf("RevokePersonalAccessToken") } - delete(i.accessTokens, fmt.Sprintf("%s_%v", gitlab.TokenTypePersonal.String(), tokenId)) + delete(i.accessTokens, fmt.Sprintf("%s_%v", t.TypePersonal.String(), tokenId)) return nil } @@ -537,7 +541,7 @@ func (i *inMemoryClient) RevokeProjectAccessToken(ctx context.Context, tokenId i if i.projectAccessTokenRevokeError { return fmt.Errorf("RevokeProjectAccessToken") } - delete(i.accessTokens, fmt.Sprintf("%s_%v", gitlab.TokenTypeProject.String(), tokenId)) + delete(i.accessTokens, fmt.Sprintf("%s_%v", t.TypeProject.String(), tokenId)) return nil } @@ -547,7 +551,7 @@ func (i *inMemoryClient) RevokeGroupAccessToken(ctx context.Context, tokenId int if i.groupAccessTokenRevokeError { return fmt.Errorf("RevokeGroupAccessToken") } - delete(i.accessTokens, fmt.Sprintf("%s_%v", gitlab.TokenTypeGroup.String(), tokenId)) + delete(i.accessTokens, fmt.Sprintf("%s_%v", t.TypeGroup.String(), tokenId)) return nil } @@ -576,12 +580,12 @@ func sanitizePath(path string) string { func getCtxGitlabClient(t *testing.T, target string) context.Context { httpClient, _ := getClient(t, target) - return gitlab.HttpClientNewContext(t.Context(), httpClient) + return utils.HttpClientNewContext(t.Context(), httpClient) } func getCtxGitlabClientWithUrl(t *testing.T, target string) (context.Context, string) { httpClient, url := getClient(t, target) - return gitlab.HttpClientNewContext(t.Context(), httpClient), url + return utils.HttpClientNewContext(t.Context(), httpClient), url } func parseTimeFromFile(name string) (t time.Time, err error) { @@ -616,7 +620,7 @@ func ctxTestTime(ctx context.Context, testName string, tokenName string) (_ cont } else { t = token.CreatedAtTime() } - return gitlab.WithStaticTime(ctx, t), t + return utils.WithStaticTime(ctx, t), t } func filterSlice[T any, Slice ~[]T](collection Slice, predicate func(item T, index int) bool) Slice { diff --git a/internal/errs/errs.go b/internal/errs/errs.go new file mode 100644 index 0000000..4fe01ba --- /dev/null +++ b/internal/errs/errs.go @@ -0,0 +1,32 @@ +package errs + +import "errors" + +var ( + // ErrNilValue represents an error indicating a nil value was encountered where it is not allowed. + ErrNilValue = errors.New("nil value") + + // ErrInvalidValue indicates that an operation encountered a value that is considered invalid or inappropriate. + ErrInvalidValue = errors.New("invalid value") + + // ErrFieldRequired represents an error when a required field is missing + ErrFieldRequired = errors.New("required field") + + // ErrFieldInvalidValue represents an error when a field contains an invalid value + ErrFieldInvalidValue = errors.New("invalid value for field") + + // ErrBackendNotConfigured represents an error when trying to use a backend that hasn't been properly configured + ErrBackendNotConfigured = errors.New("backend not configured") + + // ErrUnknown represents an error indicating an unknown or unspecified condition occurred. + ErrUnknown = errors.New("unknown") + + // ErrUnknownTokenType indicates an error when an undefined or unrecognized token type is encountered. + ErrUnknownTokenType = errors.New("unknown token type") + + // ErrUnknownTokenScope is returned when an unrecognized or undefined token scope is encountered. + ErrUnknownTokenScope = errors.New("unknown token scope") + + // ErrUnknownAccessLevel indicates an error caused by encountering an undefined or unrecognized access level. + ErrUnknownAccessLevel = errors.New("unknown access level") +) diff --git a/events.go b/internal/event/event.go similarity index 65% rename from events.go rename to internal/event/event.go index c959c12..1801f15 100644 --- a/events.go +++ b/internal/event/event.go @@ -1,4 +1,4 @@ -package gitlab +package event import ( "context" @@ -10,7 +10,7 @@ import ( "google.golang.org/protobuf/types/known/structpb" ) -func event(ctx context.Context, b *framework.Backend, eventType string, metadata map[string]string) { +func Event(ctx context.Context, b *framework.Backend, prefix, eventType string, metadata map[string]string) error { var err error var ev *logical.EventData if ev, err = logical.NewEvent(); err == nil { @@ -18,6 +18,7 @@ func event(ctx context.Context, b *framework.Backend, eventType string, metadata metadataBytes, _ = json.Marshal(metadata) ev.Metadata = &structpb.Struct{} _ = ev.Metadata.UnmarshalJSON(metadataBytes) - _ = b.SendEvent(ctx, logical.EventType(fmt.Sprintf("%s/%s", operationPrefixGitlabAccessTokens, eventType)), ev) + err = b.SendEvent(ctx, logical.EventType(fmt.Sprintf("%s/%s", prefix, eventType)), ev) } + return err } diff --git a/internal/event/event_test.go b/internal/event/event_test.go new file mode 100644 index 0000000..6beb56d --- /dev/null +++ b/internal/event/event_test.go @@ -0,0 +1,63 @@ +package event_test + +import ( + "context" + "sync" + "testing" + + "github.com/hashicorp/vault/sdk/framework" + "github.com/hashicorp/vault/sdk/logical" + "github.com/stretchr/testify/require" + + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/event" +) + +type mockEventsSender struct { + events []*logical.EventReceived + mu sync.Mutex +} + +var _ logical.EventSender = (*mockEventsSender)(nil) + +func (m *mockEventsSender) SendEvent(ctx context.Context, eventType logical.EventType, event *logical.EventData) error { + if m == nil { + return nil + } + m.mu.Lock() + defer m.mu.Unlock() + m.events = append(m.events, &logical.EventReceived{ + EventType: string(eventType), + Event: event, + }) + return nil +} + +func TestEvent(t *testing.T) { + t.Run("no sender specified", func(t *testing.T) { + b := &framework.Backend{} + require.NoError(t, b.Setup(t.Context(), &logical.BackendConfig{})) + require.ErrorIs(t, + event.Event( + t.Context(), + &framework.Backend{}, + "test", "test", + map[string]string{"test": "test"}, + ), + framework.ErrNoEvents, + ) + }) + + t.Run("with event sender", func(t *testing.T) { + b := &framework.Backend{} + evt := &mockEventsSender{} + require.NoError(t, b.Setup(t.Context(), &logical.BackendConfig{EventsSender: evt})) + require.NoError(t, + event.Event( + t.Context(), b, + "test", "test", + map[string]string{"test": "test"}, + ), + ) + require.Len(t, evt.events, 1) + }) +} diff --git a/internal/flags/flags.go b/internal/flags/flags.go new file mode 100644 index 0000000..9f08eb0 --- /dev/null +++ b/internal/flags/flags.go @@ -0,0 +1,22 @@ +package flags + +import ( + "flag" +) + +// Flags represent a set of configurable options affecting runtime behavior and configuration visibility. +type Flags struct { + + // ShowConfigToken determines if the configuration token value should be displayed when accessing the configuration endpoint. + ShowConfigToken bool `json:"show_config_token" mapstructure:"show_config_token"` + + // AllowRuntimeFlagsChange determines whether runtime flags can be dynamically modified during execution. + AllowRuntimeFlagsChange bool `json:"allow_runtime_flags_change" mapstructure:"allow_runtime_flags_change"` +} + +// FlagSet configures the provided FlagSet with flags managed by the Flags struct and returns the updated FlagSet. +func (f *Flags) FlagSet(fs *flag.FlagSet) *flag.FlagSet { + fs.BoolVar(&f.ShowConfigToken, "show-config-token", false, "Display the token value when reading it's config the configuration endpoint.") + fs.BoolVar(&f.AllowRuntimeFlagsChange, "allow-runtime-flags-change", false, "Allows you to change the flags dynamically at runtime.") + return fs +} diff --git a/flags_test.go b/internal/flags/flags_test.go similarity index 84% rename from flags_test.go rename to internal/flags/flags_test.go index b6f3c7f..3a39efb 100644 --- a/flags_test.go +++ b/internal/flags/flags_test.go @@ -1,4 +1,4 @@ -package gitlab_test +package flags_test import ( "flag" @@ -6,7 +6,7 @@ import ( "github.com/stretchr/testify/assert" - gitlab "github.com/ilijamt/vault-plugin-secrets-gitlab" + gitlab "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/flags" ) func TestFlags_FlagSet(t *testing.T) { diff --git a/internal/gitlab/client.go b/internal/gitlab/client.go new file mode 100644 index 0000000..76adf0e --- /dev/null +++ b/internal/gitlab/client.go @@ -0,0 +1,38 @@ +package gitlab + +import ( + "context" + "time" + + g "gitlab.com/gitlab-org/api/client-go" + + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/models" + t "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/token" +) + +type Client interface { + GitlabClient(ctx context.Context) *g.Client + Valid(ctx context.Context) bool + Metadata(ctx context.Context) (*g.Metadata, error) + CurrentTokenInfo(ctx context.Context) (*models.TokenConfig, error) + RotateCurrentToken(ctx context.Context) (newToken *models.TokenConfig, oldToken *models.TokenConfig, err error) + CreatePersonalAccessToken(ctx context.Context, username string, userId int, name string, expiresAt time.Time, scopes []string) (*models.TokenPersonal, error) + CreateGroupAccessToken(ctx context.Context, groupId string, name string, expiresAt time.Time, scopes []string, accessLevel t.AccessLevel) (*models.TokenGroup, error) + CreateProjectAccessToken(ctx context.Context, projectId string, name string, expiresAt time.Time, scopes []string, accessLevel t.AccessLevel) (*models.TokenProject, error) + RevokePersonalAccessToken(ctx context.Context, tokenId int) error + RevokeProjectAccessToken(ctx context.Context, tokenId int, projectId string) error + RevokeGroupAccessToken(ctx context.Context, tokenId int, groupId string) error + GetUserIdByUsername(ctx context.Context, username string) (int, error) + GetGroupIdByPath(ctx context.Context, path string) (int, error) + GetProjectIdByPath(ctx context.Context, path string) (int, error) + CreateGroupServiceAccountAccessToken(ctx context.Context, group string, groupId string, userId int, name string, expiresAt time.Time, scopes []string) (*models.TokenGroupServiceAccount, error) + CreateUserServiceAccountAccessToken(ctx context.Context, username string, userId int, name string, expiresAt time.Time, scopes []string) (*models.TokenUserServiceAccount, error) + RevokeUserServiceAccountAccessToken(ctx context.Context, token string) error + RevokeGroupServiceAccountAccessToken(ctx context.Context, token string) error + CreatePipelineProjectTriggerAccessToken(ctx context.Context, path, name string, projectId int, description string, expiresAt *time.Time) (*models.TokenPipelineProjectTrigger, error) + RevokePipelineProjectTriggerAccessToken(ctx context.Context, projectId int, tokenId int) error + CreateProjectDeployToken(ctx context.Context, path string, projectId int, name string, expiresAt *time.Time, scopes []string) (et *models.TokenProjectDeploy, err error) + RevokeProjectDeployToken(ctx context.Context, projectId, deployTokenId int) (err error) + CreateGroupDeployToken(ctx context.Context, path string, groupId int, name string, expiresAt *time.Time, scopes []string) (et *models.TokenGroupDeploy, err error) + RevokeGroupDeployToken(ctx context.Context, groupId, deployTokenId int) (err error) +} diff --git a/internal/gitlab/ctx.go b/internal/gitlab/ctx.go new file mode 100644 index 0000000..0cdd0a8 --- /dev/null +++ b/internal/gitlab/ctx.go @@ -0,0 +1,31 @@ +package gitlab + +import "context" + +type contextKey string + +var ( + ctxKeyGitlabClient = contextKey("vpsg-ctx-key-gitlab-client") +) + +// ClientNewContext returns a new context.Context that carries the provided Client. +// +// This function embeds the specified GitLab client into the given context, allowing +// it to be retrieved later in the execution flow. It's particularly useful for passing +// around client information across different layers of an application. +func ClientNewContext(ctx context.Context, client Client) context.Context { + return context.WithValue(ctx, ctxKeyGitlabClient, client) +} + +// ClientFromContext extracts the GitLab Client from the provided context. +// +// This function attempts to retrieve a Client from the given context. If it was +// not present or if it cannot be asserted as a Client, the returned Client will +// be nil and the boolean will be false. +func ClientFromContext(ctx context.Context) (Client, bool) { + u, ok := ctx.Value(ctxKeyGitlabClient).(Client) + if !ok { + u = nil + } + return u, ok +} diff --git a/defs_test.go b/internal/gitlab/ctx_test.go similarity index 50% rename from defs_test.go rename to internal/gitlab/ctx_test.go index dfcd3ef..0187d97 100644 --- a/defs_test.go +++ b/internal/gitlab/ctx_test.go @@ -1,5 +1,3 @@ -//go:build unit - package gitlab_test import ( @@ -7,7 +5,7 @@ import ( "github.com/stretchr/testify/require" - gitlab "github.com/ilijamt/vault-plugin-secrets-gitlab" + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/gitlab" ) func TestEmptyGitlabClientFromContext(t *testing.T) { @@ -15,9 +13,3 @@ func TestEmptyGitlabClientFromContext(t *testing.T) { require.False(t, ok) require.Nil(t, c) } - -func TestEmptyHttpClientFromContext(t *testing.T) { - c, ok := gitlab.HttpClientFromContext(t.Context()) - require.False(t, ok) - require.Nil(t, c) -} diff --git a/internal/gitlab/type.go b/internal/gitlab/type.go new file mode 100644 index 0000000..4de8f09 --- /dev/null +++ b/internal/gitlab/type.go @@ -0,0 +1,52 @@ +package gitlab + +import ( + "fmt" + "slices" + + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/errs" +) + +// Type defines a string-based type to represent specific categories or modes, such as "saas" or "self-managed". This is the Gitlab Type +type Type string + +const ( + // TypeSaaS represents the "saas" type, indicating the software-as-a-service mode for GitLab deployments. + TypeSaaS Type = "saas" + // TypeDedicated represents the "dedicated" type, indicating a dedicated mode for GitLab deployments. + TypeDedicated Type = "dedicated" + // TypeSelfManaged represents the "self-managed" type, indicating a self-hosted mode for GitLab deployments. + TypeSelfManaged Type = "self-managed" + + // TypeUnknown represents an uninitialized or unknown GitLab deployment type, used as a default fallback value. + TypeUnknown = Type("") +) + +var ( + ErrUnknownType = fmt.Errorf("%s: gitlab type", errs.ErrInvalidValue) + + validGitlabTypes = []string{ + TypeSaaS.String(), + TypeSelfManaged.String(), + TypeDedicated.String(), + } +) + +// String converts the Type value to its underlying string representation. +func (i Type) String() string { + return string(i) +} + +// Value returns the string representation of the Type by invoking the String method. +func (i Type) Value() string { + return i.String() +} + +// TypeParse attempts to parse the given string into a valid GitLab Type. +// Returns the corresponding Type and nil error if successful, or TypeUnknown and an error if parsing fails. +func TypeParse(value string) (Type, error) { + if slices.Contains(validGitlabTypes, value) { + return Type(value), nil + } + return TypeUnknown, fmt.Errorf("failed to parse '%s': %w", value, ErrUnknownType) +} diff --git a/gitlab_type_test.go b/internal/gitlab/type_test.go similarity index 93% rename from gitlab_type_test.go rename to internal/gitlab/type_test.go index 3dafdad..32df056 100644 --- a/gitlab_type_test.go +++ b/internal/gitlab/type_test.go @@ -7,7 +7,7 @@ import ( "github.com/stretchr/testify/assert" - gitlab "github.com/ilijamt/vault-plugin-secrets-gitlab" + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/gitlab" ) func TestType(t *testing.T) { diff --git a/token.go b/internal/models/token.go similarity index 59% rename from token.go rename to internal/models/token.go index 4752b08..705b96c 100644 --- a/token.go +++ b/internal/models/token.go @@ -1,26 +1,19 @@ -package gitlab +package models import ( "crypto/sha1" "fmt" "maps" "strconv" + "strings" "time" + + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/token" ) -type IToken interface { - Internal() map[string]any - Data() map[string]any - Event(map[string]string) map[string]string - Type() TokenType - SetConfigName(string) - SetRoleName(string) - SetGitlabRevokesToken(bool) - SetExpiresAt(*time.Time) - GetExpiresAt() time.Time - GetCreatedAt() time.Time - TTL() time.Duration -} +var _ token.Token = (*Token)(nil) +var _ token.Token = (*TokenWithScopes)(nil) +var _ token.Token = (*TokenWithScopesAndAccessLevel)(nil) type Token struct { RoleName string `json:"role_name"` @@ -28,7 +21,7 @@ type Token struct { GitlabRevokesToken bool `json:"gitlab_revokes_token"` CreatedAt *time.Time `json:"created_at"` ExpiresAt *time.Time `json:"expires_at"` - TokenType TokenType `json:"type"` + TokenType token.Type `json:"type"` Token string `json:"token"` TokenID int `json:"token_id"` ParentID string `json:"parent_id"` @@ -56,7 +49,7 @@ func (t *Token) SetExpiresAt(expiresAt *time.Time) { t.ExpiresAt = expiresAt } func (t *Token) SetConfigName(name string) { t.ConfigName = name } func (t *Token) SetRoleName(name string) { t.RoleName = name } func (t *Token) SetGitlabRevokesToken(b bool) { t.GitlabRevokesToken = b } -func (t *Token) Type() TokenType { return t.TokenType } +func (t *Token) Type() token.Type { return t.TokenType } func (t *Token) Internal() map[string]any { return map[string]any{ @@ -104,4 +97,60 @@ func (t *Token) Event(m map[string]string) (d map[string]string) { return d } -var _ IToken = (*Token)(nil) +type TokenWithScopes struct { + Token `json:",inline"` + + Scopes []string `json:"scopes"` +} + +func (t *TokenWithScopes) Internal() (d map[string]any) { + d = map[string]any{"scopes": t.Scopes} + maps.Copy(d, t.Token.Internal()) + return d +} + +func (t *TokenWithScopes) Data() (d map[string]any) { + d = map[string]any{"scopes": t.Scopes} + maps.Copy(d, t.Token.Data()) + return d +} + +func (t *TokenWithScopes) Event(m map[string]string) (d map[string]string) { + d = map[string]string{"scopes": strings.Join(t.Scopes, ",")} + maps.Copy(d, t.Token.Event(m)) + return d +} + +type TokenWithScopesAndAccessLevel struct { + Token `json:",inline"` + + Scopes []string `json:"scopes"` + AccessLevel token.AccessLevel `json:"access_level"` +} + +func (t *TokenWithScopesAndAccessLevel) Internal() (d map[string]any) { + d = map[string]any{ + "scopes": t.Scopes, + "access_level": t.AccessLevel.String(), + } + maps.Copy(d, t.Token.Internal()) + return d +} + +func (t *TokenWithScopesAndAccessLevel) Data() (d map[string]any) { + d = map[string]any{ + "scopes": t.Scopes, + "access_level": t.AccessLevel.String(), + } + maps.Copy(d, t.Token.Data()) + return d +} + +func (t *TokenWithScopesAndAccessLevel) Event(m map[string]string) (d map[string]string) { + d = map[string]string{ + "scopes": strings.Join(t.Scopes, ","), + "access_level": t.AccessLevel.String(), + } + maps.Copy(d, t.Token.Event(m)) + return d +} diff --git a/internal/models/token_config.go b/internal/models/token_config.go new file mode 100644 index 0000000..2d22c45 --- /dev/null +++ b/internal/models/token_config.go @@ -0,0 +1,34 @@ +package models + +import ( + "maps" + "strconv" + + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/token" +) + +type TokenConfig struct { + TokenWithScopes `json:",inline"` + + UserID int `json:"user_id"` +} + +func (t *TokenConfig) Internal() (d map[string]any) { + d = map[string]any{"user_id": t.UserID} + maps.Copy(d, t.TokenWithScopes.Internal()) + return d +} + +func (t *TokenConfig) Data() (d map[string]any) { + d = map[string]any{"user_id": t.UserID} + maps.Copy(d, t.TokenWithScopes.Data()) + return d +} + +func (t *TokenConfig) Event(m map[string]string) (d map[string]string) { + d = map[string]string{"user_id": strconv.Itoa(t.UserID)} + maps.Copy(d, t.Token.Event(m)) + return d +} + +var _ token.Token = (*TokenConfig)(nil) diff --git a/internal/models/token_config_test.go b/internal/models/token_config_test.go new file mode 100644 index 0000000..88f82e4 --- /dev/null +++ b/internal/models/token_config_test.go @@ -0,0 +1,19 @@ +package models_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/models" +) + +func TestTokenConfig(t *testing.T) { + data := models.TokenConfig{UserID: 1} + assert.Contains(t, data.Data(), "user_id") + assert.Contains(t, data.Event(nil), "user_id") + assert.Contains(t, data.Internal(), "user_id") + assert.EqualValues(t, 1, data.Data()["user_id"]) + assert.EqualValues(t, "1", data.Event(nil)["user_id"]) + assert.EqualValues(t, 1, data.Internal()["user_id"]) +} diff --git a/internal/models/token_group.go b/internal/models/token_group.go new file mode 100644 index 0000000..85a3097 --- /dev/null +++ b/internal/models/token_group.go @@ -0,0 +1,9 @@ +package models + +import "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/token" + +type TokenGroup struct { + TokenWithScopesAndAccessLevel `json:",inline"` +} + +var _ token.Token = (*TokenGroup)(nil) diff --git a/token_group_deploy.go b/internal/models/token_group_deploy.go similarity index 82% rename from token_group_deploy.go rename to internal/models/token_group_deploy.go index ec1f149..773c55f 100644 --- a/token_group_deploy.go +++ b/internal/models/token_group_deploy.go @@ -1,7 +1,9 @@ -package gitlab +package models import ( "maps" + + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/token" ) type TokenGroupDeploy struct { @@ -27,3 +29,5 @@ func (t *TokenGroupDeploy) Event(m map[string]string) (d map[string]string) { maps.Copy(d, t.Token.Event(m)) return d } + +var _ token.Token = (*TokenGroupDeploy)(nil) diff --git a/internal/models/token_group_deploy_test.go b/internal/models/token_group_deploy_test.go new file mode 100644 index 0000000..67e5153 --- /dev/null +++ b/internal/models/token_group_deploy_test.go @@ -0,0 +1,19 @@ +package models_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/models" +) + +func TestTokenGroupDeploy(t *testing.T) { + data := models.TokenGroupDeploy{Username: "username"} + assert.Contains(t, data.Data(), "username") + assert.Contains(t, data.Event(nil), "username") + assert.Contains(t, data.Internal(), "username") + assert.EqualValues(t, "username", data.Data()["username"]) + assert.EqualValues(t, "username", data.Event(nil)["username"]) + assert.EqualValues(t, "username", data.Internal()["username"]) +} diff --git a/token_group_service_account.go b/internal/models/token_group_service_account.go similarity index 82% rename from token_group_service_account.go rename to internal/models/token_group_service_account.go index 4c8531d..9dfa886 100644 --- a/token_group_service_account.go +++ b/internal/models/token_group_service_account.go @@ -1,8 +1,10 @@ -package gitlab +package models import ( "maps" "strconv" + + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/token" ) type TokenGroupServiceAccount struct { @@ -28,3 +30,5 @@ func (t *TokenGroupServiceAccount) Event(m map[string]string) (d map[string]stri maps.Copy(d, t.Token.Event(m)) return d } + +var _ token.Token = (*TokenGroupServiceAccount)(nil) diff --git a/internal/models/token_group_service_account_test.go b/internal/models/token_group_service_account_test.go new file mode 100644 index 0000000..f83f563 --- /dev/null +++ b/internal/models/token_group_service_account_test.go @@ -0,0 +1,19 @@ +package models_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/models" +) + +func TestTokenGroupServiceAccount(t *testing.T) { + data := models.TokenGroupServiceAccount{UserID: 1} + assert.Contains(t, data.Data(), "user_id") + assert.Contains(t, data.Event(nil), "user_id") + assert.Contains(t, data.Internal(), "user_id") + assert.EqualValues(t, 1, data.Data()["user_id"]) + assert.EqualValues(t, "1", data.Event(nil)["user_id"]) + assert.EqualValues(t, 1, data.Internal()["user_id"]) +} diff --git a/token_personal.go b/internal/models/token_personal.go similarity index 82% rename from token_personal.go rename to internal/models/token_personal.go index afa8e53..30608fa 100644 --- a/token_personal.go +++ b/internal/models/token_personal.go @@ -1,8 +1,10 @@ -package gitlab +package models import ( "maps" "strconv" + + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/token" ) type TokenPersonal struct { @@ -28,3 +30,5 @@ func (t *TokenPersonal) Event(m map[string]string) (d map[string]string) { maps.Copy(d, t.Token.Event(m)) return d } + +var _ token.Token = (*TokenPersonal)(nil) diff --git a/internal/models/token_personal_test.go b/internal/models/token_personal_test.go new file mode 100644 index 0000000..a7a306a --- /dev/null +++ b/internal/models/token_personal_test.go @@ -0,0 +1,19 @@ +package models_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/models" +) + +func TestTokenPersonal(t *testing.T) { + data := models.TokenPersonal{UserID: 1} + assert.Contains(t, data.Data(), "user_id") + assert.Contains(t, data.Event(nil), "user_id") + assert.Contains(t, data.Internal(), "user_id") + assert.EqualValues(t, 1, data.Data()["user_id"]) + assert.EqualValues(t, "1", data.Event(nil)["user_id"]) + assert.EqualValues(t, 1, data.Internal()["user_id"]) +} diff --git a/internal/models/token_pipeline_project_trigger.go b/internal/models/token_pipeline_project_trigger.go new file mode 100644 index 0000000..62fc427 --- /dev/null +++ b/internal/models/token_pipeline_project_trigger.go @@ -0,0 +1,9 @@ +package models + +import "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/token" + +type TokenPipelineProjectTrigger struct { + Token `json:",inline"` +} + +var _ token.Token = (*TokenPipelineProjectTrigger)(nil) diff --git a/internal/models/token_project.go b/internal/models/token_project.go new file mode 100644 index 0000000..04c451e --- /dev/null +++ b/internal/models/token_project.go @@ -0,0 +1,9 @@ +package models + +import "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/token" + +type TokenProject struct { + TokenWithScopesAndAccessLevel `json:",inline"` +} + +var _ token.Token = (*TokenProject)(nil) diff --git a/token_project_deploy.go b/internal/models/token_project_deploy.go similarity index 80% rename from token_project_deploy.go rename to internal/models/token_project_deploy.go index fdb987a..c5be3ea 100644 --- a/token_project_deploy.go +++ b/internal/models/token_project_deploy.go @@ -1,6 +1,10 @@ -package gitlab +package models -import "maps" +import ( + "maps" + + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/token" +) type TokenProjectDeploy struct { TokenWithScopes `json:",inline"` @@ -25,3 +29,5 @@ func (t *TokenProjectDeploy) Event(m map[string]string) (d map[string]string) { maps.Copy(d, t.Token.Event(m)) return d } + +var _ token.Token = (*TokenProjectDeploy)(nil) diff --git a/internal/models/token_project_deploy_test.go b/internal/models/token_project_deploy_test.go new file mode 100644 index 0000000..191388d --- /dev/null +++ b/internal/models/token_project_deploy_test.go @@ -0,0 +1,19 @@ +package models_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/models" +) + +func TestTokenProjectDeploy(t *testing.T) { + data := models.TokenProjectDeploy{Username: "username"} + assert.Contains(t, data.Data(), "username") + assert.Contains(t, data.Event(nil), "username") + assert.Contains(t, data.Internal(), "username") + assert.EqualValues(t, "username", data.Data()["username"]) + assert.EqualValues(t, "username", data.Event(nil)["username"]) + assert.EqualValues(t, "username", data.Internal()["username"]) +} diff --git a/internal/models/token_test.go b/internal/models/token_test.go new file mode 100644 index 0000000..d75c9eb --- /dev/null +++ b/internal/models/token_test.go @@ -0,0 +1,68 @@ +package models_test + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/models" + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/token" +) + +func TestToken(t *testing.T) { + t.Run("expires is not set so we get a 0 ttl", func(t *testing.T) { + data := models.Token{} + require.EqualValues(t, 0, data.TTL()) + }) + + t.Run("ttl has a value if both created and expires are set", func(t *testing.T) { + cat := time.Date(2025, 1, 1, 1, 0, 0, 0, time.UTC) + data := models.Token{CreatedAt: &cat} + eat := time.Date(2025, 1, 1, 2, 0, 0, 0, time.UTC) + data.SetExpiresAt(&eat) + require.EqualValues(t, time.Hour, data.TTL()) + }) + + t.Run("setters", func(t *testing.T) { + data := models.Token{} + data.SetRoleName("role-name") + data.SetConfigName("config-name") + data.SetGitlabRevokesToken(true) + + require.EqualValues(t, "role-name", data.RoleName) + require.EqualValues(t, "config-name", data.ConfigName) + require.EqualValues(t, true, data.GitlabRevokesToken) + + }) +} + +func TestTokenWithScopes(t *testing.T) { + data := models.TokenWithScopes{Scopes: []string{"scope1", "scope2"}} + assert.Contains(t, data.Data(), "scopes") + assert.Contains(t, data.Event(nil), "scopes") + assert.Contains(t, data.Internal(), "scopes") + assert.EqualValues(t, []string{"scope1", "scope2"}, data.Data()["scopes"]) + assert.EqualValues(t, "scope1,scope2", data.Event(nil)["scopes"]) + assert.EqualValues(t, []string{"scope1", "scope2"}, data.Internal()["scopes"]) +} + +func TestTokenWithScopesAndAccessLevel(t *testing.T) { + data := models.TokenWithScopesAndAccessLevel{ + Scopes: []string{"scope1", "scope2"}, + AccessLevel: token.AccessLevelNoPermissions, + } + assert.Contains(t, data.Data(), "scopes") + assert.Contains(t, data.Event(nil), "scopes") + assert.Contains(t, data.Internal(), "scopes") + assert.EqualValues(t, []string{"scope1", "scope2"}, data.Data()["scopes"]) + assert.EqualValues(t, "scope1,scope2", data.Event(nil)["scopes"]) + assert.EqualValues(t, []string{"scope1", "scope2"}, data.Internal()["scopes"]) + assert.Contains(t, data.Data(), "access_level") + assert.Contains(t, data.Event(nil), "access_level") + assert.Contains(t, data.Internal(), "access_level") + assert.EqualValues(t, token.AccessLevelNoPermissions, data.Data()["access_level"]) + assert.EqualValues(t, token.AccessLevelNoPermissions, data.Event(nil)["access_level"]) + assert.EqualValues(t, token.AccessLevelNoPermissions, data.Internal()["access_level"]) +} diff --git a/internal/models/token_user_service_account.go b/internal/models/token_user_service_account.go new file mode 100644 index 0000000..5a28df1 --- /dev/null +++ b/internal/models/token_user_service_account.go @@ -0,0 +1,9 @@ +package models + +import "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/token" + +type TokenUserServiceAccount struct { + TokenWithScopes `json:",inline"` +} + +var _ token.Token = (*TokenUserServiceAccount)(nil) diff --git a/type_access_level.go b/internal/token/access_level.go similarity index 99% rename from type_access_level.go rename to internal/token/access_level.go index 53e1d18..ac5527a 100644 --- a/type_access_level.go +++ b/internal/token/access_level.go @@ -1,4 +1,4 @@ -package gitlab +package token import ( "errors" diff --git a/type_access_level_test.go b/internal/token/access_level_test.go similarity index 94% rename from type_access_level_test.go rename to internal/token/access_level_test.go index d5cab4c..f6f4f57 100644 --- a/type_access_level_test.go +++ b/internal/token/access_level_test.go @@ -1,13 +1,13 @@ //go:build unit -package gitlab_test +package token_test import ( "testing" "github.com/stretchr/testify/assert" - gitlab "github.com/ilijamt/vault-plugin-secrets-gitlab" + gitlab "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/token" ) func TestAccessLevel(t *testing.T) { diff --git a/internal/token/scope.go b/internal/token/scope.go new file mode 100644 index 0000000..01154cd --- /dev/null +++ b/internal/token/scope.go @@ -0,0 +1,160 @@ +package token + +import ( + "fmt" + "slices" + + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/errs" +) + +type Scope string + +const ( + // ScopeApi grants complete read/write access to the API, including all groups and projects, the container registry, the dependency proxy, and the package registry. Also grants complete read/write access to the registry and repository using Git over HTTP + ScopeApi = Scope("api") + // ScopeReadApi grants read access to the scoped group and related project API, including the Package Registry + ScopeReadApi = Scope("read_api") + // ScopeReadRegistry grants read access (pull) to the Container Registry images if any project within expected group is private and authorization is required. + ScopeReadRegistry = Scope("read_registry") + // ScopeWriteRegistry grants write access (push) to the Container Registry. + ScopeWriteRegistry = Scope("write_registry") + // ScopeReadRepository grants read access (pull) to the Container Registry images if any project within expected group is private and authorization is required + ScopeReadRepository = Scope("read_repository") + // ScopeWriteRepository grants read and write access (pull and push) to all repositories within expected group + ScopeWriteRepository = Scope("write_repository") + + // ScopeReadPackageRegistry Allows read-only access to the package registry. + ScopeReadPackageRegistry = Scope("read_package_registry") + // ScopeWritePackageRegistry Allows read and write access to the package registry. + ScopeWritePackageRegistry = Scope("write_package_registry") + + // ScopeCreateRunner grants permission to create runners in expected group + ScopeCreateRunner = Scope("create_runner") + // ScopeManageRunner grants permission to manage runners in expected group + ScopeManageRunner = Scope("manage_runner") + + // ScopeReadUser grants read-only access to the authenticated user’s profile through the /user API endpoint, which includes username, public email, and full name. Also grants access to read-only API endpoints under /users. + ScopeReadUser = Scope("read_user") + // ScopeSudo grants permission to perform API actions as any user in the system, when authenticated as an administrator. + ScopeSudo = Scope("sudo") + // ScopeAdminMode grants permission to perform API actions as an administrator, when Admin Mode is enabled. + ScopeAdminMode = Scope("admin_mode") + + // ScopeAiFeatures grants permission to perform API actions for GitLab Duo. This scope is designed to work with the GitLab Duo Plugin for JetBrains. For all other extensions, see scope requirements. + ScopeAiFeatures = Scope("ai_features") + // ScopeK8SProxy grants permission to perform Kubernetes API calls using the agent for Kubernetes. + ScopeK8SProxy = Scope("k8s_proxy") + // ScopeReadServicePing grant access to download Service Ping payload through the API when authenticated as an admin use. + ScopeReadServicePing = Scope("read_service_ping") + + // ScopeSelfRotate grants permission to rotate this token using the personal access token API. Does not allow rotation of other tokens. + ScopeSelfRotate = Scope("self_rotate") + // ScopeReadVirtualRegistry if a project is private and authorization is required, grants read-only (pull) access to container images through the dependency proxy. Available only when the dependency proxy is enabled. + ScopeReadVirtualRegistry = Scope("read_virtual_registry") + // ScopeWriteVirtualRegistry if a project is private and authorization is required, grants read (pull), write (push), and delete access to container images through the dependency proxy. Available only when the dependency proxy is enabled. + ScopeWriteVirtualRegistry = Scope("write_virtual_registry") + + ScopeUnknown = Scope("") +) + +var ( + + // ValidPersonalTokenScopes defines the actions you can perform when you authenticate with a project access token. + ValidPersonalTokenScopes = []string{ + ScopeApi.String(), + ScopeReadUser.String(), + ScopeReadApi.String(), + ScopeReadRepository.String(), + ScopeWriteRepository.String(), + ScopeReadRegistry.String(), + ScopeWriteRegistry.String(), + ScopeReadVirtualRegistry.String(), + ScopeWriteVirtualRegistry.String(), + ScopeSudo.String(), + ScopeAdminMode.String(), + ScopeCreateRunner.String(), + ScopeManageRunner.String(), + ScopeAiFeatures.String(), + ScopeK8SProxy.String(), + ScopeSelfRotate.String(), + ScopeReadServicePing.String(), + } + + ValidProjectTokenScopes = []string{ + ScopeApi.String(), + ScopeReadApi.String(), + ScopeReadRegistry.String(), + ScopeWriteRegistry.String(), + ScopeReadRepository.String(), + ScopeWriteRepository.String(), + ScopeCreateRunner.String(), + ScopeManageRunner.String(), + ScopeAiFeatures.String(), + ScopeK8SProxy.String(), + ScopeSelfRotate.String(), + } + + ValidGroupTokenScopes = []string{ + ScopeApi.String(), + ScopeReadApi.String(), + ScopeReadRegistry.String(), + ScopeWriteRegistry.String(), + ScopeReadVirtualRegistry.String(), + ScopeWriteVirtualRegistry.String(), + ScopeReadRepository.String(), + ScopeWriteRepository.String(), + ScopeCreateRunner.String(), + ScopeManageRunner.String(), + ScopeAiFeatures.String(), + ScopeK8SProxy.String(), + ScopeSelfRotate.String(), + } + + ValidUserServiceAccountTokenScopes = ValidPersonalTokenScopes + + ValidGroupServiceAccountTokenScopes = ValidGroupTokenScopes + + ValidPipelineProjectTokenScopes []string + + ValidProjectDeployTokenScopes = []string{ + ScopeReadRepository.String(), + ScopeReadRegistry.String(), + ScopeWriteRegistry.String(), + ScopeReadVirtualRegistry.String(), + ScopeWriteVirtualRegistry.String(), + ScopeReadPackageRegistry.String(), + ScopeWritePackageRegistry.String(), + } + + ValidGroupDeployTokenScopes = []string{ + ScopeReadRepository.String(), + ScopeReadRegistry.String(), + ScopeWriteRegistry.String(), + ScopeReadVirtualRegistry.String(), + ScopeWriteVirtualRegistry.String(), + ScopeReadPackageRegistry.String(), + ScopeWritePackageRegistry.String(), + } +) + +func (i Scope) String() string { + return string(i) +} + +func (i Scope) Value() string { + return i.String() +} + +func ParseScope(value string) (Scope, error) { + if slices.Contains(ValidGroupTokenScopes, value) || + slices.Contains(ValidPipelineProjectTokenScopes, value) || + slices.Contains(ValidGroupDeployTokenScopes, value) || + slices.Contains(ValidProjectDeployTokenScopes, value) || + slices.Contains(ValidPersonalTokenScopes, value) || + slices.Contains(ValidProjectTokenScopes, value) || + slices.Contains(ValidUserServiceAccountTokenScopes, value) || + slices.Contains(ValidGroupServiceAccountTokenScopes, value) { + return Scope(value), nil + } + return ScopeUnknown, fmt.Errorf("failed to parse '%s': %w", value, errs.ErrUnknownTokenScope) +} diff --git a/internal/token/scope_test.go b/internal/token/scope_test.go new file mode 100644 index 0000000..c9a0840 --- /dev/null +++ b/internal/token/scope_test.go @@ -0,0 +1,91 @@ +//go:build unit + +package token_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/errs" + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/token" +) + +func TestTokenScope(t *testing.T) { + var tests = []struct { + expected token.Scope + input string + err bool + }{ + { + expected: token.ScopeApi, + input: token.ScopeApi.String(), + }, + { + expected: token.ScopeReadApi, + input: token.ScopeReadApi.String(), + }, + { + expected: token.ScopeReadRegistry, + input: token.ScopeReadRegistry.String(), + }, + { + expected: token.ScopeWriteRegistry, + input: token.ScopeWriteRegistry.String(), + }, + { + expected: token.ScopeReadRepository, + input: token.ScopeReadRepository.String(), + }, + { + expected: token.ScopeWriteRepository, + input: token.ScopeWriteRepository.String(), + }, + { + expected: token.ScopeCreateRunner, + input: token.ScopeCreateRunner.String(), + }, + { + expected: token.ScopeReadUser, + input: token.ScopeReadUser.String(), + }, + { + expected: token.ScopeSudo, + input: token.ScopeSudo.String(), + }, + { + expected: token.ScopeAdminMode, + input: token.ScopeAdminMode.String(), + }, + { + expected: token.ScopeReadPackageRegistry, + input: token.ScopeReadPackageRegistry.String(), + }, + { + expected: token.ScopeWritePackageRegistry, + input: token.ScopeWritePackageRegistry.String(), + }, + { + expected: token.ScopeUnknown, + input: "what", + err: true, + }, + { + expected: token.ScopeUnknown, + input: "unknown", + err: true, + }, + } + + for _, test := range tests { + t.Logf("assert parse(%s) = %s (err: %v)", test.input, test.expected, test.err) + val, err := token.ParseScope(test.input) + assert.EqualValues(t, test.expected, val) + assert.EqualValues(t, test.expected.Value(), test.expected.String()) + if test.err { + assert.ErrorIs(t, err, errs.ErrUnknownTokenScope) + } else { + assert.NoError(t, err) + } + } +} diff --git a/internal/token/token.go b/internal/token/token.go new file mode 100644 index 0000000..6ef104e --- /dev/null +++ b/internal/token/token.go @@ -0,0 +1,19 @@ +package token + +import ( + "time" +) + +type Token interface { + Internal() map[string]any + Data() map[string]any + Event(map[string]string) map[string]string + Type() Type + SetConfigName(string) + SetRoleName(string) + SetGitlabRevokesToken(bool) + SetExpiresAt(*time.Time) + GetExpiresAt() time.Time + GetCreatedAt() time.Time + TTL() time.Duration +} diff --git a/internal/token/type.go b/internal/token/type.go new file mode 100644 index 0000000..e577a95 --- /dev/null +++ b/internal/token/type.go @@ -0,0 +1,51 @@ +package token + +import ( + "fmt" + "slices" + + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/errs" +) + +type Type string + +const ( + TypePersonal = Type("personal") + TypeProject = Type("project") + TypeGroup = Type("group") + TypeUserServiceAccount = Type("user-service-account") + TypeGroupServiceAccount = Type("group-service-account") + TypePipelineProjectTrigger = Type("pipeline-project-trigger") + TypeProjectDeploy = Type("project-deploy") + TypeGroupDeploy = Type("group-deploy") + + TypeUnknown = Type("") +) + +var ( + ValidTokenTypes = []string{ + TypePersonal.String(), + TypeProject.String(), + TypeGroup.String(), + TypeUserServiceAccount.String(), + TypeGroupServiceAccount.String(), + TypePipelineProjectTrigger.String(), + TypeProjectDeploy.String(), + TypeGroupDeploy.String(), + } +) + +func (i Type) String() string { + return string(i) +} + +func (i Type) Value() string { + return i.String() +} + +func ParseType(value string) (Type, error) { + if slices.Contains(ValidTokenTypes, value) { + return Type(value), nil + } + return TypeUnknown, fmt.Errorf("failed to parse '%s': %w", value, errs.ErrUnknownTokenType) +} diff --git a/internal/token/type_test.go b/internal/token/type_test.go new file mode 100644 index 0000000..86b0057 --- /dev/null +++ b/internal/token/type_test.go @@ -0,0 +1,75 @@ +//go:build unit + +package token_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/errs" + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/token" +) + +func TestTokenType(t *testing.T) { + var tests = []struct { + expected token.Type + input string + err bool + }{ + { + expected: token.TypePersonal, + input: token.TypePersonal.String(), + }, + { + expected: token.TypeGroup, + input: token.TypeGroup.String(), + }, + { + expected: token.TypeProject, + input: token.TypeProject.String(), + }, + { + expected: token.TypeUserServiceAccount, + input: token.TypeUserServiceAccount.String(), + }, + { + expected: token.TypeGroupServiceAccount, + input: token.TypeGroupServiceAccount.String(), + }, + { + expected: token.TypePipelineProjectTrigger, + input: token.TypePipelineProjectTrigger.String(), + }, + { + expected: token.TypeProjectDeploy, + input: token.TypeProjectDeploy.String(), + }, + { + expected: token.TypeGroupDeploy, + input: token.TypeGroupDeploy.String(), + }, + { + expected: token.TypeUnknown, + input: "unknown", + err: true, + }, + { + expected: token.TypeUnknown, + input: "unknown", + err: true, + }, + } + + for _, test := range tests { + t.Logf("assert parse(%s) = %s (err: %v)", test.input, test.expected, test.err) + val, err := token.ParseType(test.input) + assert.EqualValues(t, test.expected, val) + assert.EqualValues(t, test.expected.Value(), test.expected.String()) + if test.err { + assert.ErrorIs(t, err, errs.ErrUnknownTokenType) + } else { + assert.NoError(t, err) + } + } +} diff --git a/internal/utils/calculate_gitlab_ttl.go b/internal/utils/calculate_gitlab_ttl.go new file mode 100644 index 0000000..842a399 --- /dev/null +++ b/internal/utils/calculate_gitlab_ttl.go @@ -0,0 +1,29 @@ +package utils + +import ( + "time" +) + +// CalculateGitlabTTL calculates the Time-To-Live (TTL) and expiration time for +// a GitLab-related operation based on a specified duration and start time. +// +// The function ensures that the calculated expiration does not exceed one year +// from the start time. It computes the expiration to be at the midnight +// following the calculated expiration date. +func CalculateGitlabTTL(duration time.Duration, start time.Time) (ttl time.Duration, exp time.Time, err error) { + start = start.UTC() + const D = 24 * time.Hour + const maxDuration = 365 * 24 * time.Hour + if duration > maxDuration { + duration = maxDuration + } + var val = start.Add(duration).Round(0) + exp = val.AddDate(0, 0, 1).Truncate(D) + ttl = exp.Sub(start.Round(0)) + if ttl > maxDuration { + m := start.Add(maxDuration) + exp = time.Date(m.Year(), m.Month(), m.Day(), 0, 0, 0, 0, m.Location()) + ttl = exp.Sub(start.Round(0)) + } + return ttl, exp, nil +} diff --git a/utils_test.go b/internal/utils/calculate_gitlab_ttl_test.go similarity index 79% rename from utils_test.go rename to internal/utils/calculate_gitlab_ttl_test.go index a1c728d..b4cfa59 100644 --- a/utils_test.go +++ b/internal/utils/calculate_gitlab_ttl_test.go @@ -1,6 +1,6 @@ //go:build unit -package gitlab +package utils_test import ( "testing" @@ -8,33 +8,9 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" -) - -func TestConvertToInt(t *testing.T) { - var tests = []struct { - in any - outVal int - outErr error - }{ - {int(52), int(52), nil}, - {int8(13), int(13), nil}, - {int16(612), int(612), nil}, - {int32(56236), int(56236), nil}, - {int64(23462346), int(23462346), nil}, - {float32(62346.62), int(62346), nil}, - {float64(263467.26), int(263467), nil}, - {"1", int(0), ErrInvalidValue}, - } - for _, tst := range tests { - t.Logf("convertToInt(%T(%v))", tst.in, tst.in) - val, err := convertToInt(tst.in) - assert.Equal(t, tst.outVal, val) - if tst.outErr != nil { - assert.ErrorIs(t, err, tst.outErr) - } - } -} + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/utils" +) func TestCalculateGitlabTTL(t *testing.T) { locMST, err := time.LoadLocation("MST") @@ -118,8 +94,8 @@ func TestCalculateGitlabTTL(t *testing.T) { } for _, tst := range tests { - t.Logf("calculateGitlabTTL(%s, %s) = duration %s, expiry %s, error %v", tst.inDuration, tst.inTime.Format(time.RFC3339), tst.outDuration, tst.outExpiry.Format(time.RFC3339), tst.outErr) - dur, exp, err := calculateGitlabTTL(tst.inDuration, tst.inTime) + t.Logf("CalculateGitlabTTL(%s, %s) = duration %s, expiry %s, error %v", tst.inDuration, tst.inTime.Format(time.RFC3339), tst.outDuration, tst.outExpiry.Format(time.RFC3339), tst.outErr) + dur, exp, err := utils.CalculateGitlabTTL(tst.inDuration, tst.inTime) if err != nil { assert.ErrorIs(t, err, tst.outErr) } diff --git a/internal/utils/convert_to_int.go b/internal/utils/convert_to_int.go new file mode 100644 index 0000000..6452e14 --- /dev/null +++ b/internal/utils/convert_to_int.go @@ -0,0 +1,33 @@ +package utils + +import ( + "fmt" + + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/errs" +) + +// ConvertToInt attempts to convert various numeric types to an int. +// +// This function handles conversions from several numeric types (including int, int8, +// int16, int32, int64, float32, and float64) to a standard int. It utilizes type +// assertion to check the underlying type of the input. If the input is not a supported +// numeric type, it returns an error. +func ConvertToInt(num any) (int, error) { + switch val := num.(type) { + case int: + return val, nil + case int8: + return int(val), nil + case int16: + return int(val), nil + case int32: + return int(val), nil + case int64: + return int(val), nil + case float32: + return int(val), nil + case float64: + return int(val), nil + } + return 0, fmt.Errorf("%v: %w", num, errs.ErrInvalidValue) +} diff --git a/internal/utils/convert_to_int_test.go b/internal/utils/convert_to_int_test.go new file mode 100644 index 0000000..23d554c --- /dev/null +++ b/internal/utils/convert_to_int_test.go @@ -0,0 +1,38 @@ +//go:build unit + +package utils_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/errs" + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/utils" +) + +func TestConvertToInt(t *testing.T) { + var tests = []struct { + in any + outVal int + outErr error + }{ + {int(52), int(52), nil}, + {int8(13), int(13), nil}, + {int16(612), int(612), nil}, + {int32(56236), int(56236), nil}, + {int64(23462346), int(23462346), nil}, + {float32(62346.62), int(62346), nil}, + {float64(263467.26), int(263467), nil}, + {"1", int(0), errs.ErrInvalidValue}, + } + + for _, tst := range tests { + t.Logf("ConvertToInt(%T(%v))", tst.in, tst.in) + val, err := utils.ConvertToInt(tst.in) + assert.Equal(t, tst.outVal, val) + if tst.outErr != nil { + assert.ErrorIs(t, err, tst.outErr) + } + } +} diff --git a/internal/utils/ctx.go b/internal/utils/ctx.go new file mode 100644 index 0000000..f9ef064 --- /dev/null +++ b/internal/utils/ctx.go @@ -0,0 +1,3 @@ +package utils + +type contextKey string diff --git a/internal/utils/ctx_http_client.go b/internal/utils/ctx_http_client.go new file mode 100644 index 0000000..c07a61e --- /dev/null +++ b/internal/utils/ctx_http_client.go @@ -0,0 +1,33 @@ +package utils + +import ( + "context" + "net/http" +) + +var ( + ctxKeyHttpClient = contextKey("vpsg-ctx-key-http-client") +) + +// HttpClientNewContext returns a new context.Context that carries the provided http.Client. +// +// This function embeds a given HTTP client into the provided context, allowing it +// to be passed through the application and retrieved later. This is useful for +// managing HTTP client configurations and dependency injection across different +// parts of an application that require HTTP clients. +func HttpClientNewContext(ctx context.Context, httpClient *http.Client) context.Context { + return context.WithValue(ctx, ctxKeyHttpClient, httpClient) +} + +// HttpClientFromContext extracts the http.Client from a given context. +// +// This function retrieves an HTTP client that was previously embedded in the context. +// If the context does not contain an HTTP client or it cannot be asserted as an +// *http.Client, the function returns nil and false. +func HttpClientFromContext(ctx context.Context) (*http.Client, bool) { + u, ok := ctx.Value(ctxKeyHttpClient).(*http.Client) + if !ok { + u = nil + } + return u, ok +} diff --git a/internal/utils/ctx_http_client_test.go b/internal/utils/ctx_http_client_test.go new file mode 100644 index 0000000..456d56d --- /dev/null +++ b/internal/utils/ctx_http_client_test.go @@ -0,0 +1,25 @@ +package utils_test + +import ( + "net/http" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/utils" +) + +func TestHttpClientFromContext(t *testing.T) { + t.Run("no http client", func(t *testing.T) { + c, ok := utils.HttpClientFromContext(t.Context()) + require.False(t, ok) + require.Nil(t, c) + }) + + t.Run("with http client", func(t *testing.T) { + ctx := utils.HttpClientNewContext(t.Context(), &http.Client{}) + c, ok := utils.HttpClientFromContext(ctx) + require.True(t, ok) + require.NotNil(t, c) + }) +} diff --git a/internal/utils/ctx_time.go b/internal/utils/ctx_time.go new file mode 100644 index 0000000..ca4bf8a --- /dev/null +++ b/internal/utils/ctx_time.go @@ -0,0 +1,33 @@ +package utils + +import ( + "context" + "time" +) + +var ( + ctxKeyTimeNow = contextKey("vpsg-ctx-key-time-now") +) + +// WithStaticTime returns a new context.Context that carries a specific static time. +// +// This function embeds a given time.Time value into the provided context, allowing +// parts of an application to operate with a fixed notion of the current time. This +// can be particularly useful in testing scenarios where you need to control or +// simulate time progression. +func WithStaticTime(ctx context.Context, t time.Time) context.Context { + return context.WithValue(ctx, ctxKeyTimeNow, t) +} + +// TimeFromContext extracts a time.Time from the given context. +// +// This function retrieves a time value that was previously embedded in the context. +// If the context does not contain such a time value, it defaults to returning time.Now(), +// effectively providing the current system time. +func TimeFromContext(ctx context.Context) time.Time { + t, ok := ctx.Value(ctxKeyTimeNow).(time.Time) + if !ok { + return time.Now() + } + return t +} diff --git a/internal/utils/ctx_time_test.go b/internal/utils/ctx_time_test.go new file mode 100644 index 0000000..1695540 --- /dev/null +++ b/internal/utils/ctx_time_test.go @@ -0,0 +1,24 @@ +package utils_test + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/utils" +) + +func TestWithTime(t *testing.T) { + t.Run("no time should default to time now", func(t *testing.T) { + tm := utils.TimeFromContext(t.Context()) + require.False(t, tm.IsZero()) + }) + + t.Run("with time", func(t *testing.T) { + tm := time.Date(2009, 1, 1, 1, 0, 0, 0, time.UTC) + ctx := utils.WithStaticTime(t.Context(), tm) + tmCtx := utils.TimeFromContext(ctx) + require.False(t, tmCtx.IsZero()) + }) +} diff --git a/internal/utils/name_tpl.go b/internal/utils/name_tpl.go new file mode 100644 index 0000000..646abb1 --- /dev/null +++ b/internal/utils/name_tpl.go @@ -0,0 +1,87 @@ +package utils + +import ( + "crypto/rand" + "fmt" + "strings" + "text/template" + "time" + _ "unsafe" + + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/errs" +) + +func yesNoBool(in bool) string { + if in { + return "yes" + } + return "no" +} +func randHexString(bytes int) string { + buf := make([]byte, bytes) + _, _ = rand.Read(buf) + return fmt.Sprintf("%x", buf) +} + +func timeNowFormat(layout string) string { + return time.Now().UTC().Format(layout) +} + +var tplFuncMap = template.FuncMap{ + "randHexString": randHexString, + "stringsJoin": strings.Join, + "yesNoBool": yesNoBool, + "timeNowFormat": timeNowFormat, +} + +// TokenNameData defines an interface for objects that contain a token name and +// methods for obtaining data relevant to token-based operations. +// +// This interface provides a contract for structures that need to offer +// token name data and conversion capabilities. It is used to ensure consistent +// handling of token names and associated logic. +type TokenNameData interface { + // GetName returns the token's name as a string + GetName() string + // LogicalResponseData returns a map containing relevant data that can be used in template operations or logical evaluations + LogicalResponseData() map[string]any + // IsNil returns a boolean indicating whether the instance is considered nil or invalid + IsNil() bool +} + +// ValidateTokenNameName validates the template syntax of a token name. +// +// This function checks if the provided TokenNameData instance is non-nil and executes +// basic validation of the token name's syntax by parsing it as a template. This helps +// ensure the token name format adheres to expected patterns and contains no syntax errors. +func ValidateTokenNameName(role TokenNameData) (err error) { + if role == nil || role.IsNil() { + return fmt.Errorf("role: %w", errs.ErrNilValue) + } + _, err = template.New("name").Funcs(tplFuncMap).Parse(role.GetName()) + return err +} + +// TokenName generates a token name by executing the template defined in TokenNameData. +// +// This function retrieves the template string from the TokenNameData, parses it, and +// then executes it while substituting placeholders with the logical response data +// provided by the token role. An additional "unix_timestamp_utc" field is added to the +// data map, representing the current UTC Unix timestamp. +func TokenName(role TokenNameData) (name string, err error) { + if role == nil || role.IsNil() { + return "", fmt.Errorf("role: %w", errs.ErrNilValue) + } + var tpl *template.Template + tpl, err = template.New("name").Funcs(tplFuncMap).Parse(role.GetName()) + if err != nil { + return "", err + } + buf := new(strings.Builder) + var data = role.LogicalResponseData() + data["unix_timestamp_utc"] = time.Now().UTC().Unix() + delete(data, "name") + err = tpl.Execute(buf, data) + name = buf.String() + return name, err +} diff --git a/internal/utils/name_tpl_test.go b/internal/utils/name_tpl_test.go new file mode 100644 index 0000000..9ca66e9 --- /dev/null +++ b/internal/utils/name_tpl_test.go @@ -0,0 +1,137 @@ +package utils_test + +import ( + "fmt" + "strconv" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/utils" +) + +type tokenName struct { + name string + data map[string]any +} + +func (t *tokenName) IsNil() bool { + return t == nil +} + +func (t *tokenName) GetName() string { + return t.name +} + +func (t *tokenName) LogicalResponseData() map[string]any { + if t.data == nil { + return make(map[string]any) + } + return t.data +} + +var _ utils.TokenNameData = (*tokenName)(nil) + +func TestTokenNameGenerator(t *testing.T) { + var tests = []struct { + in *tokenName + outVal string + outErr bool + }{ + {nil, "", true}, + + // invalid template + { + &tokenName{ + name: "{{ .role_name", + }, + "", + true, + }, + + // combination template + { + &tokenName{ + name: "{{ .role_name }}-{{ .token_type }}-access-token-{{ yesNoBool .gitlab_revokes_token }}", + data: map[string]any{ + "role_name": "test", + "token_type": "personal", + "gitlab_revokes_token": true, + }, + }, + "test-personal-access-token-yes", + false, + }, + + // with stringsJoin + { + &tokenName{ + name: "{{ .role_name }}-{{ .token_type }}-{{ stringsJoin .scopes \"-\" }}-{{ yesNoBool .gitlab_revokes_token }}", + data: map[string]any{ + "role_name": "test", + "token_type": "personal", + "scopes": []string{"api", "sudo"}, + "gitlab_revokes_token": false, + }, + }, + "test-personal-api-sudo-no", + false, + }, + + // with timeNowFormat + { + &tokenName{ + name: "{{ .role_name }}-{{ .token_type }}-{{ timeNowFormat \"2006-01\" }}", + data: map[string]any{ + "role_name": "test", + "token_type": "personal", + }, + }, + fmt.Sprintf("test-personal-%d-%02d", time.Now().UTC().Year(), time.Now().UTC().Month()), + false, + }, + } + + for _, tst := range tests { + t.Logf("TokenName(%v)", tst.in) + val, err := utils.TokenName(tst.in) + assert.Equal(t, tst.outVal, val) + if tst.outErr { + assert.Error(t, err, tst.outErr) + } else { + assert.NoError(t, err) + } + } +} + +func TestValidateTokenNameName(t *testing.T) { + require.Error(t, utils.ValidateTokenNameName(&tokenName{name: "{{ .name"})) + require.Error(t, utils.ValidateTokenNameName(nil)) +} + +func TestTokenNameGenerator_RandString(t *testing.T) { + val, err := utils.TokenName( + &tokenName{ + name: "{{ randHexString 8 }}", + }, + ) + require.NoError(t, err) + require.NotEmpty(t, val) + require.Len(t, val, 16) +} + +func TestTokenNameGenerator_UnixTimeStamp(t *testing.T) { + now := time.Now().UTC().Unix() + val, err := utils.TokenName( + &tokenName{ + name: "{{ .unix_timestamp_utc }}", + }, + ) + require.NoError(t, err) + require.NotEmpty(t, val) + i, err := strconv.ParseInt(val, 10, 64) + require.NoError(t, err) + require.GreaterOrEqual(t, i, now) +} diff --git a/internal/utils/to_any.go b/internal/utils/to_any.go new file mode 100644 index 0000000..3af9bae --- /dev/null +++ b/internal/utils/to_any.go @@ -0,0 +1,15 @@ +package utils + +// ToAny converts a slice of values of type int or string to a slice of empty interfaces (any). +// +// This function is a generic utility that allows for the conversion of a variadic list +// of integers or strings into a slice of empty interfaces (`[]any`). This is particularly +// useful when you need to pass mixed-type values around, or when API requirements dictate +// an `any` type. +func ToAny[T int | string](values ...T) (ret []any) { + ret = make([]any, 0, len(values)) + for _, value := range values { + ret = append(ret, value) + } + return ret +} diff --git a/internal/utils/to_any_test.go b/internal/utils/to_any_test.go new file mode 100644 index 0000000..d8b115f --- /dev/null +++ b/internal/utils/to_any_test.go @@ -0,0 +1,62 @@ +package utils_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/utils" +) + +func TestToAny(t *testing.T) { + tests := []struct { + name string + input any + expected []any + }{ + { + name: "empty int slice", + input: []int{}, + expected: []any{}, + }, + { + name: "single int", + input: []int{42}, + expected: []any{42}, + }, + { + name: "multiple ints", + input: []int{1, 2, 3}, + expected: []any{1, 2, 3}, + }, + { + name: "empty string slice", + input: []string{}, + expected: []any{}, + }, + { + name: "single string", + input: []string{"hello"}, + expected: []any{"hello"}, + }, + { + name: "multiple strings", + input: []string{"a", "b", "c"}, + expected: []any{"a", "b", "c"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var result []any + switch v := tt.input.(type) { + case []int: + result = utils.ToAny(v...) + case []string: + result = utils.ToAny(v...) + } + + assert.EqualValues(t, tt.expected, result) + }) + } +} diff --git a/name_tpl.go b/name_tpl.go deleted file mode 100644 index 931eb18..0000000 --- a/name_tpl.go +++ /dev/null @@ -1,51 +0,0 @@ -package gitlab - -import ( - "crypto/rand" - "fmt" - "strings" - "text/template" - "time" - _ "unsafe" -) - -func yesNoBool(in bool) string { - if in { - return "yes" - } - return "no" -} -func randHexString(bytes int) string { - buf := make([]byte, bytes) - _, _ = rand.Read(buf) - return fmt.Sprintf("%x", buf) -} - -func timeNowFormat(layout string) string { - return time.Now().UTC().Format(layout) -} - -var tplFuncMap = template.FuncMap{ - "randHexString": randHexString, - "stringsJoin": strings.Join, - "yesNoBool": yesNoBool, - "timeNowFormat": timeNowFormat, -} - -func TokenName(role *EntryRole) (name string, err error) { - if role == nil { - return "", fmt.Errorf("role: %w", ErrNilValue) - } - var tpl *template.Template - tpl, err = template.New("name").Funcs(tplFuncMap).Parse(role.Name) - if err != nil { - return "", err - } - buf := new(strings.Builder) - var data = role.LogicalResponseData() - data["unix_timestamp_utc"] = time.Now().UTC().Unix() - delete(data, "name") - err = tpl.Execute(buf, data) - name = buf.String() - return name, err -} diff --git a/name_tpl_rand_string_test.go b/name_tpl_rand_string_test.go deleted file mode 100644 index f079f87..0000000 --- a/name_tpl_rand_string_test.go +++ /dev/null @@ -1,30 +0,0 @@ -//go:build unit - -package gitlab_test - -import ( - "testing" - "time" - - "github.com/stretchr/testify/require" - - g "github.com/ilijamt/vault-plugin-secrets-gitlab" -) - -func TestTokenNameGenerator_RandString(t *testing.T) { - val, err := g.TokenName( - &g.EntryRole{ - RoleName: "test", - TTL: time.Hour, - Path: "/path", - Name: "{{ randHexString 8 }}", - Scopes: []string{g.TokenScopeApi.String()}, - AccessLevel: g.AccessLevelNoPermissions, - TokenType: g.TokenTypePersonal, - GitlabRevokesTokens: false, - }, - ) - require.NoError(t, err) - require.NotEmpty(t, val) - require.Len(t, val, 16) -} diff --git a/name_tpl_test.go b/name_tpl_test.go deleted file mode 100644 index dd534b8..0000000 --- a/name_tpl_test.go +++ /dev/null @@ -1,98 +0,0 @@ -//go:build unit - -package gitlab_test - -import ( - "fmt" - "testing" - "time" - - "github.com/stretchr/testify/assert" - - g "github.com/ilijamt/vault-plugin-secrets-gitlab" -) - -func TestTokenNameGenerator(t *testing.T) { - var tests = []struct { - in *g.EntryRole - outVal string - outErr bool - }{ - {nil, "", true}, - - // invalid template - { - &g.EntryRole{ - RoleName: "test", - TTL: time.Hour, - Path: "/path", - Name: "{{ .role_name", - Scopes: []string{g.TokenScopeApi.String()}, - AccessLevel: g.AccessLevelNoPermissions, - TokenType: g.TokenTypePersonal, - GitlabRevokesTokens: true, - }, - "", - true, - }, - - // combination template - { - &g.EntryRole{ - RoleName: "test", - TTL: time.Hour, - Path: "/path", - Name: "{{ .role_name }}-{{ .token_type }}-access-token-{{ yesNoBool .gitlab_revokes_token }}", - Scopes: []string{g.TokenScopeApi.String()}, - AccessLevel: g.AccessLevelNoPermissions, - TokenType: g.TokenTypePersonal, - GitlabRevokesTokens: true, - }, - "test-personal-access-token-yes", - false, - }, - - // with stringsJoin - { - &g.EntryRole{ - RoleName: "test", - TTL: time.Hour, - Path: "/path", - Name: "{{ .role_name }}-{{ .token_type }}-{{ stringsJoin .scopes \"-\" }}-{{ yesNoBool .gitlab_revokes_token }}", - Scopes: []string{g.TokenScopeApi.String(), g.TokenScopeSudo.String()}, - AccessLevel: g.AccessLevelNoPermissions, - TokenType: g.TokenTypePersonal, - GitlabRevokesTokens: false, - }, - "test-personal-api-sudo-no", - false, - }, - - // with timeNowFormat - { - &g.EntryRole{ - RoleName: "test", - TTL: time.Hour, - Path: "/path", - Name: "{{ .role_name }}-{{ .token_type }}-{{ timeNowFormat \"2006-01\" }}", - Scopes: []string{g.TokenScopeApi.String(), g.TokenScopeSudo.String()}, - AccessLevel: g.AccessLevelNoPermissions, - TokenType: g.TokenTypePersonal, - GitlabRevokesTokens: false, - }, - fmt.Sprintf("test-personal-%d-%02d", time.Now().UTC().Year(), time.Now().UTC().Month()), - false, - }, - } - - for _, tst := range tests { - t.Logf("TokenName(%v)", tst.in) - val, err := g.TokenName(tst.in) - assert.Equal(t, tst.outVal, val) - if tst.outErr { - assert.Error(t, err, tst.outErr) - } else { - assert.NoError(t, err) - } - } -} diff --git a/name_tpl_unix_timestamp_test.go b/name_tpl_unix_timestamp_test.go deleted file mode 100644 index 7579786..0000000 --- a/name_tpl_unix_timestamp_test.go +++ /dev/null @@ -1,34 +0,0 @@ -//go:build unit - -package gitlab_test - -import ( - "strconv" - "testing" - "time" - - "github.com/stretchr/testify/require" - - g "github.com/ilijamt/vault-plugin-secrets-gitlab" -) - -func TestTokenNameGenerator_UnixTimeStamp(t *testing.T) { - now := time.Now().UTC().Unix() - val, err := g.TokenName( - &g.EntryRole{ - RoleName: "test", - TTL: time.Hour, - Path: "/path", - Name: "{{ .unix_timestamp_utc }}", - Scopes: []string{g.TokenScopeApi.String()}, - AccessLevel: g.AccessLevelNoPermissions, - TokenType: g.TokenTypePersonal, - GitlabRevokesTokens: false, - }, - ) - require.NoError(t, err) - require.NotEmpty(t, val) - i, err := strconv.ParseInt(val, 10, 64) - require.NoError(t, err) - require.GreaterOrEqual(t, i, now) -} diff --git a/path_config.go b/path_config.go index 1750fd9..ba359bd 100644 --- a/path_config.go +++ b/path_config.go @@ -11,6 +11,12 @@ import ( "github.com/hashicorp/vault/sdk/framework" "github.com/hashicorp/vault/sdk/logical" g "gitlab.com/gitlab-org/api/client-go" + + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/errs" + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/event" + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/gitlab" + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/models" + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/utils" ) const ( @@ -40,9 +46,9 @@ var ( Type: framework.TypeString, Required: true, AllowedValues: []any{ - TypeSelfManaged, - TypeSaaS, - TypeDedicated, + gitlab.TypeSelfManaged, + gitlab.TypeSaaS, + gitlab.TypeDedicated, }, Description: `The type of GitLab instance you are connecting to. This could typically distinguish between 'self-managed' for on-premises GitLab installations or 'saas' or 'dedicated' for the GitLab SaaS offering. This field helps the plugin to adjust any necessary configurations or request patterns specific to the type of GitLab instance.`, DisplayAttrs: &framework.DisplayAttributes{ @@ -84,11 +90,11 @@ func (b *Backend) pathConfigDelete(ctx context.Context, req *logical.Request, da if config, err := getConfig(ctx, req.Storage, name); err == nil { if config == nil { - return logical.ErrorResponse(ErrBackendNotConfigured.Error()), nil + return logical.ErrorResponse(errs.ErrBackendNotConfigured.Error()), nil } if err = req.Storage.Delete(ctx, fmt.Sprintf("%s/%s", PathConfigStorage, name)); err == nil { - event(ctx, b.Backend, "config-delete", map[string]string{ + _ = event.Event(ctx, b.Backend, operationPrefixGitlabAccessTokens, "config-delete", map[string]string{ "path": fmt.Sprintf("%s/%s", PathConfigStorage, name), }) b.SetClient(nil, name) @@ -106,7 +112,7 @@ func (b *Backend) pathConfigRead(ctx context.Context, req *logical.Request, data var config *EntryConfig if config, err = getConfig(ctx, req.Storage, name); err == nil { if config == nil { - return logical.ErrorResponse(ErrBackendNotConfigured.Error()), nil + return logical.ErrorResponse(errs.ErrBackendNotConfigured.Error()), nil } lrd := config.LogicalResponseData(b.flags.ShowConfigToken) b.Logger().Debug("Reading configuration info", "info", lrd) @@ -125,7 +131,7 @@ func (b *Backend) pathConfigPatch(ctx context.Context, req *logical.Request, dat return nil, err } if config == nil { - return logical.ErrorResponse(ErrBackendNotConfigured.Error()), nil + return logical.ErrorResponse(errs.ErrBackendNotConfigured.Error()), nil } warnings, changes, err = config.Merge(data) @@ -143,7 +149,7 @@ func (b *Backend) pathConfigPatch(ctx context.Context, req *logical.Request, dat defer b.lockClientMutex.Unlock() if err = saveConfig(ctx, *config, req.Storage); err == nil { lrd := config.LogicalResponseData(b.flags.ShowConfigToken) - event(ctx, b.Backend, "config-patch", changes) + _ = event.Event(ctx, b.Backend, operationPrefixGitlabAccessTokens, "config-patch", changes) b.SetClient(nil, name) b.Logger().Debug("Patched config", "lrd", lrd, "warnings", warnings) lResp = &logical.Response{Data: lrd, Warnings: warnings} @@ -152,11 +158,11 @@ func (b *Backend) pathConfigPatch(ctx context.Context, req *logical.Request, dat return lResp, err } -func (b *Backend) updateConfigClientInfo(ctx context.Context, config *EntryConfig) (et *TokenConfig, err error) { +func (b *Backend) updateConfigClientInfo(ctx context.Context, config *EntryConfig) (et *models.TokenConfig, err error) { var httpClient *http.Client var client Client - httpClient, _ = HttpClientFromContext(ctx) - if client, _ = ClientFromContext(ctx); client == nil { + httpClient, _ = utils.HttpClientFromContext(ctx) + if client, _ = gitlab.ClientFromContext(ctx); client == nil { if client, err = NewGitlabClient(config, httpClient, b.Logger()); err == nil { b.SetClient(client, config.Name) } else { @@ -166,7 +172,7 @@ func (b *Backend) updateConfigClientInfo(ctx context.Context, config *EntryConfi et, err = client.CurrentTokenInfo(ctx) if err != nil { - return et, fmt.Errorf("token cannot be validated: %s", ErrInvalidValue) + return et, fmt.Errorf("token cannot be validated: %s", errs.ErrInvalidValue) } config.TokenCreatedAt = *et.CreatedAt @@ -202,7 +208,7 @@ func (b *Backend) pathConfigWrite(ctx context.Context, req *logical.Request, dat var lResp *logical.Response if err = saveConfig(ctx, *config, req.Storage); err == nil { - event(ctx, b.Backend, "config-write", map[string]string{ + _ = event.Event(ctx, b.Backend, operationPrefixGitlabAccessTokens, "config-write", map[string]string{ "path": fmt.Sprintf("%s/%s", PathConfigStorage, name), "auto_rotate_token": strconv.FormatBool(config.AutoRotateToken), "auto_rotate_before": config.AutoRotateBefore.String(), diff --git a/path_config_list_test.go b/path_config_list_test.go index 9a54598..b25151d 100644 --- a/path_config_list_test.go +++ b/path_config_list_test.go @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/require" gitlab "github.com/ilijamt/vault-plugin-secrets-gitlab" + gitlab2 "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/gitlab" ) func TestPathConfigList(t *testing.T) { @@ -37,7 +38,7 @@ func TestPathConfigList(t *testing.T) { map[string]any{ "token": getGitlabToken("admin_user_root").Token, "base_url": cmp.Or(os.Getenv("GITLAB_URL"), "http://localhost:8080/"), - "type": gitlab.TypeSaaS.String(), + "type": gitlab2.TypeSaaS.String(), }, gitlab.DefaultConfigName, ) @@ -51,7 +52,7 @@ func TestPathConfigList(t *testing.T) { map[string]any{ "token": getGitlabToken("admin_user_initial_token").Token, "base_url": cmp.Or(os.Getenv("GITLAB_URL"), "http://localhost:8080/"), - "type": gitlab.TypeSelfManaged.String(), + "type": gitlab2.TypeSelfManaged.String(), }, "admin", ), @@ -62,7 +63,7 @@ func TestPathConfigList(t *testing.T) { map[string]any{ "token": getGitlabToken("normal_user_initial_token").Token, "base_url": cmp.Or(os.Getenv("GITLAB_URL"), "http://localhost:8080/"), - "type": gitlab.TypeDedicated.String(), + "type": gitlab2.TypeDedicated.String(), }, "normal", ), @@ -96,7 +97,7 @@ func TestPathConfigList(t *testing.T) { require.NoError(t, err) require.NotNil(t, resp) require.NotEmpty(t, resp.Data) - require.EqualValues(t, gitlab.TypeSaaS.String(), resp.Data["type"]) + require.EqualValues(t, gitlab2.TypeSaaS.String(), resp.Data["type"]) resp, err = b.HandleRequest(ctx, &logical.Request{ Operation: logical.ReadOperation, @@ -105,7 +106,7 @@ func TestPathConfigList(t *testing.T) { require.NoError(t, err) require.NotNil(t, resp) require.NotEmpty(t, resp.Data) - require.EqualValues(t, gitlab.TypeDedicated.String(), resp.Data["type"]) + require.EqualValues(t, gitlab2.TypeDedicated.String(), resp.Data["type"]) resp, err = b.HandleRequest(ctx, &logical.Request{ Operation: logical.ReadOperation, @@ -114,6 +115,6 @@ func TestPathConfigList(t *testing.T) { require.NoError(t, err) require.NotNil(t, resp) require.NotEmpty(t, resp.Data) - require.EqualValues(t, gitlab.TypeSelfManaged.String(), resp.Data["type"]) + require.EqualValues(t, gitlab2.TypeSelfManaged.String(), resp.Data["type"]) }) } diff --git a/path_config_rotate.go b/path_config_rotate.go index 6663797..a810c50 100644 --- a/path_config_rotate.go +++ b/path_config_rotate.go @@ -10,6 +10,10 @@ import ( "github.com/hashicorp/vault/sdk/framework" "github.com/hashicorp/vault/sdk/logical" + + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/errs" + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/event" + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/models" ) const pathConfigRotateHelpSynopsis = `Rotate the gitlab token for this configuration.` @@ -70,14 +74,14 @@ func (b *Backend) pathConfigTokenRotate(ctx context.Context, request *logical.Re if config == nil { // no configuration yet so we don't need to rotate anything - return logical.ErrorResponse(ErrBackendNotConfigured.Error()), nil + return logical.ErrorResponse(errs.ErrBackendNotConfigured.Error()), nil } if client, err = b.getClient(ctx, request.Storage, name); err != nil { return nil, err } - var entryToken *TokenConfig + var entryToken *models.TokenConfig entryToken, _, err = client.RotateCurrentToken(ctx) if err != nil { b.Logger().Error("Failed to rotate main token", "err", err) @@ -103,7 +107,7 @@ func (b *Backend) pathConfigTokenRotate(ctx context.Context, request *logical.Re lResp = &logical.Response{Data: config.LogicalResponseData(b.flags.ShowConfigToken)} lResp.Data["token"] = config.Token - event(ctx, b.Backend, "config-token-rotate", map[string]string{ + _ = event.Event(ctx, b.Backend, operationPrefixGitlabAccessTokens, "config-token-rotate", map[string]string{ "path": fmt.Sprintf("%s/%s", PathConfigStorage, name), "expires_at": entryToken.ExpiresAt.Format(time.RFC3339), "created_at": entryToken.CreatedAt.Format(time.RFC3339), diff --git a/path_config_rotate_test.go b/path_config_rotate_test.go index 5646dac..d35a8a5 100644 --- a/path_config_rotate_test.go +++ b/path_config_rotate_test.go @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/require" gitlab "github.com/ilijamt/vault-plugin-secrets-gitlab" + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/errs" ) func TestPathConfigRotate(t *testing.T) { @@ -24,6 +25,6 @@ func TestPathConfigRotate(t *testing.T) { require.NoError(t, err) require.NotNil(t, resp) require.Error(t, resp.Error()) - require.EqualValues(t, resp.Error(), gitlab.ErrBackendNotConfigured) + require.EqualValues(t, resp.Error(), errs.ErrBackendNotConfigured) }) } diff --git a/path_config_test.go b/path_config_test.go index 5f0ea36..266138d 100644 --- a/path_config_test.go +++ b/path_config_test.go @@ -12,6 +12,10 @@ import ( "github.com/stretchr/testify/require" gitlab "github.com/ilijamt/vault-plugin-secrets-gitlab" + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/errs" + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/flags" + gitlab2 "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/gitlab" + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/utils" ) func TestPathConfig(t *testing.T) { @@ -26,7 +30,7 @@ func TestPathConfig(t *testing.T) { require.NoError(t, err) require.NotNil(t, resp) require.Error(t, resp.Error()) - require.EqualValues(t, resp.Error(), gitlab.ErrBackendNotConfigured) + require.EqualValues(t, resp.Error(), errs.ErrBackendNotConfigured) }) t.Run("deleting uninitialized config should fail with backend not configured", func(t *testing.T) { @@ -42,12 +46,12 @@ func TestPathConfig(t *testing.T) { require.NotNil(t, resp) require.Error(t, resp.Error()) require.True(t, resp.IsError()) - require.EqualValues(t, resp.Error(), gitlab.ErrBackendNotConfigured) + require.EqualValues(t, resp.Error(), errs.ErrBackendNotConfigured) }) t.Run("write, read, delete and read config", func(t *testing.T) { httpClient, url := getClient(t, "unit") - ctx := gitlab.HttpClientNewContext(t.Context(), httpClient) + ctx := utils.HttpClientNewContext(t.Context(), httpClient) b, l, events, err := getBackendWithEvents(ctx) require.NoError(t, err) @@ -58,7 +62,7 @@ func TestPathConfig(t *testing.T) { Data: map[string]any{ "token": getGitlabToken("admin_user_root").Token, "base_url": url, - "type": gitlab.TypeSelfManaged.String(), + "type": gitlab2.TypeSelfManaged.String(), }, }) @@ -103,9 +107,9 @@ func TestPathConfig(t *testing.T) { t.Run("write, read, delete and read config with show config token", func(t *testing.T) { httpClient, url := getClient(t, "unit") - ctx := gitlab.HttpClientNewContext(t.Context(), httpClient) + ctx := utils.HttpClientNewContext(t.Context(), httpClient) - b, l, events, err := getBackendWithFlagsWithEvents(ctx, gitlab.Flags{ShowConfigToken: true}) + b, l, events, err := getBackendWithFlagsWithEvents(ctx, flags.Flags{ShowConfigToken: true}) require.NoError(t, err) resp, err := b.HandleRequest(ctx, &logical.Request{ @@ -114,7 +118,7 @@ func TestPathConfig(t *testing.T) { Data: map[string]any{ "token": getGitlabToken("admin_user_root").Token, "base_url": url, - "type": gitlab.TypeSelfManaged.String(), + "type": gitlab2.TypeSelfManaged.String(), }, }) @@ -158,7 +162,7 @@ func TestPathConfig(t *testing.T) { }) t.Run("invalid token", func(t *testing.T) { httpClient, url := getClient(t, "unit") - ctx := gitlab.HttpClientNewContext(t.Context(), httpClient) + ctx := utils.HttpClientNewContext(t.Context(), httpClient) b, l, events, err := getBackendWithEvents(ctx) require.NoError(t, err) @@ -169,7 +173,7 @@ func TestPathConfig(t *testing.T) { Data: map[string]any{ "token": "invalid-token", "base_url": url, - "type": gitlab.TypeSelfManaged.String(), + "type": gitlab2.TypeSelfManaged.String(), }, }) @@ -194,13 +198,13 @@ func TestPathConfig(t *testing.T) { require.Nil(t, resp) var errorMap = countErrByName(err.(*multierror.Error)) - assert.EqualValues(t, 3, errorMap[gitlab.ErrFieldRequired.Error()]) + assert.EqualValues(t, 3, errorMap[errs.ErrFieldRequired.Error()]) require.Len(t, errorMap, 1) }) t.Run("patch a config with no storage", func(t *testing.T) { httpClient, url := getClient(t, "unit") - ctx := gitlab.HttpClientNewContext(t.Context(), httpClient) + ctx := utils.HttpClientNewContext(t.Context(), httpClient) b, _, err := getBackend(ctx) require.NoError(t, err) @@ -211,17 +215,17 @@ func TestPathConfig(t *testing.T) { Data: map[string]any{ "token": getGitlabToken("admin_user_root").Token, "base_url": url, - "type": gitlab.TypeSelfManaged.String(), + "type": gitlab2.TypeSelfManaged.String(), }, }) - require.ErrorIs(t, err, gitlab.ErrNilValue) + require.ErrorIs(t, err, errs.ErrNilValue) require.Nil(t, resp) }) t.Run("patch a config no backend", func(t *testing.T) { httpClient, url := getClient(t, "unit") - ctx := gitlab.HttpClientNewContext(t.Context(), httpClient) + ctx := utils.HttpClientNewContext(t.Context(), httpClient) b, l, err := getBackend(ctx) require.NoError(t, err) @@ -232,18 +236,18 @@ func TestPathConfig(t *testing.T) { Data: map[string]any{ "token": getGitlabToken("admin_user_root").Token, "base_url": url, - "type": gitlab.TypeSelfManaged.String(), + "type": gitlab2.TypeSelfManaged.String(), }, }) require.NoError(t, err) require.NotNil(t, resp) - require.EqualValues(t, resp.Error(), gitlab.ErrBackendNotConfigured) + require.EqualValues(t, resp.Error(), errs.ErrBackendNotConfigured) }) t.Run("patch a config", func(t *testing.T) { httpClient, url := getClient(t, "unit") - ctx := gitlab.HttpClientNewContext(t.Context(), httpClient) + ctx := utils.HttpClientNewContext(t.Context(), httpClient) var path = fmt.Sprintf("%s/%s", gitlab.PathConfigStorage, gitlab.DefaultConfigName) b, l, events, err := getBackendWithEvents(ctx) @@ -255,7 +259,7 @@ func TestPathConfig(t *testing.T) { Data: map[string]any{ "token": getGitlabToken("admin_user_root").Token, "base_url": url, - "type": gitlab.TypeSelfManaged.String(), + "type": gitlab2.TypeSelfManaged.String(), }, }) @@ -273,14 +277,14 @@ func TestPathConfig(t *testing.T) { require.NoError(t, resp.Error()) tokenOriginalSha1Hash := resp.Data["token_sha1_hash"].(string) require.NotEmpty(t, tokenOriginalSha1Hash) - require.Equal(t, gitlab.TypeSelfManaged.String(), resp.Data["type"]) + require.Equal(t, gitlab2.TypeSelfManaged.String(), resp.Data["type"]) require.NotNil(t, b.GetClient(gitlab.DefaultConfigName).GitlabClient(ctx)) resp, err = b.HandleRequest(ctx, &logical.Request{ Operation: logical.PatchOperation, Path: path, Storage: l, Data: map[string]interface{}{ - "type": gitlab.TypeSaaS.String(), + "type": gitlab2.TypeSaaS.String(), "token": getGitlabToken("admin_user_initial_token").Token, }, }) @@ -291,7 +295,7 @@ func TestPathConfig(t *testing.T) { require.NotEmpty(t, tokenNewSha1Hash) require.NotEqual(t, tokenOriginalSha1Hash, tokenNewSha1Hash) - require.Equal(t, gitlab.TypeSaaS.String(), resp.Data["type"]) + require.Equal(t, gitlab2.TypeSaaS.String(), resp.Data["type"]) require.NotNil(t, b.GetClient(gitlab.DefaultConfigName).GitlabClient(ctx)) events.expectEvents(t, []expectedEvent{ diff --git a/path_config_token_autorotate_test.go b/path_config_token_autorotate_test.go index 231a472..836b226 100644 --- a/path_config_token_autorotate_test.go +++ b/path_config_token_autorotate_test.go @@ -12,6 +12,8 @@ import ( "github.com/stretchr/testify/require" gitlab "github.com/ilijamt/vault-plugin-secrets-gitlab" + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/errs" + gitlab2 "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/gitlab" ) func TestPathConfig_AutoRotate(t *testing.T) { @@ -25,7 +27,7 @@ func TestPathConfig_AutoRotate(t *testing.T) { Data: map[string]any{ "token": getGitlabToken("admin_user_root").Token, "base_url": url, - "type": gitlab.TypeSelfManaged.String(), + "type": gitlab2.TypeSelfManaged.String(), }, }) require.NoError(t, err) @@ -44,12 +46,12 @@ func TestPathConfig_AutoRotate(t *testing.T) { "token": getGitlabToken("admin_user_root").Token, "base_url": url, "auto_rotate_before": "2h", - "type": gitlab.TypeSelfManaged.String(), + "type": gitlab2.TypeSelfManaged.String(), }, }) require.Error(t, err) require.Nil(t, resp) - require.ErrorIs(t, err, gitlab.ErrInvalidValue) + require.ErrorIs(t, err, errs.ErrInvalidValue) }) t.Run("auto_rotate_before should be less than the maximal limit", func(t *testing.T) { @@ -63,10 +65,10 @@ func TestPathConfig_AutoRotate(t *testing.T) { "token": getGitlabToken("admin_user_root").Token, "base_url": url, "auto_rotate_before": (gitlab.DefaultAutoRotateBeforeMaxTTL + time.Hour).String(), - "type": gitlab.TypeSelfManaged.String(), + "type": gitlab2.TypeSelfManaged.String(), }, }) - require.ErrorIs(t, err, gitlab.ErrInvalidValue) + require.ErrorIs(t, err, errs.ErrInvalidValue) require.Nil(t, resp) }) @@ -81,7 +83,7 @@ func TestPathConfig_AutoRotate(t *testing.T) { "token": getGitlabToken("admin_user_root").Token, "base_url": url, "auto_rotate_before": "48h", - "type": gitlab.TypeSelfManaged.String(), + "type": gitlab2.TypeSelfManaged.String(), }, }) require.NoError(t, err) @@ -100,10 +102,10 @@ func TestPathConfig_AutoRotate(t *testing.T) { "token": getGitlabToken("admin_user_root").Token, "base_url": url, "auto_rotate_before": (gitlab.DefaultAutoRotateBeforeMinTTL - time.Hour).String(), - "type": gitlab.TypeSelfManaged.String(), + "type": gitlab2.TypeSelfManaged.String(), }, }) - require.ErrorIs(t, err, gitlab.ErrInvalidValue) + require.ErrorIs(t, err, errs.ErrInvalidValue) require.Nil(t, resp) }) @@ -117,7 +119,7 @@ func TestPathConfig_AutoRotate(t *testing.T) { Data: map[string]any{ "token": getGitlabToken("admin_user_root").Token, "base_url": url, - "type": gitlab.TypeSelfManaged.String(), + "type": gitlab2.TypeSelfManaged.String(), }, }) require.NoError(t, err) @@ -136,10 +138,10 @@ func TestPathConfig_AutoRotate(t *testing.T) { "token": getGitlabToken("admin_user_root").Token, "base_url": url, "auto_rotate_before": "10h", - "type": gitlab.TypeSelfManaged.String(), + "type": gitlab2.TypeSelfManaged.String(), }, }) - require.ErrorIs(t, err, gitlab.ErrInvalidValue) + require.ErrorIs(t, err, errs.ErrInvalidValue) require.Nil(t, resp) }) } @@ -157,11 +159,11 @@ func TestPathConfig_AutoRotateToken(t *testing.T) { t.Run("no error when auto rotate is disabled and config is set", func(t *testing.T) { var client = newInMemoryClient(true) ctx, url := getCtxGitlabClientWithUrl(t, "unit") - ctx = gitlab.ClientNewContext(ctx, client) + ctx = gitlab2.ClientNewContext(ctx, client) b, l, err := getBackendWithConfig(ctx, map[string]any{ "token": "glpat-secret-token", "base_url": url, - "type": gitlab.TypeSelfManaged.String(), + "type": gitlab2.TypeSelfManaged.String(), }) require.NoError(t, err) @@ -173,13 +175,13 @@ func TestPathConfig_AutoRotateToken(t *testing.T) { t.Run("call auto rotate the main token and rotate the token", func(t *testing.T) { var client = newInMemoryClient(true) ctx, url := getCtxGitlabClientWithUrl(t, "unit") - ctx = gitlab.ClientNewContext(ctx, newInMemoryClient(true)) + ctx = gitlab2.ClientNewContext(ctx, newInMemoryClient(true)) b, l, events, err := getBackendWithEventsAndConfig(ctx, map[string]any{ "token": "token", "base_url": url, "auto_rotate_token": true, "auto_rotate_before": "360h", - "type": gitlab.TypeSelfManaged.String(), + "type": gitlab2.TypeSelfManaged.String(), }) require.NoError(t, err) @@ -224,13 +226,13 @@ func TestPathConfig_AutoRotateToken(t *testing.T) { t.Run("call auto rotate the main token but the token is still valid", func(t *testing.T) { var client = newInMemoryClient(true) ctx, url := getCtxGitlabClientWithUrl(t, "unit") - ctx = gitlab.ClientNewContext(ctx, newInMemoryClient(true)) + ctx = gitlab2.ClientNewContext(ctx, newInMemoryClient(true)) b, l, err := getBackendWithConfig(ctx, map[string]any{ "token": "token", "base_url": url, "auto_rotate_token": true, "auto_rotate_before": "24h", - "type": gitlab.TypeSelfManaged.String(), + "type": gitlab2.TypeSelfManaged.String(), }) require.NoError(t, err) diff --git a/path_flags.go b/path_flags.go index b9d1f85..ededa5c 100644 --- a/path_flags.go +++ b/path_flags.go @@ -9,6 +9,8 @@ import ( "github.com/hashicorp/vault/sdk/framework" "github.com/hashicorp/vault/sdk/logical" "github.com/mitchellh/mapstructure" + + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/event" ) const ( @@ -43,7 +45,7 @@ func (b *Backend) pathFlagsUpdate(ctx context.Context, req *logical.Request, dat eventData["show_config_token"] = strconv.FormatBool(b.flags.ShowConfigToken) } - event(ctx, b.Backend, "flags-write", eventData) + _ = event.Event(ctx, b.Backend, operationPrefixGitlabAccessTokens, "flags-write", eventData) var flagData map[string]any err = mapstructure.Decode(b.flags, &flagData) diff --git a/path_flags_test.go b/path_flags_test.go index d68eadc..f5c0b88 100644 --- a/path_flags_test.go +++ b/path_flags_test.go @@ -9,11 +9,12 @@ import ( "github.com/stretchr/testify/require" gitlab "github.com/ilijamt/vault-plugin-secrets-gitlab" + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/flags" ) func TestPathFlags(t *testing.T) { var ctx = t.Context() - b, l, events, err := getBackendWithFlagsWithEvents(ctx, gitlab.Flags{AllowRuntimeFlagsChange: true}) + b, l, events, err := getBackendWithFlagsWithEvents(ctx, flags.Flags{AllowRuntimeFlagsChange: true}) require.NoError(t, err) resp, err := b.HandleRequest(ctx, &logical.Request{ diff --git a/path_role.go b/path_role.go index 584b1d8..e0a651d 100644 --- a/path_role.go +++ b/path_role.go @@ -7,13 +7,18 @@ import ( "net/http" "slices" "strings" - "text/template" "time" "github.com/hashicorp/go-multierror" "github.com/hashicorp/vault/sdk/framework" "github.com/hashicorp/vault/sdk/helper/locksutil" "github.com/hashicorp/vault/sdk/logical" + + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/errs" + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/event" + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/gitlab" + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/token" + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/utils" ) const ( @@ -54,7 +59,7 @@ var ( DisplayAttrs: &framework.DisplayAttributes{ Name: "Scopes", }, - AllowedValues: allowedValues(ValidPersonalTokenScopes...), + AllowedValues: utils.ToAny(token.ValidPersonalTokenScopes...), }, "ttl": { Type: framework.TypeDurationSecond, @@ -71,13 +76,13 @@ var ( DisplayAttrs: &framework.DisplayAttributes{ Name: "Access Level", }, - AllowedValues: allowedValues(ValidAccessLevels...), + AllowedValues: utils.ToAny(token.ValidAccessLevels...), }, "token_type": { Type: framework.TypeString, Description: "access token type", Required: true, - AllowedValues: allowedValues(validTokenTypes...), + AllowedValues: utils.ToAny(token.ValidTokenTypes...), DisplayAttrs: &framework.DisplayAttributes{ Name: "Token Type", }, @@ -161,7 +166,7 @@ func (b *Backend) pathRolesDelete(ctx context.Context, req *logical.Request, dat return nil, fmt.Errorf("error deleting role: %w", err) } - event(ctx, b.Backend, "role-delete", map[string]string{ + _ = event.Event(ctx, b.Backend, operationPrefixGitlabAccessTokens, "role-delete", map[string]string{ "path": "roles", "role_name": roleName, }) @@ -199,8 +204,8 @@ func (b *Backend) pathRolesWrite(ctx context.Context, req *logical.Request, data var config *EntryConfig var err error var warnings []string - var tokenType TokenType - var accessLevel AccessLevel + var tokenType token.Type + var accessLevel token.AccessLevel var configName = cmp.Or(data.Get("config_name").(string), TypeConfigDefault) b.lockClientMutex.RLock() @@ -211,11 +216,11 @@ func (b *Backend) pathRolesWrite(ctx context.Context, req *logical.Request, data } if config == nil { - return logical.ErrorResponse(ErrBackendNotConfigured.Error()), nil + return logical.ErrorResponse(errs.ErrBackendNotConfigured.Error()), nil } - tokenType, _ = TokenTypeParse(data.Get("token_type").(string)) - accessLevel, _ = AccessLevelParse(data.Get("access_level").(string)) + tokenType, _ = token.ParseType(data.Get("token_type").(string)) + accessLevel, _ = token.AccessLevelParse(data.Get("access_level").(string)) var role = EntryRole{ RoleName: roleName, @@ -230,13 +235,13 @@ func (b *Backend) pathRolesWrite(ctx context.Context, req *logical.Request, data } // validate name of the entry role - if _, e := template.New("name").Funcs(tplFuncMap).Parse(role.Name); e != nil { + if e := utils.ValidateTokenNameName(role); e != nil { err = multierror.Append(err, fmt.Errorf("invalid template %s for name: %w", role.Name, e)) } // validate token type - if !slices.Contains(validTokenTypes, tokenType.String()) { - err = multierror.Append(err, fmt.Errorf("token_type='%s', should be one of %v: %w", data.Get("token_type").(string), validTokenTypes, ErrFieldInvalidValue)) + if !slices.Contains(token.ValidTokenTypes, tokenType.String()) { + err = multierror.Append(err, fmt.Errorf("token_type='%s', should be one of %v: %w", data.Get("token_type").(string), token.ValidTokenTypes, errs.ErrFieldInvalidValue)) } // validate access level and which fields to skip for validation @@ -246,44 +251,44 @@ func (b *Backend) pathRolesWrite(ctx context.Context, req *logical.Request, data var skipFields []string switch tokenType { - case TokenTypePersonal: - validAccessLevels = ValidPersonalAccessLevels - validScopes = ValidPersonalTokenScopes + case token.TypePersonal: + validAccessLevels = token.ValidPersonalAccessLevels + validScopes = token.ValidPersonalTokenScopes noEmptyScopes = false skipFields = []string{"config_name", "access_level"} - case TokenTypeGroup: - validAccessLevels = ValidGroupAccessLevels - validScopes = ValidGroupTokenScopes + case token.TypeGroup: + validAccessLevels = token.ValidGroupAccessLevels + validScopes = token.ValidGroupTokenScopes noEmptyScopes = false skipFields = []string{"config_name"} - case TokenTypeProject: - validAccessLevels = ValidProjectAccessLevels - validScopes = ValidProjectTokenScopes + case token.TypeProject: + validAccessLevels = token.ValidProjectAccessLevels + validScopes = token.ValidProjectTokenScopes noEmptyScopes = false skipFields = []string{"config_name"} - case TokenTypeUserServiceAccount: - validAccessLevels = ValidUserServiceAccountAccessLevels - validScopes = ValidUserServiceAccountTokenScopes + case token.TypeUserServiceAccount: + validAccessLevels = token.ValidUserServiceAccountAccessLevels + validScopes = token.ValidUserServiceAccountTokenScopes noEmptyScopes = false skipFields = []string{"config_name", "access_level"} - case TokenTypeGroupServiceAccount: - validAccessLevels = ValidGroupServiceAccountAccessLevels - validScopes = ValidGroupServiceAccountTokenScopes + case token.TypeGroupServiceAccount: + validAccessLevels = token.ValidGroupServiceAccountAccessLevels + validScopes = token.ValidGroupServiceAccountTokenScopes noEmptyScopes = false skipFields = []string{"config_name", "access_level"} - case TokenTypePipelineProjectTrigger: - validAccessLevels = ValidPipelineProjectTriggerAccessLevels + case token.TypePipelineProjectTrigger: + validAccessLevels = token.ValidPipelineProjectTriggerAccessLevels validScopes = []string{} noEmptyScopes = false skipFields = []string{"config_name", "access_level", "scopes"} - case TokenTypeProjectDeploy: - validAccessLevels = ValidProjectDeployAccessLevels - validScopes = ValidProjectDeployTokenScopes + case token.TypeProjectDeploy: + validAccessLevels = token.ValidProjectDeployAccessLevels + validScopes = token.ValidProjectDeployTokenScopes noEmptyScopes = true skipFields = []string{"config_name", "access_level"} - case TokenTypeGroupDeploy: - validAccessLevels = ValidGroupDeployAccessLevels - validScopes = ValidGroupDeployTokenScopes + case token.TypeGroupDeploy: + validAccessLevels = token.ValidGroupDeployAccessLevels + validScopes = token.ValidGroupDeployTokenScopes noEmptyScopes = true skipFields = []string{"config_name", "access_level"} } @@ -297,37 +302,37 @@ func (b *Backend) pathRolesWrite(ctx context.Context, req *logical.Request, data } val, ok, _ := data.GetOkErr(name) - if (tokenType == TokenTypePersonal && name == "access_level") || + if (tokenType == token.TypePersonal && name == "access_level") || name == "gitlab_revokes_token" { continue } var required = field.Required - if name == "ttl" && !slices.Contains([]TokenType{TokenTypePipelineProjectTrigger}, tokenType) { + if name == "ttl" && !slices.Contains([]token.Type{token.TypePipelineProjectTrigger}, tokenType) { required = true } if required && !ok { - err = multierror.Append(err, fmt.Errorf("%s: %w", name, ErrFieldRequired)) + err = multierror.Append(err, fmt.Errorf("%s: %w", name, errs.ErrFieldRequired)) } else if !required && val == nil { warnings = append(warnings, fmt.Sprintf("field '%s' is using expected default value of %v", name, val)) } if required && name == "ttl" { if role.TTL > DefaultAccessTokenMaxPossibleTTL { - err = multierror.Append(err, fmt.Errorf("ttl = %s [ttl <= max_ttl = %s]: %w", role.TTL.String(), DefaultAccessTokenMaxPossibleTTL, ErrInvalidValue)) + err = multierror.Append(err, fmt.Errorf("ttl = %s [ttl <= max_ttl = %s]: %w", role.TTL.String(), DefaultAccessTokenMaxPossibleTTL, errs.ErrInvalidValue)) } if role.GitlabRevokesTokens && role.TTL < 24*time.Hour { - err = multierror.Append(err, fmt.Errorf("ttl = %s [%s <= ttl <= %s]: %w", role.TTL, DefaultAccessTokenMinTTL, DefaultAccessTokenMaxPossibleTTL, ErrInvalidValue)) + err = multierror.Append(err, fmt.Errorf("ttl = %s [%s <= ttl <= %s]: %w", role.TTL, DefaultAccessTokenMinTTL, DefaultAccessTokenMaxPossibleTTL, errs.ErrInvalidValue)) } if !role.GitlabRevokesTokens && role.TTL < time.Hour { - err = multierror.Append(err, fmt.Errorf("ttl = %s [ttl >= 1h]: %w", role.TTL, ErrInvalidValue)) + err = multierror.Append(err, fmt.Errorf("ttl = %s [ttl >= 1h]: %w", role.TTL, errs.ErrInvalidValue)) } } } if !slices.Contains(validAccessLevels, accessLevel.String()) { - err = multierror.Append(err, fmt.Errorf("access_level='%s', should be one of %v: %w", data.Get("access_level").(string), validAccessLevels, ErrFieldInvalidValue)) + err = multierror.Append(err, fmt.Errorf("access_level='%s', should be one of %v: %w", data.Get("access_level").(string), validAccessLevels, errs.ErrFieldInvalidValue)) } for _, scope := range role.Scopes { @@ -337,15 +342,15 @@ func (b *Backend) pathRolesWrite(ctx context.Context, req *logical.Request, data } if len(invalidScopes) > 0 { - err = multierror.Append(err, fmt.Errorf("scopes='%v', should be one or more of '%v': %w", invalidScopes, validScopes, ErrFieldInvalidValue)) + err = multierror.Append(err, fmt.Errorf("scopes='%v', should be one or more of '%v': %w", invalidScopes, validScopes, errs.ErrFieldInvalidValue)) } if noEmptyScopes && len(role.Scopes) == 0 { - err = multierror.Append(err, fmt.Errorf("should be one or more of '%v': %w", validScopes, ErrFieldInvalidValue)) + err = multierror.Append(err, fmt.Errorf("should be one or more of '%v': %w", validScopes, errs.ErrFieldInvalidValue)) } - if tokenType == TokenTypeUserServiceAccount && (config.Type == TypeSaaS || config.Type == TypeDedicated) { - err = multierror.Append(err, fmt.Errorf("cannot create %s with %s: %w", tokenType, config.Type, ErrInvalidValue)) + if tokenType == token.TypeUserServiceAccount && (config.Type == gitlab.TypeSaaS || config.Type == gitlab.TypeDedicated) { + err = multierror.Append(err, fmt.Errorf("cannot create %s with %s: %w", tokenType, config.Type, errs.ErrInvalidValue)) } if err != nil { @@ -365,7 +370,7 @@ func (b *Backend) pathRolesWrite(ctx context.Context, req *logical.Request, data return nil, err } - event(ctx, b.Backend, "role-write", map[string]string{ + _ = event.Event(ctx, b.Backend, operationPrefixGitlabAccessTokens, "role-write", map[string]string{ "path": "roles", "role_name": roleName, "config_name": role.ConfigName, diff --git a/path_role_deploy_tokens_test.go b/path_role_deploy_tokens_test.go index 198ddcc..e7bd85c 100644 --- a/path_role_deploy_tokens_test.go +++ b/path_role_deploy_tokens_test.go @@ -15,32 +15,35 @@ import ( "github.com/stretchr/testify/require" gitlab "github.com/ilijamt/vault-plugin-secrets-gitlab" + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/errs" + gitlab2 "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/gitlab" + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/token" ) func TestPathRolesDeployTokens(t *testing.T) { var defaultConfig = map[string]any{ "token": getGitlabToken("admin_user_root").Token, "base_url": cmp.Or(os.Getenv("GITLAB_URL"), "http://localhost:8080/"), - "type": gitlab.TypeSelfManaged.String(), + "type": gitlab2.TypeSelfManaged.String(), } var tests = []struct { - tokenType gitlab.TokenType - accessLevel gitlab.AccessLevel + tokenType token.Type + accessLevel token.AccessLevel scopes []string ttl string path string name string }{ { - tokenType: gitlab.TokenTypeProjectDeploy, + tokenType: token.TypeProjectDeploy, path: "example/example", - scopes: []string{gitlab.TokenScopeReadRepository.String()}, + scopes: []string{token.ScopeReadRepository.String()}, }, { - tokenType: gitlab.TokenTypeGroupDeploy, + tokenType: token.TypeGroupDeploy, path: "test/test1", - scopes: []string{gitlab.TokenScopeReadRepository.String()}, + scopes: []string{token.ScopeReadRepository.String()}, }, } @@ -56,7 +59,7 @@ func TestPathRolesDeployTokens(t *testing.T) { Data: map[string]any{ "path": tt.path, "name": tt.name, - "access_level": cmp.Or(tt.accessLevel, gitlab.AccessLevelUnknown).String(), + "access_level": cmp.Or(tt.accessLevel, token.AccessLevelUnknown).String(), "token_type": tt.tokenType.String(), "scopes": tt.scopes, "ttl": cmp.Or(tt.ttl, "1h"), @@ -76,7 +79,7 @@ func TestPathRolesDeployTokens(t *testing.T) { Data: map[string]any{ "path": tt.path, "name": tt.name, - "access_level": gitlab.AccessLevelNoPermissions.String(), + "access_level": token.AccessLevelNoPermissions.String(), "token_type": tt.tokenType.String(), "ttl": cmp.Or(tt.ttl, "1h"), "scopes": []string{}, @@ -85,7 +88,7 @@ func TestPathRolesDeployTokens(t *testing.T) { require.Error(t, err) require.NotNil(t, resp) var errorMap = countErrByName(err.(*multierror.Error)) - assert.EqualValues(t, 2, errorMap[gitlab.ErrFieldInvalidValue.Error()]) + assert.EqualValues(t, 2, errorMap[errs.ErrFieldInvalidValue.Error()]) }) }) } diff --git a/path_role_pipeline_project_trigger_token_test.go b/path_role_pipeline_project_trigger_token_test.go index 6728c43..5b05ec4 100644 --- a/path_role_pipeline_project_trigger_token_test.go +++ b/path_role_pipeline_project_trigger_token_test.go @@ -15,13 +15,16 @@ import ( "github.com/stretchr/testify/require" gitlab "github.com/ilijamt/vault-plugin-secrets-gitlab" + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/errs" + gitlab2 "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/gitlab" + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/token" ) func TestPathRolesPipelineProjectTrigger(t *testing.T) { var defaultConfig = map[string]any{ "token": getGitlabToken("admin_user_root").Token, "base_url": cmp.Or(os.Getenv("GITLAB_URL"), "http://localhost:8080/"), - "type": gitlab.TypeSelfManaged.String(), + "type": gitlab2.TypeSelfManaged.String(), } t.Run("should fail if have defined scopes or access level", func(t *testing.T) { @@ -34,16 +37,16 @@ func TestPathRolesPipelineProjectTrigger(t *testing.T) { Data: map[string]any{ "path": "user", "name": "Example user personal token", - "access_level": gitlab.AccessLevelNoPermissions.String(), - "token_type": gitlab.TokenTypePipelineProjectTrigger.String(), - "scopes": []string{gitlab.TokenScopeApi.String()}, + "access_level": token.AccessLevelNoPermissions.String(), + "token_type": token.TypePipelineProjectTrigger.String(), + "scopes": []string{token.ScopeApi.String()}, "ttl": "1h", }, }) require.Error(t, err) require.NotNil(t, resp) var errorMap = countErrByName(err.(*multierror.Error)) - assert.EqualValues(t, 2, errorMap[gitlab.ErrFieldInvalidValue.Error()]) + assert.EqualValues(t, 2, errorMap[errs.ErrFieldInvalidValue.Error()]) }) t.Run("ttl is set", func(t *testing.T) { @@ -56,8 +59,8 @@ func TestPathRolesPipelineProjectTrigger(t *testing.T) { Data: map[string]any{ "path": "user", "name": "Example user personal token", - "access_level": gitlab.AccessLevelUnknown.String(), - "token_type": gitlab.TokenTypePipelineProjectTrigger.String(), + "access_level": token.AccessLevelUnknown.String(), + "token_type": token.TypePipelineProjectTrigger.String(), "scopes": []string{}, "ttl": "1h", }, @@ -77,8 +80,8 @@ func TestPathRolesPipelineProjectTrigger(t *testing.T) { Data: map[string]any{ "path": "user", "name": "Example user personal token", - "access_level": gitlab.AccessLevelUnknown.String(), - "token_type": gitlab.TokenTypePipelineProjectTrigger.String(), + "access_level": token.AccessLevelUnknown.String(), + "token_type": token.TypePipelineProjectTrigger.String(), "scopes": []string{}, }, }) diff --git a/path_role_test.go b/path_role_test.go index 4d084bd..269ea8d 100644 --- a/path_role_test.go +++ b/path_role_test.go @@ -15,6 +15,9 @@ import ( "github.com/stretchr/testify/require" gitlab "github.com/ilijamt/vault-plugin-secrets-gitlab" + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/errs" + gitlab2 "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/gitlab" + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/token" ) func TestPathRolesList(t *testing.T) { @@ -37,7 +40,7 @@ func TestPathRoles(t *testing.T) { var defaultConfig = map[string]any{ "token": getGitlabToken("admin_user_root").Token, "base_url": cmp.Or(os.Getenv("GITLAB_URL"), "http://localhost:8080/"), - "type": gitlab.TypeSelfManaged.String(), + "type": gitlab2.TypeSelfManaged.String(), } t.Run("delete non existing role", func(t *testing.T) { @@ -63,11 +66,11 @@ func TestPathRoles(t *testing.T) { require.NoError(t, err) require.NotNil(t, resp) require.Error(t, resp.Error()) - require.EqualValues(t, gitlab.ErrBackendNotConfigured, resp.Error()) + require.EqualValues(t, errs.ErrBackendNotConfigured, resp.Error()) }) t.Run("access level", func(t *testing.T) { - t.Run(gitlab.TokenTypePersonal.String(), func(t *testing.T) { + t.Run(token.TypePersonal.String(), func(t *testing.T) { t.Run("no access level defined", func(t *testing.T) { ctx := getCtxGitlabClient(t, "unit") var b, l, err = getBackendWithConfig(ctx, defaultConfig) @@ -77,10 +80,10 @@ func TestPathRoles(t *testing.T) { Path: fmt.Sprintf("%s/test", gitlab.PathRoleStorage), Storage: l, Data: map[string]any{ "path": "user", - "name": gitlab.TokenTypePersonal.String(), - "token_type": gitlab.TokenTypePersonal.String(), + "name": token.TypePersonal.String(), + "token_type": token.TypePersonal.String(), "ttl": gitlab.DefaultAccessTokenMinTTL, - "scopes": gitlab.ValidPersonalTokenScopes, + "scopes": token.ValidPersonalTokenScopes, "gitlab_revokes_token": false, }, }) @@ -98,11 +101,11 @@ func TestPathRoles(t *testing.T) { Path: fmt.Sprintf("%s/test", gitlab.PathRoleStorage), Storage: l, Data: map[string]any{ "path": "user", - "name": gitlab.TokenTypePersonal.String(), - "access_level": gitlab.AccessLevelOwnerPermissions.String(), - "token_type": gitlab.TokenTypePersonal.String(), + "name": token.TypePersonal.String(), + "access_level": token.AccessLevelOwnerPermissions.String(), + "token_type": token.TypePersonal.String(), "ttl": gitlab.DefaultAccessTokenMinTTL, - "scopes": gitlab.ValidPersonalTokenScopes, + "scopes": token.ValidPersonalTokenScopes, "gitlab_revokes_token": false, }, }) @@ -112,7 +115,7 @@ func TestPathRoles(t *testing.T) { }) }) - t.Run(gitlab.TokenTypeProject.String(), func(t *testing.T) { + t.Run(token.TypeProject.String(), func(t *testing.T) { t.Run("no access level defined", func(t *testing.T) { ctx := getCtxGitlabClient(t, "unit") var b, l, err = getBackendWithConfig(ctx, defaultConfig) @@ -122,10 +125,10 @@ func TestPathRoles(t *testing.T) { Path: fmt.Sprintf("%s/test", gitlab.PathRoleStorage), Storage: l, Data: map[string]any{ "path": "user", - "name": gitlab.TokenTypeProject.String(), - "token_type": gitlab.TokenTypeProject.String(), + "name": token.TypeProject.String(), + "token_type": token.TypeProject.String(), "ttl": gitlab.DefaultAccessTokenMinTTL, - "scopes": gitlab.ValidProjectTokenScopes, + "scopes": token.ValidProjectTokenScopes, "gitlab_revokes_token": false, }, }) @@ -142,11 +145,11 @@ func TestPathRoles(t *testing.T) { Path: fmt.Sprintf("%s/test", gitlab.PathRoleStorage), Storage: l, Data: map[string]any{ "path": "user", - "name": gitlab.TokenTypeProject.String(), - "access_level": gitlab.AccessLevelOwnerPermissions.String(), - "token_type": gitlab.TokenTypeProject.String(), + "name": token.TypeProject.String(), + "access_level": token.AccessLevelOwnerPermissions.String(), + "token_type": token.TypeProject.String(), "ttl": gitlab.DefaultAccessTokenMinTTL, - "scopes": gitlab.ValidProjectTokenScopes, + "scopes": token.ValidProjectTokenScopes, "gitlab_revokes_token": false, }, }) @@ -157,7 +160,7 @@ func TestPathRoles(t *testing.T) { }) }) - t.Run(gitlab.TokenTypeGroup.String(), func(t *testing.T) { + t.Run(token.TypeGroup.String(), func(t *testing.T) { t.Run("no access level defined", func(t *testing.T) { ctx := getCtxGitlabClient(t, "unit") var b, l, err = getBackendWithConfig(ctx, defaultConfig) @@ -167,10 +170,10 @@ func TestPathRoles(t *testing.T) { Path: fmt.Sprintf("%s/test", gitlab.PathRoleStorage), Storage: l, Data: map[string]any{ "path": "user", - "name": gitlab.TokenTypeGroup.String(), - "token_type": gitlab.TokenTypeGroup.String(), + "name": token.TypeGroup.String(), + "token_type": token.TypeGroup.String(), "ttl": gitlab.DefaultAccessTokenMinTTL, - "scopes": gitlab.ValidGroupTokenScopes, + "scopes": token.ValidGroupTokenScopes, "gitlab_revokes_token": false, }, }) @@ -187,11 +190,11 @@ func TestPathRoles(t *testing.T) { Path: fmt.Sprintf("%s/test", gitlab.PathRoleStorage), Storage: l, Data: map[string]any{ "path": "user", - "name": gitlab.TokenTypeGroup.String(), - "access_level": gitlab.AccessLevelOwnerPermissions.String(), - "token_type": gitlab.TokenTypeGroup.String(), + "name": token.TypeGroup.String(), + "access_level": token.AccessLevelOwnerPermissions.String(), + "token_type": token.TypeGroup.String(), "ttl": gitlab.DefaultAccessTokenMinTTL, - "scopes": gitlab.ValidGroupTokenScopes, + "scopes": token.ValidGroupTokenScopes, "gitlab_revokes_token": false, }, }) @@ -218,8 +221,8 @@ func TestPathRoles(t *testing.T) { require.NotNil(t, resp) require.Error(t, resp.Error()) var errorMap = countErrByName(err.(*multierror.Error)) - assert.EqualValues(t, 4, errorMap[gitlab.ErrFieldRequired.Error()]) - assert.EqualValues(t, 2, errorMap[gitlab.ErrFieldInvalidValue.Error()]) + assert.EqualValues(t, 4, errorMap[errs.ErrFieldRequired.Error()]) + assert.EqualValues(t, 2, errorMap[errs.ErrFieldInvalidValue.Error()]) }) t.Run("invalid name template", func(t *testing.T) { @@ -232,9 +235,9 @@ func TestPathRoles(t *testing.T) { Data: map[string]any{ "path": "user", "name": "{{ . } invalid template", - "token_type": gitlab.TokenTypePersonal.String(), + "token_type": token.TypePersonal.String(), "ttl": gitlab.DefaultAccessTokenMinTTL, - "scopes": gitlab.ValidPersonalTokenScopes, + "scopes": token.ValidPersonalTokenScopes, "gitlab_revokes_token": false, }, }) @@ -255,10 +258,10 @@ func TestPathRoles(t *testing.T) { Data: map[string]any{ "path": "user", "name": "Example user personal token", - "access_level": gitlab.AccessLevelOwnerPermissions.String(), + "access_level": token.AccessLevelOwnerPermissions.String(), "ttl": "48h", - "token_type": gitlab.TokenTypeProject.String(), - "scopes": gitlab.ValidProjectTokenScopes, + "token_type": token.TypeProject.String(), + "scopes": token.ValidProjectTokenScopes, }, }) require.NoError(t, err) @@ -276,17 +279,17 @@ func TestPathRoles(t *testing.T) { Data: map[string]any{ "path": "user", "name": "Example project personal token", - "access_level": gitlab.AccessLevelOwnerPermissions.String(), - "token_type": gitlab.TokenTypeProject.String(), + "access_level": token.AccessLevelOwnerPermissions.String(), + "token_type": token.TypeProject.String(), "ttl": "48h", - "scopes": gitlab.ValidPersonalTokenScopes, + "scopes": token.ValidPersonalTokenScopes, "gitlab_revokes_token": false, }, }) require.Error(t, err) require.NotNil(t, resp) var errorMap = countErrByName(err.(*multierror.Error)) - assert.EqualValues(t, 1, errorMap[gitlab.ErrFieldInvalidValue.Error()]) + assert.EqualValues(t, 1, errorMap[errs.ErrFieldInvalidValue.Error()]) }) }) @@ -302,8 +305,8 @@ func TestPathRoles(t *testing.T) { "path": "user", "name": "Example user personal token", "ttl": "48h", - "token_type": gitlab.TokenTypePersonal.String(), - "scopes": gitlab.ValidPersonalTokenScopes, + "token_type": token.TypePersonal.String(), + "scopes": token.ValidPersonalTokenScopes, }, }) require.NoError(t, err) @@ -321,7 +324,7 @@ func TestPathRoles(t *testing.T) { Data: map[string]any{ "path": "user", "name": "Example user personal token", - "token_type": gitlab.TokenTypePersonal.String(), + "token_type": token.TypePersonal.String(), "scopes": []string{ "invalid_scope", }, @@ -330,7 +333,7 @@ func TestPathRoles(t *testing.T) { require.Error(t, err) require.NotNil(t, resp) var errorMap = countErrByName(err.(*multierror.Error)) - assert.EqualValues(t, 1, errorMap[gitlab.ErrFieldInvalidValue.Error()]) + assert.EqualValues(t, 1, errorMap[errs.ErrFieldInvalidValue.Error()]) }) }) @@ -346,9 +349,9 @@ func TestPathRoles(t *testing.T) { "path": "user", "name": "Example user personal token", "ttl": "48h", - "access_level": gitlab.AccessLevelOwnerPermissions.String(), - "token_type": gitlab.TokenTypeGroup.String(), - "scopes": gitlab.ValidProjectTokenScopes, + "access_level": token.AccessLevelOwnerPermissions.String(), + "token_type": token.TypeGroup.String(), + "scopes": token.ValidProjectTokenScopes, }, }) require.NoError(t, err) @@ -366,15 +369,15 @@ func TestPathRoles(t *testing.T) { Data: map[string]any{ "path": "user", "name": "Example user personal token", - "access_level": gitlab.AccessLevelOwnerPermissions.String(), - "token_type": gitlab.TokenTypeGroup.String(), - "scopes": gitlab.ValidPersonalTokenScopes, + "access_level": token.AccessLevelOwnerPermissions.String(), + "token_type": token.TypeGroup.String(), + "scopes": token.ValidPersonalTokenScopes, }, }) require.Error(t, err) require.NotNil(t, resp) var errorMap = countErrByName(err.(*multierror.Error)) - assert.EqualValues(t, 1, errorMap[gitlab.ErrFieldInvalidValue.Error()]) + assert.EqualValues(t, 1, errorMap[errs.ErrFieldInvalidValue.Error()]) }) }) @@ -400,7 +403,7 @@ func TestPathRoles(t *testing.T) { var defaultConfig = map[string]any{ "token": getGitlabToken("admin_user_root").Token, "base_url": cmp.Or(os.Getenv("GITLAB_URL"), "http://localhost:8080/"), - "type": gitlab.TypeSelfManaged.String(), + "type": gitlab2.TypeSelfManaged.String(), } // create a configuration with max ttl set to 10 days @@ -417,12 +420,12 @@ func TestPathRoles(t *testing.T) { var roleData = map[string]any{ "path": "user", "name": "Example user personal token", - "token_type": gitlab.TokenTypePersonal.String(), + "token_type": token.TypePersonal.String(), "ttl": int64((5 * 24 * time.Hour).Seconds()), "gitlab_revokes_token": false, "scopes": []string{ - gitlab.TokenScopeApi.String(), - gitlab.TokenScopeReadRegistry.String(), + token.ScopeApi.String(), + token.ScopeReadRegistry.String(), }, } diff --git a/path_role_ttl_test.go b/path_role_ttl_test.go index 313a242..82660a7 100644 --- a/path_role_ttl_test.go +++ b/path_role_ttl_test.go @@ -14,23 +14,26 @@ import ( "github.com/stretchr/testify/require" gitlab "github.com/ilijamt/vault-plugin-secrets-gitlab" + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/errs" + gitlab2 "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/gitlab" + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/token" ) func TestPathRolesTTL(t *testing.T) { var defaultConfig = map[string]any{ "token": getGitlabToken("admin_user_root").Token, "base_url": cmp.Or(os.Getenv("GITLAB_URL"), "http://localhost:8080/"), - "type": gitlab.TypeSelfManaged.String(), + "type": gitlab2.TypeSelfManaged.String(), } t.Run("general ttl limits", func(t *testing.T) { var generalRole = map[string]any{ "path": "user", "name": "Example user personal token", - "token_type": gitlab.TokenTypePersonal.String(), + "token_type": token.TypePersonal.String(), "scopes": []string{ - gitlab.TokenScopeApi.String(), - gitlab.TokenScopeReadRegistry.String(), + token.ScopeApi.String(), + token.ScopeReadRegistry.String(), }, "gitlab_revokes_token": false, } @@ -49,7 +52,7 @@ func TestPathRolesTTL(t *testing.T) { Data: role, }) require.Error(t, err) - require.ErrorIs(t, err, gitlab.ErrInvalidValue) + require.ErrorIs(t, err, errs.ErrInvalidValue) require.NotNil(t, resp) require.True(t, resp.IsError()) require.ErrorContains(t, resp.Error(), "ttl = 8761h0m0s [ttl <= max_ttl = 8760h0m0s]") @@ -88,10 +91,10 @@ func TestPathRolesTTL(t *testing.T) { var generalRole = map[string]any{ "path": "user", "name": "Example user personal token", - "token_type": gitlab.TokenTypePersonal.String(), + "token_type": token.TypePersonal.String(), "scopes": []string{ - gitlab.TokenScopeApi.String(), - gitlab.TokenScopeReadRegistry.String(), + token.ScopeApi.String(), + token.ScopeReadRegistry.String(), }, "gitlab_revokes_token": false, } @@ -139,7 +142,7 @@ func TestPathRolesTTL(t *testing.T) { Data: role, }) require.Error(t, err) - require.ErrorIs(t, err, gitlab.ErrInvalidValue) + require.ErrorIs(t, err, errs.ErrInvalidValue) require.NotNil(t, resp) require.True(t, resp.IsError()) require.ErrorContains(t, resp.Error(), "ttl = 59m59s [ttl >= 1h]") @@ -150,10 +153,10 @@ func TestPathRolesTTL(t *testing.T) { var generalRole = map[string]any{ "path": "user", "name": "Example user personal token", - "token_type": gitlab.TokenTypePersonal.String(), + "token_type": token.TypePersonal.String(), "scopes": []string{ - gitlab.TokenScopeApi.String(), - gitlab.TokenScopeReadRegistry.String(), + token.ScopeApi.String(), + token.ScopeReadRegistry.String(), }, "gitlab_revokes_token": true, } @@ -172,7 +175,7 @@ func TestPathRolesTTL(t *testing.T) { Data: role, }) require.Error(t, err) - require.ErrorIs(t, err, gitlab.ErrInvalidValue) + require.ErrorIs(t, err, errs.ErrInvalidValue) require.NotNil(t, resp) require.True(t, resp.IsError()) require.ErrorContains(t, resp.Error(), "ttl = 23h59m59s [24h0m0s <= ttl <= 8760h0m0s]") diff --git a/path_token_role.go b/path_token_role.go index 68b4384..0a357f7 100644 --- a/path_token_role.go +++ b/path_token_role.go @@ -11,6 +11,11 @@ import ( "github.com/hashicorp/vault/sdk/framework" "github.com/hashicorp/vault/sdk/helper/locksutil" "github.com/hashicorp/vault/sdk/logical" + + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/errs" + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/event" + token2 "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/token" + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/utils" ) const ( @@ -56,11 +61,11 @@ func (b *Backend) pathTokenRoleCreate(ctx context.Context, req *logical.Request, defer b.Logger().Debug("Created token for role", "role_name", roleName, "token_type", role.TokenType.String()) var name string - var token IToken + var token token2.Token var expiresAt time.Time - var startTime = TimeFromContext(ctx).UTC() + var startTime = utils.TimeFromContext(ctx).UTC() - name, err = TokenName(role) + name, err = utils.TokenName(role) if err != nil { return nil, fmt.Errorf("error generating token name: %w", err) } @@ -69,7 +74,7 @@ func (b *Backend) pathTokenRoleCreate(ctx context.Context, req *logical.Request, var gitlabRevokesTokens = role.GitlabRevokesTokens var vaultRevokesTokens = !role.GitlabRevokesTokens - _, expiresAt, _ = calculateGitlabTTL(role.TTL, startTime) + _, expiresAt, _ = utils.CalculateGitlabTTL(role.TTL, startTime) client, err = b.getClient(ctx, req.Storage, role.ConfigName) if err != nil { @@ -77,26 +82,26 @@ func (b *Backend) pathTokenRoleCreate(ctx context.Context, req *logical.Request, } switch role.TokenType { - case TokenTypeGroup: + case token2.TypeGroup: b.Logger().Debug("Creating group access token for role", "path", role.Path, "name", name, "expiresAt", expiresAt, "scopes", role.Scopes, "accessLevel", role.AccessLevel) token, err = client.CreateGroupAccessToken(ctx, role.Path, name, expiresAt, role.Scopes, role.AccessLevel) - case TokenTypeProject: + case token2.TypeProject: b.Logger().Debug("Creating project access token for role", "path", role.Path, "name", name, "expiresAt", expiresAt, "scopes", role.Scopes, "accessLevel", role.AccessLevel) token, err = client.CreateProjectAccessToken(ctx, role.Path, name, expiresAt, role.Scopes, role.AccessLevel) - case TokenTypePersonal: + case token2.TypePersonal: var userId int userId, err = client.GetUserIdByUsername(ctx, role.Path) if err == nil { b.Logger().Debug("Creating personal access token for role", "path", role.Path, "userId", userId, "name", name, "expiresAt", expiresAt, "scopes", role.Scopes) token, err = client.CreatePersonalAccessToken(ctx, role.Path, userId, name, expiresAt, role.Scopes) } - case TokenTypeUserServiceAccount: + case token2.TypeUserServiceAccount: var userId int if userId, err = client.GetUserIdByUsername(ctx, role.Path); err == nil { b.Logger().Debug("Creating user service account access token for role", "path", role.Path, "userId", userId, "name", name, "expiresAt", expiresAt, "scopes", role.Scopes) token, err = client.CreateUserServiceAccountAccessToken(ctx, role.Path, userId, name, expiresAt, role.Scopes) } - case TokenTypeGroupServiceAccount: + case token2.TypeGroupServiceAccount: var serviceAccount, groupId string { parts := strings.Split(role.Path, "/") @@ -108,27 +113,27 @@ func (b *Backend) pathTokenRoleCreate(ctx context.Context, req *logical.Request, b.Logger().Debug("Creating group service account access token for role", "path", role.Path, "groupId", groupId, "userId", userId, "name", name, "expiresAt", expiresAt, "scopes", role.Scopes) token, err = client.CreateGroupServiceAccountAccessToken(ctx, role.Path, groupId, userId, name, expiresAt, role.Scopes) } - case TokenTypeProjectDeploy: + case token2.TypeProjectDeploy: var projectId int if projectId, err = client.GetProjectIdByPath(ctx, role.Path); err == nil { token, err = client.CreateProjectDeployToken(ctx, role.Path, projectId, name, &expiresAt, role.Scopes) } - case TokenTypeGroupDeploy: + case token2.TypeGroupDeploy: var groupId int if groupId, err = client.GetGroupIdByPath(ctx, role.Path); err == nil { token, err = client.CreateGroupDeployToken(ctx, role.Path, groupId, name, &expiresAt, role.Scopes) } - case TokenTypePipelineProjectTrigger: + case token2.TypePipelineProjectTrigger: var projectId int if projectId, err = client.GetProjectIdByPath(ctx, role.Path); err == nil { token, err = client.CreatePipelineProjectTriggerAccessToken(ctx, role.Path, name, projectId, name, &expiresAt) } default: - return logical.ErrorResponse("invalid token type"), fmt.Errorf("%s: %w", role.TokenType.String(), ErrUnknownTokenType) + return logical.ErrorResponse("invalid token type"), fmt.Errorf("%s: %w", role.TokenType.String(), errs.ErrUnknownTokenType) } if err != nil || token == nil { - return nil, cmp.Or(err, fmt.Errorf("%w: token is nil", ErrNilValue)) + return nil, cmp.Or(err, fmt.Errorf("%w: token is nil", errs.ErrNilValue)) } token.SetConfigName(cmp.Or(role.ConfigName, DefaultConfigName)) @@ -151,8 +156,8 @@ func (b *Backend) pathTokenRoleCreate(ctx context.Context, req *logical.Request, resp.Secret.TTL = token.TTL() } - event( - ctx, b.Backend, "token-write", + _ = event.Event( + ctx, b.Backend, operationPrefixGitlabAccessTokens, "token-write", token.Event(map[string]string{"path": fmt.Sprintf("%s/%s", PathRoleStorage, roleName)}), ) return resp, nil diff --git a/path_token_role_multiple_config_test.go b/path_token_role_multiple_config_test.go index f09a794..9261847 100644 --- a/path_token_role_multiple_config_test.go +++ b/path_token_role_multiple_config_test.go @@ -11,11 +11,14 @@ import ( g "gitlab.com/gitlab-org/api/client-go" gitlab "github.com/ilijamt/vault-plugin-secrets-gitlab" + gitlab2 "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/gitlab" + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/token" + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/utils" ) func TestPathTokenRolesMultipleConfigs(t *testing.T) { httpClient, gitlabUrl := getClient(t, "unit") - ctx := gitlab.HttpClientNewContext(t.Context(), httpClient) + ctx := utils.HttpClientNewContext(t.Context(), httpClient) b, l, events, err := getBackendWithEvents(ctx) require.NoError(t, err) @@ -35,7 +38,7 @@ func TestPathTokenRolesMultipleConfigs(t *testing.T) { map[string]any{ "token": token, "base_url": gitlabUrl, - "type": gitlab.TypeSelfManaged.String(), + "type": gitlab2.TypeSelfManaged.String(), }, name, ), @@ -44,8 +47,8 @@ func TestPathTokenRolesMultipleConfigs(t *testing.T) { type roleData struct { roleName, path, tokenName string - tokenType gitlab.TokenType - accessLevel gitlab.AccessLevel + tokenType token.Type + accessLevel token.AccessLevel scopes []string } var roles = map[string][]roleData{ @@ -53,15 +56,15 @@ func TestPathTokenRolesMultipleConfigs(t *testing.T) { { roleName: "root-root", path: "root", - tokenType: gitlab.TokenTypePersonal, - scopes: []string{gitlab.TokenScopeApi.String(), gitlab.TokenScopeSelfRotate.String()}, + tokenType: token.TypePersonal, + scopes: []string{token.ScopeApi.String(), token.ScopeSelfRotate.String()}, tokenName: "admin_user_root", }, { roleName: "root-normal-user", path: "normal-user", - tokenType: gitlab.TokenTypePersonal, - scopes: []string{gitlab.TokenScopeApi.String(), gitlab.TokenScopeSelfRotate.String()}, + tokenType: token.TypePersonal, + scopes: []string{token.ScopeApi.String(), token.ScopeSelfRotate.String()}, tokenName: "admin_user_root", }, }, @@ -69,9 +72,9 @@ func TestPathTokenRolesMultipleConfigs(t *testing.T) { { roleName: "admin-example-example", path: "example/example", - tokenType: gitlab.TokenTypeProject, - accessLevel: gitlab.AccessLevelGuestPermissions, - scopes: []string{gitlab.TokenScopeApi.String(), gitlab.TokenScopeSelfRotate.String()}, + tokenType: token.TypeProject, + accessLevel: token.AccessLevelGuestPermissions, + scopes: []string{token.ScopeApi.String(), token.ScopeSelfRotate.String()}, tokenName: "admin_user_initial_token", }, }, @@ -79,9 +82,9 @@ func TestPathTokenRolesMultipleConfigs(t *testing.T) { { roleName: "normal-example", path: "example", - tokenType: gitlab.TokenTypeGroup, - accessLevel: gitlab.AccessLevelGuestPermissions, - scopes: []string{gitlab.TokenScopeApi.String(), gitlab.TokenScopeSelfRotate.String()}, + tokenType: token.TypeGroup, + accessLevel: token.AccessLevelGuestPermissions, + scopes: []string{token.ScopeApi.String(), token.ScopeSelfRotate.String()}, tokenName: "normal_user_initial_token", }, }, @@ -96,13 +99,13 @@ func TestPathTokenRolesMultipleConfigs(t *testing.T) { } switch rd.tokenType { - case gitlab.TokenTypePersonal: + case token.TypePersonal: data["access_level"] = rd.accessLevel.String() data["scopes"] = rd.scopes - case gitlab.TokenTypeGroup: + case token.TypeGroup: data["access_level"] = rd.accessLevel.String() data["scopes"] = rd.scopes - case gitlab.TokenTypeProject: + case token.TypeProject: data["access_level"] = rd.accessLevel.String() data["scopes"] = rd.scopes } diff --git a/path_token_role_test.go b/path_token_role_test.go index 7b9769e..b605edc 100644 --- a/path_token_role_test.go +++ b/path_token_role_test.go @@ -13,13 +13,15 @@ import ( "github.com/stretchr/testify/require" gitlab "github.com/ilijamt/vault-plugin-secrets-gitlab" + gitlab2 "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/gitlab" + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/token" ) func TestPathTokenRoles(t *testing.T) { var defaultConfig = map[string]any{ "token": getGitlabToken("admin_user_root").Token, "base_url": cmp.Or(os.Getenv("GITLAB_URL"), "http://localhost:8080/"), - "type": gitlab.TypeSelfManaged.String(), + "type": gitlab2.TypeSelfManaged.String(), } t.Run("role not found", func(t *testing.T) { @@ -35,11 +37,11 @@ func TestPathTokenRoles(t *testing.T) { require.ErrorIs(t, err, gitlab.ErrRoleNotFound) }) - var generalTokenCreation = func(t *testing.T, tokenType gitlab.TokenType, level gitlab.AccessLevel, gitlabRevokesToken bool) { + var generalTokenCreation = func(t *testing.T, tokenType token.Type, level token.AccessLevel, gitlabRevokesToken bool) { t.Logf("token creation, token type: %s, level: %s, gitlab revokes token: %t", tokenType, level, gitlabRevokesToken) ctx := getCtxGitlabClient(t, "unit") client := newInMemoryClient(true) - ctx = gitlab.ClientNewContext(ctx, client) + ctx = gitlab2.ClientNewContext(ctx, client) var b, l, events, err = getBackendWithEvents(ctx) require.NoError(t, err) require.NoError(t, writeBackendConfig(ctx, b, l, defaultConfig)) @@ -56,11 +58,11 @@ func TestPathTokenRoles(t *testing.T) { var path string switch tokenType { - case gitlab.TokenTypeProject: + case token.TypeProject: path = "example/example" - case gitlab.TokenTypePersonal: + case token.TypePersonal: path = "admin-user" - case gitlab.TokenTypeGroup: + case token.TypeGroup: path = "example" } @@ -123,11 +125,11 @@ func TestPathTokenRoles(t *testing.T) { if !gitlabRevokesToken { // calling revoke again would return a token not found in internal error switch tokenType { - case gitlab.TokenTypeProject: + case token.TypeProject: client.projectAccessTokenRevokeError = true - case gitlab.TokenTypePersonal: + case token.TypePersonal: client.personalAccessTokenRevokeError = true - case gitlab.TokenTypeGroup: + case token.TypeGroup: client.groupAccessTokenRevokeError = true } resp, err = b.HandleRequest(ctx, &logical.Request{ @@ -150,17 +152,17 @@ func TestPathTokenRoles(t *testing.T) { } t.Run("personal access token", func(t *testing.T) { - generalTokenCreation(t, gitlab.TokenTypePersonal, gitlab.AccessLevelUnknown, false) - generalTokenCreation(t, gitlab.TokenTypePersonal, gitlab.AccessLevelUnknown, true) + generalTokenCreation(t, token.TypePersonal, token.AccessLevelUnknown, false) + generalTokenCreation(t, token.TypePersonal, token.AccessLevelUnknown, true) }) t.Run("project access token", func(t *testing.T) { - generalTokenCreation(t, gitlab.TokenTypeProject, gitlab.AccessLevelGuestPermissions, false) - generalTokenCreation(t, gitlab.TokenTypeProject, gitlab.AccessLevelGuestPermissions, true) + generalTokenCreation(t, token.TypeProject, token.AccessLevelGuestPermissions, false) + generalTokenCreation(t, token.TypeProject, token.AccessLevelGuestPermissions, true) }) t.Run("group access token", func(t *testing.T) { - generalTokenCreation(t, gitlab.TokenTypeGroup, gitlab.AccessLevelGuestPermissions, false) - generalTokenCreation(t, gitlab.TokenTypeGroup, gitlab.AccessLevelGuestPermissions, true) + generalTokenCreation(t, token.TypeGroup, token.AccessLevelGuestPermissions, false) + generalTokenCreation(t, token.TypeGroup, token.AccessLevelGuestPermissions, true) }) } diff --git a/secret_access_tokens.go b/secret_access_tokens.go index 2b50a94..1fe7aff 100644 --- a/secret_access_tokens.go +++ b/secret_access_tokens.go @@ -8,6 +8,11 @@ import ( "github.com/hashicorp/vault/sdk/framework" "github.com/hashicorp/vault/sdk/logical" + + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/errs" + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/event" + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/token" + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/utils" ) const ( @@ -55,12 +60,12 @@ func (b *Backend) secretAccessTokenRevoke(ctx context.Context, req *logical.Requ var err error if req.Storage == nil { - return nil, fmt.Errorf("storage: %w", ErrNilValue) + return nil, fmt.Errorf("storage: %w", errs.ErrNilValue) } var secret = req.Secret if secret == nil { - return nil, fmt.Errorf("secret: %w", ErrNilValue) + return nil, fmt.Errorf("secret: %w", errs.ErrNilValue) } var configName = DefaultConfigName @@ -69,7 +74,7 @@ func (b *Backend) secretAccessTokenRevoke(ctx context.Context, req *logical.Requ } var tokenId int - tokenId, err = convertToInt(req.Secret.InternalData["token_id"]) + tokenId, err = utils.ConvertToInt(req.Secret.InternalData["token_id"]) if err != nil { return nil, fmt.Errorf("token_id: %w", err) } @@ -77,9 +82,9 @@ func (b *Backend) secretAccessTokenRevoke(ctx context.Context, req *logical.Requ var gitlabRevokesToken = req.Secret.InternalData["gitlab_revokes_token"].(bool) var vaultRevokesToken = !gitlabRevokesToken var parentId = req.Secret.InternalData["parent_id"].(string) - var tokenType TokenType + var tokenType token.Type var tokenTypeValue = req.Secret.InternalData["token_type"].(string) - tokenType, _ = TokenTypeParse(tokenTypeValue) + tokenType, _ = token.ParseType(tokenTypeValue) if vaultRevokesToken { var client Client @@ -89,29 +94,29 @@ func (b *Backend) secretAccessTokenRevoke(ctx context.Context, req *logical.Requ } switch tokenType { - case TokenTypePersonal: + case token.TypePersonal: err = client.RevokePersonalAccessToken(ctx, tokenId) - case TokenTypeProject: + case token.TypeProject: err = client.RevokeProjectAccessToken(ctx, tokenId, parentId) - case TokenTypeGroup: + case token.TypeGroup: err = client.RevokeGroupAccessToken(ctx, tokenId, parentId) - case TokenTypeUserServiceAccount: + case token.TypeUserServiceAccount: var token = req.Secret.InternalData["token"].(string) err = client.RevokeUserServiceAccountAccessToken(ctx, token) - case TokenTypeGroupServiceAccount: + case token.TypeGroupServiceAccount: var token = req.Secret.InternalData["token"].(string) err = client.RevokeGroupServiceAccountAccessToken(ctx, token) - case TokenTypePipelineProjectTrigger: + case token.TypePipelineProjectTrigger: var projectId int if projectId, err = strconv.Atoi(parentId); err == nil { err = client.RevokePipelineProjectTriggerAccessToken(ctx, projectId, tokenId) } - case TokenTypeGroupDeploy: + case token.TypeGroupDeploy: var groupId int if groupId, err = strconv.Atoi(parentId); err == nil { err = client.RevokeGroupDeployToken(ctx, groupId, tokenId) } - case TokenTypeProjectDeploy: + case token.TypeProjectDeploy: var projectId int if projectId, err = strconv.Atoi(parentId); err == nil { err = client.RevokeProjectDeployToken(ctx, projectId, tokenId) @@ -123,7 +128,7 @@ func (b *Backend) secretAccessTokenRevoke(ctx context.Context, req *logical.Requ } } - event(ctx, b.Backend, "token-revoke", map[string]string{ + _ = event.Event(ctx, b.Backend, operationPrefixGitlabAccessTokens, "token-revoke", map[string]string{ "lease_id": secret.LeaseID, "path": req.Secret.InternalData["path"].(string), "name": req.Secret.InternalData["name"].(string), diff --git a/secret_access_tokens_test.go b/secret_access_tokens_test.go index d144d06..4b941fb 100644 --- a/secret_access_tokens_test.go +++ b/secret_access_tokens_test.go @@ -10,11 +10,14 @@ import ( "github.com/stretchr/testify/require" gitlab "github.com/ilijamt/vault-plugin-secrets-gitlab" + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/errs" + gitlab2 "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/gitlab" + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/utils" ) func TestSecretAccessTokenRevokeToken(t *testing.T) { httpClient, url := getClient(t, "unit") - ctx := gitlab.HttpClientNewContext(t.Context(), httpClient) + ctx := utils.HttpClientNewContext(t.Context(), httpClient) b, l, events, err := getBackendWithEvents(ctx) require.NoError(t, err) @@ -24,7 +27,7 @@ func TestSecretAccessTokenRevokeToken(t *testing.T) { resp, err := b.Secret(gitlab.SecretAccessTokenType).HandleRevoke(ctx, &logical.Request{}) require.Error(t, err) require.Nil(t, resp) - require.ErrorIs(t, err, gitlab.ErrNilValue) + require.ErrorIs(t, err, errs.ErrNilValue) events.expectEvents(t, []expectedEvent{}) }) @@ -38,7 +41,7 @@ func TestSecretAccessTokenRevokeToken(t *testing.T) { "base_url": url, "auto_rotate_token": true, "auto_rotate_before": "24h", - "type": gitlab.TypeSelfManaged.String(), + "type": gitlab2.TypeSelfManaged.String(), }, }) @@ -50,7 +53,7 @@ func TestSecretAccessTokenRevokeToken(t *testing.T) { resp, err = b.Secret(gitlab.SecretAccessTokenType).HandleRevoke(ctx, &logical.Request{Storage: l}) require.Error(t, err) require.Nil(t, resp) - require.ErrorIs(t, err, gitlab.ErrNilValue) + require.ErrorIs(t, err, errs.ErrNilValue) events.expectEvents(t, []expectedEvent{ {eventType: "gitlab/config-write"}, @@ -68,7 +71,7 @@ func TestSecretAccessTokenRevokeToken(t *testing.T) { "base_url": url, "auto_rotate_token": true, "auto_rotate_before": "24h", - "type": gitlab.TypeSelfManaged.String(), + "type": gitlab2.TypeSelfManaged.String(), }, }) @@ -87,7 +90,7 @@ func TestSecretAccessTokenRevokeToken(t *testing.T) { }) require.Error(t, err) require.Nil(t, resp) - require.ErrorIs(t, err, gitlab.ErrInvalidValue) + require.ErrorIs(t, err, errs.ErrInvalidValue) events.expectEvents(t, []expectedEvent{ {eventType: "gitlab/config-write"}, diff --git a/token_config.go b/token_config.go deleted file mode 100644 index 16f791a..0000000 --- a/token_config.go +++ /dev/null @@ -1,7 +0,0 @@ -package gitlab - -type TokenConfig struct { - TokenWithScopes `json:",inline"` - - UserID int `json:"user_id"` -} diff --git a/token_group.go b/token_group.go deleted file mode 100644 index 86c10af..0000000 --- a/token_group.go +++ /dev/null @@ -1,5 +0,0 @@ -package gitlab - -type TokenGroup struct { - TokenWithScopesAndAccessLevel `json:",inline"` -} diff --git a/token_pipeline_project_trigger.go b/token_pipeline_project_trigger.go deleted file mode 100644 index 81131c8..0000000 --- a/token_pipeline_project_trigger.go +++ /dev/null @@ -1,5 +0,0 @@ -package gitlab - -type TokenPipelineProjectTrigger struct { - Token `json:",inline"` -} diff --git a/token_project.go b/token_project.go deleted file mode 100644 index 99e28a4..0000000 --- a/token_project.go +++ /dev/null @@ -1,5 +0,0 @@ -package gitlab - -type TokenProject struct { - TokenWithScopesAndAccessLevel `json:",inline"` -} diff --git a/token_user_service_account.go b/token_user_service_account.go deleted file mode 100644 index f3db5f0..0000000 --- a/token_user_service_account.go +++ /dev/null @@ -1,5 +0,0 @@ -package gitlab - -type TokenUserServiceAccount struct { - TokenWithScopes `json:",inline"` -} diff --git a/token_with_scopes.go b/token_with_scopes.go deleted file mode 100644 index d3a7ced..0000000 --- a/token_with_scopes.go +++ /dev/null @@ -1,32 +0,0 @@ -package gitlab - -import ( - "maps" - "strings" -) - -type TokenWithScopes struct { - Token `json:",inline"` - - Scopes []string `json:"scopes"` -} - -func (t *TokenWithScopes) Internal() (d map[string]any) { - d = map[string]any{"scopes": t.Scopes} - maps.Copy(d, t.Token.Internal()) - return d -} - -func (t *TokenWithScopes) Data() (d map[string]any) { - d = map[string]any{"scopes": t.Scopes} - maps.Copy(d, t.Token.Data()) - return d -} - -func (t *TokenWithScopes) Event(m map[string]string) (d map[string]string) { - d = map[string]string{"scopes": strings.Join(t.Scopes, ",")} - maps.Copy(d, t.Token.Event(m)) - return d -} - -var _ IToken = (*TokenWithScopes)(nil) diff --git a/token_with_scopes_and_access_level.go b/token_with_scopes_and_access_level.go deleted file mode 100644 index 6aacc72..0000000 --- a/token_with_scopes_and_access_level.go +++ /dev/null @@ -1,42 +0,0 @@ -package gitlab - -import ( - "maps" - "strings" -) - -type TokenWithScopesAndAccessLevel struct { - Token `json:",inline"` - - Scopes []string `json:"scopes"` - AccessLevel AccessLevel `json:"access_level"` -} - -func (t *TokenWithScopesAndAccessLevel) Internal() (d map[string]any) { - d = map[string]any{ - "scopes": t.Scopes, - "access_level": t.AccessLevel.String(), - } - maps.Copy(d, t.Token.Internal()) - return d -} - -func (t *TokenWithScopesAndAccessLevel) Data() (d map[string]any) { - d = map[string]any{ - "scopes": t.Scopes, - "access_level": t.AccessLevel.String(), - } - maps.Copy(d, t.Token.Data()) - return d -} - -func (t *TokenWithScopesAndAccessLevel) Event(m map[string]string) (d map[string]string) { - d = map[string]string{ - "scopes": strings.Join(t.Scopes, ","), - "access_level": t.AccessLevel.String(), - } - maps.Copy(d, t.Token.Event(m)) - return d -} - -var _ IToken = (*TokenWithScopesAndAccessLevel)(nil) diff --git a/type_token_scope.go b/type_token_scope.go deleted file mode 100644 index 7687777..0000000 --- a/type_token_scope.go +++ /dev/null @@ -1,160 +0,0 @@ -package gitlab - -import ( - "errors" - "fmt" - "slices" -) - -type TokenScope string - -const ( - // TokenScopeApi grants complete read/write access to the API, including all groups and projects, the container registry, the dependency proxy, and the package registry. Also grants complete read/write access to the registry and repository using Git over HTTP - TokenScopeApi = TokenScope("api") - // TokenScopeReadApi grants read access to the scoped group and related project API, including the Package Registry - TokenScopeReadApi = TokenScope("read_api") - // TokenScopeReadRegistry grants read access (pull) to the Container Registry images if any project within expected group is private and authorization is required. - TokenScopeReadRegistry = TokenScope("read_registry") - // TokenScopeWriteRegistry grants write access (push) to the Container Registry. - TokenScopeWriteRegistry = TokenScope("write_registry") - // TokenScopeReadRepository grants read access (pull) to the Container Registry images if any project within expected group is private and authorization is required - TokenScopeReadRepository = TokenScope("read_repository") - // TokenScopeWriteRepository grants read and write access (pull and push) to all repositories within expected group - TokenScopeWriteRepository = TokenScope("write_repository") - - // TokenScopeReadPackageRegistry Allows read-only access to the package registry. - TokenScopeReadPackageRegistry = TokenScope("read_package_registry") - // TokenScopeWritePackageRegistry Allows read and write access to the package registry. - TokenScopeWritePackageRegistry = TokenScope("write_package_registry") - - // TokenScopeCreateRunner grants permission to create runners in expected group - TokenScopeCreateRunner = TokenScope("create_runner") - // TokenScopeManageRunner grants permission to manage runners in expected group - TokenScopeManageRunner = TokenScope("manage_runner") - - // TokenScopeReadUser grants read-only access to the authenticated user’s profile through the /user API endpoint, which includes username, public email, and full name. Also grants access to read-only API endpoints under /users. - TokenScopeReadUser = TokenScope("read_user") - // TokenScopeSudo grants permission to perform API actions as any user in the system, when authenticated as an administrator. - TokenScopeSudo = TokenScope("sudo") - // TokenScopeAdminMode grants permission to perform API actions as an administrator, when Admin Mode is enabled. - TokenScopeAdminMode = TokenScope("admin_mode") - - // TokenScopeAiFeatures grants permission to perform API actions for GitLab Duo. This scope is designed to work with the GitLab Duo Plugin for JetBrains. For all other extensions, see scope requirements. - TokenScopeAiFeatures = TokenScope("ai_features") - // TokenScopeK8SProxy grants permission to perform Kubernetes API calls using the agent for Kubernetes. - TokenScopeK8SProxy = TokenScope("k8s_proxy") - // TokenScopeReadServicePing grant access to download Service Ping payload through the API when authenticated as an admin use. - TokenScopeReadServicePing = TokenScope("read_service_ping") - - // TokenScopeSelfRotate grants permission to rotate this token using the personal access token API. Does not allow rotation of other tokens. - TokenScopeSelfRotate = TokenScope("self_rotate") - // TokenScopeReadVirtualRegistry if a project is private and authorization is required, grants read-only (pull) access to container images through the dependency proxy. Available only when the dependency proxy is enabled. - TokenScopeReadVirtualRegistry = TokenScope("read_virtual_registry") - // TokenScopeWriteVirtualRegistry if a project is private and authorization is required, grants read (pull), write (push), and delete access to container images through the dependency proxy. Available only when the dependency proxy is enabled. - TokenScopeWriteVirtualRegistry = TokenScope("write_virtual_registry") - - TokenScopeUnknown = TokenScope("") -) - -var ( - ErrUnknownTokenScope = errors.New("unknown token scope") - - // ValidPersonalTokenScopes defines the actions you can perform when you authenticate with a project access token. - ValidPersonalTokenScopes = []string{ - TokenScopeApi.String(), - TokenScopeReadUser.String(), - TokenScopeReadApi.String(), - TokenScopeReadRepository.String(), - TokenScopeWriteRepository.String(), - TokenScopeReadRegistry.String(), - TokenScopeWriteRegistry.String(), - TokenScopeReadVirtualRegistry.String(), - TokenScopeWriteVirtualRegistry.String(), - TokenScopeSudo.String(), - TokenScopeAdminMode.String(), - TokenScopeCreateRunner.String(), - TokenScopeManageRunner.String(), - TokenScopeAiFeatures.String(), - TokenScopeK8SProxy.String(), - TokenScopeSelfRotate.String(), - TokenScopeReadServicePing.String(), - } - - ValidProjectTokenScopes = []string{ - TokenScopeApi.String(), - TokenScopeReadApi.String(), - TokenScopeReadRegistry.String(), - TokenScopeWriteRegistry.String(), - TokenScopeReadRepository.String(), - TokenScopeWriteRepository.String(), - TokenScopeCreateRunner.String(), - TokenScopeManageRunner.String(), - TokenScopeAiFeatures.String(), - TokenScopeK8SProxy.String(), - TokenScopeSelfRotate.String(), - } - - ValidGroupTokenScopes = []string{ - TokenScopeApi.String(), - TokenScopeReadApi.String(), - TokenScopeReadRegistry.String(), - TokenScopeWriteRegistry.String(), - TokenScopeReadVirtualRegistry.String(), - TokenScopeWriteVirtualRegistry.String(), - TokenScopeReadRepository.String(), - TokenScopeWriteRepository.String(), - TokenScopeCreateRunner.String(), - TokenScopeManageRunner.String(), - TokenScopeAiFeatures.String(), - TokenScopeK8SProxy.String(), - TokenScopeSelfRotate.String(), - } - - ValidUserServiceAccountTokenScopes = ValidPersonalTokenScopes - - ValidGroupServiceAccountTokenScopes = ValidGroupTokenScopes - - ValidPipelineProjectTokenScopes []string - - ValidProjectDeployTokenScopes = []string{ - TokenScopeReadRepository.String(), - TokenScopeReadRegistry.String(), - TokenScopeWriteRegistry.String(), - TokenScopeReadVirtualRegistry.String(), - TokenScopeWriteVirtualRegistry.String(), - TokenScopeReadPackageRegistry.String(), - TokenScopeWritePackageRegistry.String(), - } - - ValidGroupDeployTokenScopes = []string{ - TokenScopeReadRepository.String(), - TokenScopeReadRegistry.String(), - TokenScopeWriteRegistry.String(), - TokenScopeReadVirtualRegistry.String(), - TokenScopeWriteVirtualRegistry.String(), - TokenScopeReadPackageRegistry.String(), - TokenScopeWritePackageRegistry.String(), - } -) - -func (i TokenScope) String() string { - return string(i) -} - -func (i TokenScope) Value() string { - return i.String() -} - -func TokenScopeParse(value string) (TokenScope, error) { - if slices.Contains(ValidGroupTokenScopes, value) || - slices.Contains(ValidPipelineProjectTokenScopes, value) || - slices.Contains(ValidGroupDeployTokenScopes, value) || - slices.Contains(ValidProjectDeployTokenScopes, value) || - slices.Contains(ValidPersonalTokenScopes, value) || - slices.Contains(ValidProjectTokenScopes, value) || - slices.Contains(ValidUserServiceAccountTokenScopes, value) || - slices.Contains(ValidGroupServiceAccountTokenScopes, value) { - return TokenScope(value), nil - } - return TokenScopeUnknown, fmt.Errorf("failed to parse '%s': %w", value, ErrUnknownTokenScope) -} diff --git a/type_token_scope_test.go b/type_token_scope_test.go deleted file mode 100644 index fa0614c..0000000 --- a/type_token_scope_test.go +++ /dev/null @@ -1,90 +0,0 @@ -//go:build unit - -package gitlab_test - -import ( - "testing" - - "github.com/stretchr/testify/assert" - - gitlab "github.com/ilijamt/vault-plugin-secrets-gitlab" -) - -func TestTokenScope(t *testing.T) { - var tests = []struct { - expected gitlab.TokenScope - input string - err bool - }{ - { - expected: gitlab.TokenScopeApi, - input: gitlab.TokenScopeApi.String(), - }, - { - expected: gitlab.TokenScopeReadApi, - input: gitlab.TokenScopeReadApi.String(), - }, - { - expected: gitlab.TokenScopeReadRegistry, - input: gitlab.TokenScopeReadRegistry.String(), - }, - { - expected: gitlab.TokenScopeWriteRegistry, - input: gitlab.TokenScopeWriteRegistry.String(), - }, - { - expected: gitlab.TokenScopeReadRepository, - input: gitlab.TokenScopeReadRepository.String(), - }, - { - expected: gitlab.TokenScopeWriteRepository, - input: gitlab.TokenScopeWriteRepository.String(), - }, - { - expected: gitlab.TokenScopeCreateRunner, - input: gitlab.TokenScopeCreateRunner.String(), - }, - { - expected: gitlab.TokenScopeReadUser, - input: gitlab.TokenScopeReadUser.String(), - }, - { - expected: gitlab.TokenScopeSudo, - input: gitlab.TokenScopeSudo.String(), - }, - { - expected: gitlab.TokenScopeAdminMode, - input: gitlab.TokenScopeAdminMode.String(), - }, - { - expected: gitlab.TokenScopeReadPackageRegistry, - input: gitlab.TokenScopeReadPackageRegistry.String(), - }, - { - expected: gitlab.TokenScopeWritePackageRegistry, - input: gitlab.TokenScopeWritePackageRegistry.String(), - }, - { - expected: gitlab.TokenScopeUnknown, - input: "what", - err: true, - }, - { - expected: gitlab.TokenScopeUnknown, - input: "unknown", - err: true, - }, - } - - for _, test := range tests { - t.Logf("assert parse(%s) = %s (err: %v)", test.input, test.expected, test.err) - val, err := gitlab.TokenScopeParse(test.input) - assert.EqualValues(t, test.expected, val) - assert.EqualValues(t, test.expected.Value(), test.expected.String()) - if test.err { - assert.ErrorIs(t, err, gitlab.ErrUnknownTokenScope) - } else { - assert.NoError(t, err) - } - } -} diff --git a/type_token_type.go b/type_token_type.go deleted file mode 100644 index 315e029..0000000 --- a/type_token_type.go +++ /dev/null @@ -1,52 +0,0 @@ -package gitlab - -import ( - "errors" - "fmt" - "slices" -) - -type TokenType string - -const ( - TokenTypePersonal = TokenType("personal") - TokenTypeProject = TokenType("project") - TokenTypeGroup = TokenType("group") - TokenTypeUserServiceAccount = TokenType("user-service-account") - TokenTypeGroupServiceAccount = TokenType("group-service-account") - TokenTypePipelineProjectTrigger = TokenType("pipeline-project-trigger") - TokenTypeProjectDeploy = TokenType("project-deploy") - TokenTypeGroupDeploy = TokenType("group-deploy") - - TokenTypeUnknown = TokenType("") -) - -var ( - ErrUnknownTokenType = errors.New("unknown token type") - - validTokenTypes = []string{ - TokenTypePersonal.String(), - TokenTypeProject.String(), - TokenTypeGroup.String(), - TokenTypeUserServiceAccount.String(), - TokenTypeGroupServiceAccount.String(), - TokenTypePipelineProjectTrigger.String(), - TokenTypeProjectDeploy.String(), - TokenTypeGroupDeploy.String(), - } -) - -func (i TokenType) String() string { - return string(i) -} - -func (i TokenType) Value() string { - return i.String() -} - -func TokenTypeParse(value string) (TokenType, error) { - if slices.Contains(validTokenTypes, value) { - return TokenType(value), nil - } - return TokenTypeUnknown, fmt.Errorf("failed to parse '%s': %w", value, ErrUnknownTokenType) -} diff --git a/type_token_type_test.go b/type_token_type_test.go deleted file mode 100644 index 561a4e1..0000000 --- a/type_token_type_test.go +++ /dev/null @@ -1,74 +0,0 @@ -//go:build unit - -package gitlab_test - -import ( - "testing" - - "github.com/stretchr/testify/assert" - - gitlab "github.com/ilijamt/vault-plugin-secrets-gitlab" -) - -func TestTokenType(t *testing.T) { - var tests = []struct { - expected gitlab.TokenType - input string - err bool - }{ - { - expected: gitlab.TokenTypePersonal, - input: gitlab.TokenTypePersonal.String(), - }, - { - expected: gitlab.TokenTypeGroup, - input: gitlab.TokenTypeGroup.String(), - }, - { - expected: gitlab.TokenTypeProject, - input: gitlab.TokenTypeProject.String(), - }, - { - expected: gitlab.TokenTypeUserServiceAccount, - input: gitlab.TokenTypeUserServiceAccount.String(), - }, - { - expected: gitlab.TokenTypeGroupServiceAccount, - input: gitlab.TokenTypeGroupServiceAccount.String(), - }, - { - expected: gitlab.TokenTypePipelineProjectTrigger, - input: gitlab.TokenTypePipelineProjectTrigger.String(), - }, - { - expected: gitlab.TokenTypeProjectDeploy, - input: gitlab.TokenTypeProjectDeploy.String(), - }, - { - expected: gitlab.TokenTypeGroupDeploy, - input: gitlab.TokenTypeGroupDeploy.String(), - }, - { - expected: gitlab.TokenTypeUnknown, - input: "unknown", - err: true, - }, - { - expected: gitlab.TokenTypeUnknown, - input: "unknown", - err: true, - }, - } - - for _, test := range tests { - t.Logf("assert parse(%s) = %s (err: %v)", test.input, test.expected, test.err) - val, err := gitlab.TokenTypeParse(test.input) - assert.EqualValues(t, test.expected, val) - assert.EqualValues(t, test.expected.Value(), test.expected.String()) - if test.err { - assert.ErrorIs(t, err, gitlab.ErrUnknownTokenType) - } else { - assert.NoError(t, err) - } - } -} diff --git a/utils.go b/utils.go deleted file mode 100644 index 6303bf7..0000000 --- a/utils.go +++ /dev/null @@ -1,51 +0,0 @@ -package gitlab - -import ( - "fmt" - "time" -) - -func allowedValues(values ...string) (ret []any) { - for _, value := range values { - ret = append(ret, value) - } - return ret -} - -func convertToInt(num any) (int, error) { - switch val := num.(type) { - case int: - return val, nil - case int8: - return int(val), nil - case int16: - return int(val), nil - case int32: - return int(val), nil - case int64: - return int(val), nil - case float32: - return int(val), nil - case float64: - return int(val), nil - } - return 0, fmt.Errorf("%v: %w", num, ErrInvalidValue) -} - -func calculateGitlabTTL(duration time.Duration, start time.Time) (ttl time.Duration, exp time.Time, err error) { - start = start.UTC() - const D = 24 * time.Hour - const maxDuration = 365 * 24 * time.Hour - if duration > maxDuration { - duration = maxDuration - } - var val = start.Add(duration).Round(0) - exp = val.AddDate(0, 0, 1).Truncate(D) - ttl = exp.Sub(start.Round(0)) - if ttl > maxDuration { - m := start.Add(maxDuration) - exp = time.Date(m.Year(), m.Month(), m.Day(), 0, 0, 0, 0, m.Location()) - ttl = exp.Sub(start.Round(0)) - } - return ttl, exp, nil -} diff --git a/with_admin_user_pat_gitlab_revokes_token_test.go b/with_admin_user_pat_gitlab_revokes_token_test.go index eb19798..65ef125 100644 --- a/with_admin_user_pat_gitlab_revokes_token_test.go +++ b/with_admin_user_pat_gitlab_revokes_token_test.go @@ -15,11 +15,14 @@ import ( g "gitlab.com/gitlab-org/api/client-go" gitlab "github.com/ilijamt/vault-plugin-secrets-gitlab" + gitlab2 "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/gitlab" + token2 "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/token" + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/utils" ) func TestWithAdminUser_PAT_AdminUser_GitlabRevokesToken(t *testing.T) { httpClient, url := getClient(t, "local") - ctx := gitlab.HttpClientNewContext(t.Context(), httpClient) + ctx := utils.HttpClientNewContext(t.Context(), httpClient) b, l, events, err := getBackendWithEvents(ctx) require.NoError(t, err) @@ -33,7 +36,7 @@ func TestWithAdminUser_PAT_AdminUser_GitlabRevokesToken(t *testing.T) { "base_url": url, "auto_rotate_token": true, "auto_rotate_before": "24h", - "type": gitlab.TypeSelfManaged.String(), + "type": gitlab2.TypeSelfManaged.String(), }, }) @@ -52,13 +55,13 @@ func TestWithAdminUser_PAT_AdminUser_GitlabRevokesToken(t *testing.T) { Path: fmt.Sprintf("%s/normal-user", gitlab.PathRoleStorage), Storage: l, Data: map[string]any{ "path": "normal-user", - "name": gitlab.TokenTypePersonal.String(), - "token_type": gitlab.TokenTypePersonal.String(), + "name": token2.TypePersonal.String(), + "token_type": token2.TypePersonal.String(), "ttl": time.Hour * 120, "gitlab_revokes_token": strconv.FormatBool(true), "scopes": strings.Join( []string{ - gitlab.TokenScopeReadApi.String(), + token2.ScopeReadApi.String(), }, ","), }, diff --git a/with_admin_user_pat_vault_revokes_token_test.go b/with_admin_user_pat_vault_revokes_token_test.go index 187b617..a4c7caa 100644 --- a/with_admin_user_pat_vault_revokes_token_test.go +++ b/with_admin_user_pat_vault_revokes_token_test.go @@ -15,11 +15,14 @@ import ( g "gitlab.com/gitlab-org/api/client-go" gitlab "github.com/ilijamt/vault-plugin-secrets-gitlab" + gitlab2 "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/gitlab" + token2 "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/token" + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/utils" ) func TestWithAdminUser_PAT_AdminUser_VaultRevokesToken(t *testing.T) { httpClient, url := getClient(t, "local") - ctx := gitlab.HttpClientNewContext(t.Context(), httpClient) + ctx := utils.HttpClientNewContext(t.Context(), httpClient) var tokenName = "admin_user_initial_token" b, l, events, err := getBackendWithEvents(ctx) @@ -33,7 +36,7 @@ func TestWithAdminUser_PAT_AdminUser_VaultRevokesToken(t *testing.T) { "base_url": url, "auto_rotate_token": true, "auto_rotate_before": "24h", - "type": gitlab.TypeSelfManaged.String(), + "type": gitlab2.TypeSelfManaged.String(), }, }) @@ -53,11 +56,11 @@ func TestWithAdminUser_PAT_AdminUser_VaultRevokesToken(t *testing.T) { Path: fmt.Sprintf("%s/admin-user", gitlab.PathRoleStorage), Data: map[string]any{ "path": "admin-user", - "name": gitlab.TokenTypePersonal.String(), - "token_type": gitlab.TokenTypePersonal.String(), + "name": token2.TypePersonal.String(), + "token_type": token2.TypePersonal.String(), "scopes": strings.Join( []string{ - gitlab.TokenScopeReadApi.String(), + token2.ScopeReadApi.String(), }, ","), "ttl": time.Hour, diff --git a/with_gitlab_com_user_rotate_token_test.go b/with_gitlab_com_user_rotate_token_test.go index cc68d0f..ac29ae0 100644 --- a/with_gitlab_com_user_rotate_token_test.go +++ b/with_gitlab_com_user_rotate_token_test.go @@ -12,11 +12,13 @@ import ( g "gitlab.com/gitlab-org/api/client-go" gitlab "github.com/ilijamt/vault-plugin-secrets-gitlab" + gitlab2 "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/gitlab" + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/utils" ) func TestWithGitlabUser_RotateToken(t *testing.T) { httpClient, _ := getClient(t, "saas") - ctx := gitlab.HttpClientNewContext(t.Context(), httpClient) + ctx := utils.HttpClientNewContext(t.Context(), httpClient) var tokenName = "" b, l, events, err := getBackendWithEvents(ctx) @@ -31,7 +33,7 @@ func TestWithGitlabUser_RotateToken(t *testing.T) { "base_url": gitlabComUrl, "auto_rotate_token": true, "auto_rotate_before": "24h", - "type": gitlab.TypeSaaS.String(), + "type": gitlab2.TypeSaaS.String(), }, }) diff --git a/with_group_deploy_token_test.go b/with_group_deploy_token_test.go index ab5863d..b906be5 100644 --- a/with_group_deploy_token_test.go +++ b/with_group_deploy_token_test.go @@ -13,11 +13,14 @@ import ( g "gitlab.com/gitlab-org/api/client-go" gitlab "github.com/ilijamt/vault-plugin-secrets-gitlab" + gitlab2 "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/gitlab" + token2 "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/token" + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/utils" ) func TestWithGroupDeployToken(t *testing.T) { httpClient, url := getClient(t, "local") - ctx := gitlab.HttpClientNewContext(t.Context(), httpClient) + ctx := utils.HttpClientNewContext(t.Context(), httpClient) var tokenName = "normal_user_initial_token" b, l, events, err := getBackendWithEvents(ctx) @@ -31,7 +34,7 @@ func TestWithGroupDeployToken(t *testing.T) { "base_url": url, "auto_rotate_token": true, "auto_rotate_before": "24h", - "type": gitlab.TypeSelfManaged.String(), + "type": gitlab2.TypeSelfManaged.String(), }, }) @@ -50,11 +53,11 @@ func TestWithGroupDeployToken(t *testing.T) { Path: fmt.Sprintf("%s/role", gitlab.PathRoleStorage), Storage: l, Data: map[string]any{ "path": "example", - "name": gitlab.TokenTypeGroupDeploy.String(), - "token_type": gitlab.TokenTypeGroupDeploy.String(), + "name": token2.TypeGroupDeploy.String(), + "token_type": token2.TypeGroupDeploy.String(), "gitlab_revokes_token": strconv.FormatBool(false), "ttl": 120 * time.Hour, - "scopes": []string{gitlab.TokenScopeReadRepository.String()}, + "scopes": []string{token2.ScopeReadRepository.String()}, }, }) require.NoError(t, err) diff --git a/with_normal_user_gat_test.go b/with_normal_user_gat_test.go index acbcd83..c60d58c 100644 --- a/with_normal_user_gat_test.go +++ b/with_normal_user_gat_test.go @@ -15,11 +15,14 @@ import ( g "gitlab.com/gitlab-org/api/client-go" gitlab "github.com/ilijamt/vault-plugin-secrets-gitlab" + gitlab2 "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/gitlab" + token2 "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/token" + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/utils" ) func TestWithNormalUser_GAT(t *testing.T) { httpClient, url := getClient(t, "local") - ctx := gitlab.HttpClientNewContext(t.Context(), httpClient) + ctx := utils.HttpClientNewContext(t.Context(), httpClient) var tokenName = "normal_user_initial_token" b, l, events, err := getBackendWithEvents(ctx) @@ -33,7 +36,7 @@ func TestWithNormalUser_GAT(t *testing.T) { "base_url": url, "auto_rotate_token": true, "auto_rotate_before": "24h", - "type": gitlab.TypeSelfManaged.String(), + "type": gitlab2.TypeSelfManaged.String(), }, }) @@ -52,11 +55,11 @@ func TestWithNormalUser_GAT(t *testing.T) { Data: map[string]any{ "path": "example", "name": `gat-token`, - "token_type": gitlab.TokenTypeGroup.String(), + "token_type": token2.TypeGroup.String(), "ttl": time.Hour * 120, "gitlab_revokes_token": strconv.FormatBool(false), - "access_level": gitlab.AccessLevelMaintainerPermissions.String(), - "scopes": strings.Join([]string{gitlab.TokenScopeReadApi.String()}, ","), + "access_level": token2.AccessLevelMaintainerPermissions.String(), + "scopes": strings.Join([]string{token2.ScopeReadApi.String()}, ","), }, }) require.NoError(t, err) diff --git a/with_normal_user_personal_at_fails_test.go b/with_normal_user_personal_at_fails_test.go index 0ad1a3a..f79edea 100644 --- a/with_normal_user_personal_at_fails_test.go +++ b/with_normal_user_personal_at_fails_test.go @@ -13,11 +13,14 @@ import ( "github.com/stretchr/testify/require" gitlab "github.com/ilijamt/vault-plugin-secrets-gitlab" + gitlab2 "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/gitlab" + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/token" + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/utils" ) func TestWithNormalUser_PersonalAT_Fails(t *testing.T) { httpClient, url := getClient(t, "local") - ctx := gitlab.HttpClientNewContext(t.Context(), httpClient) + ctx := utils.HttpClientNewContext(t.Context(), httpClient) var tokenName = "normal_user_initial_token" b, l, events, err := getBackendWithEvents(ctx) @@ -31,7 +34,7 @@ func TestWithNormalUser_PersonalAT_Fails(t *testing.T) { "base_url": url, "auto_rotate_token": true, "auto_rotate_before": "24h", - "type": gitlab.TypeSelfManaged.String(), + "type": gitlab2.TypeSelfManaged.String(), }, }) @@ -46,13 +49,13 @@ func TestWithNormalUser_PersonalAT_Fails(t *testing.T) { Path: fmt.Sprintf("%s/normal-user", gitlab.PathRoleStorage), Storage: l, Data: map[string]any{ "path": "normal-user", - "name": gitlab.TokenTypePersonal.String(), - "token_type": gitlab.TokenTypePersonal.String(), + "name": token.TypePersonal.String(), + "token_type": token.TypePersonal.String(), "ttl": time.Hour * 120, "gitlab_revokes_token": strconv.FormatBool(true), "scopes": strings.Join( []string{ - gitlab.TokenScopeReadApi.String(), + token.ScopeReadApi.String(), }, ","), }, diff --git a/with_normal_user_project_at_test.go b/with_normal_user_project_at_test.go index 52fb5bd..336a1e2 100644 --- a/with_normal_user_project_at_test.go +++ b/with_normal_user_project_at_test.go @@ -15,11 +15,14 @@ import ( g "gitlab.com/gitlab-org/api/client-go" gitlab "github.com/ilijamt/vault-plugin-secrets-gitlab" + gitlab2 "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/gitlab" + tok "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/token" + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/utils" ) func TestWithNormalUser_ProjectAT(t *testing.T) { httpClient, url := getClient(t, "local") - ctx := gitlab.HttpClientNewContext(t.Context(), httpClient) + ctx := utils.HttpClientNewContext(t.Context(), httpClient) var tokenName = "normal_user_initial_token" b, l, events, err := getBackendWithEvents(ctx) @@ -33,7 +36,7 @@ func TestWithNormalUser_ProjectAT(t *testing.T) { "base_url": url, "auto_rotate_token": true, "auto_rotate_before": "24h", - "type": gitlab.TypeSelfManaged.String(), + "type": gitlab2.TypeSelfManaged.String(), }, }) @@ -52,14 +55,14 @@ func TestWithNormalUser_ProjectAT(t *testing.T) { Path: fmt.Sprintf("%s/pat", gitlab.PathRoleStorage), Storage: l, Data: map[string]any{ "path": "example/example", - "name": gitlab.TokenTypeProject.String(), - "token_type": gitlab.TokenTypeProject.String(), + "name": tok.TypeProject.String(), + "token_type": tok.TypeProject.String(), "ttl": time.Hour * 120, "gitlab_revokes_token": strconv.FormatBool(false), - "access_level": gitlab.AccessLevelMaintainerPermissions.String(), + "access_level": tok.AccessLevelMaintainerPermissions.String(), "scopes": strings.Join( []string{ - gitlab.TokenScopeReadApi.String(), + tok.ScopeReadApi.String(), }, ","), }, diff --git a/with_pipeline_project_trigger_token_test.go b/with_pipeline_project_trigger_token_test.go index 60bdf98..544197c 100644 --- a/with_pipeline_project_trigger_token_test.go +++ b/with_pipeline_project_trigger_token_test.go @@ -12,11 +12,14 @@ import ( g "gitlab.com/gitlab-org/api/client-go" gitlab "github.com/ilijamt/vault-plugin-secrets-gitlab" + gitlab2 "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/gitlab" + token2 "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/token" + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/utils" ) func TestWithPipelineProjectTriggerAccessToken(t *testing.T) { httpClient, url := getClient(t, "local") - ctx := gitlab.HttpClientNewContext(t.Context(), httpClient) + ctx := utils.HttpClientNewContext(t.Context(), httpClient) var tokenName = "normal_user_initial_token" b, l, events, err := getBackendWithEvents(ctx) @@ -30,7 +33,7 @@ func TestWithPipelineProjectTriggerAccessToken(t *testing.T) { "base_url": url, "auto_rotate_token": true, "auto_rotate_before": "24h", - "type": gitlab.TypeSelfManaged.String(), + "type": gitlab2.TypeSelfManaged.String(), }, }) @@ -49,8 +52,8 @@ func TestWithPipelineProjectTriggerAccessToken(t *testing.T) { Path: fmt.Sprintf("%s/pptat", gitlab.PathRoleStorage), Storage: l, Data: map[string]any{ "path": "example/example", - "name": gitlab.TokenTypePipelineProjectTrigger.String(), - "token_type": gitlab.TokenTypePipelineProjectTrigger.String(), + "name": token2.TypePipelineProjectTrigger.String(), + "token_type": token2.TypePipelineProjectTrigger.String(), "gitlab_revokes_token": strconv.FormatBool(false), }, }) diff --git a/with_project_deploy_token_test.go b/with_project_deploy_token_test.go index 357eddf..5ec5358 100644 --- a/with_project_deploy_token_test.go +++ b/with_project_deploy_token_test.go @@ -13,11 +13,14 @@ import ( g "gitlab.com/gitlab-org/api/client-go" gitlab "github.com/ilijamt/vault-plugin-secrets-gitlab" + gitlab2 "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/gitlab" + token2 "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/token" + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/utils" ) func TestWithProjectDeployToken(t *testing.T) { httpClient, url := getClient(t, "local") - ctx := gitlab.HttpClientNewContext(t.Context(), httpClient) + ctx := utils.HttpClientNewContext(t.Context(), httpClient) var tokenName = "normal_user_initial_token" b, l, events, err := getBackendWithEvents(ctx) @@ -31,7 +34,7 @@ func TestWithProjectDeployToken(t *testing.T) { "base_url": url, "auto_rotate_token": true, "auto_rotate_before": "24h", - "type": gitlab.TypeSelfManaged.String(), + "type": gitlab2.TypeSelfManaged.String(), }, }) @@ -50,11 +53,11 @@ func TestWithProjectDeployToken(t *testing.T) { Path: fmt.Sprintf("%s/role", gitlab.PathRoleStorage), Storage: l, Data: map[string]any{ "path": "example/example", - "name": gitlab.TokenTypeProjectDeploy.String(), - "token_type": gitlab.TokenTypeProjectDeploy.String(), + "name": token2.TypeProjectDeploy.String(), + "token_type": token2.TypeProjectDeploy.String(), "gitlab_revokes_token": strconv.FormatBool(false), "ttl": 120 * time.Hour, - "scopes": []string{gitlab.TokenScopeReadRepository.String()}, + "scopes": []string{token2.ScopeReadRepository.String()}, }, }) require.NoError(t, err) diff --git a/with_service_account_fail_test.go b/with_service_account_fail_test.go index a4a9fa5..60834f6 100644 --- a/with_service_account_fail_test.go +++ b/with_service_account_fail_test.go @@ -11,16 +11,19 @@ import ( g "gitlab.com/gitlab-org/api/client-go" gitlab "github.com/ilijamt/vault-plugin-secrets-gitlab" + gitlab2 "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/gitlab" + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/token" + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/utils" ) func TestWithServiceAccountUserFail(t *testing.T) { - for _, typ := range []gitlab.Type{ - gitlab.TypeSaaS, - gitlab.TypeDedicated, + for _, typ := range []gitlab2.Type{ + gitlab2.TypeSaaS, + gitlab2.TypeDedicated, } { t.Run(typ.String(), func(t *testing.T) { httpClient, _ := getClient(t, "selfhosted") - ctx := gitlab.HttpClientNewContext(t.Context(), httpClient) + ctx := utils.HttpClientNewContext(t.Context(), httpClient) b, l, events, err := getBackendWithEvents(ctx) require.NoError(t, err) @@ -56,9 +59,9 @@ func TestWithServiceAccountUserFail(t *testing.T) { Data: map[string]any{ "path": usr.Username, "name": fmt.Sprintf(`user-service-account-%s`, usr.Username), - "token_type": gitlab.TokenTypeUserServiceAccount.String(), + "token_type": token.TypeUserServiceAccount.String(), "ttl": gitlab.DefaultAccessTokenMinTTL, - "scopes": gitlab.ValidUserServiceAccountTokenScopes, + "scopes": token.ValidUserServiceAccountTokenScopes, "gitlab_revokes_token": false, }, }) diff --git a/with_service_account_group_test.go b/with_service_account_group_test.go index f4bdc2d..a2b1523 100644 --- a/with_service_account_group_test.go +++ b/with_service_account_group_test.go @@ -13,11 +13,14 @@ import ( g "gitlab.com/gitlab-org/api/client-go" gitlab "github.com/ilijamt/vault-plugin-secrets-gitlab" + gitlab2 "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/gitlab" + token2 "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/token" + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/utils" ) func TestWithServiceAccountGroup(t *testing.T) { httpClient, _ := getClient(t, "selfhosted") - ctx := gitlab.HttpClientNewContext(t.Context(), httpClient) + ctx := utils.HttpClientNewContext(t.Context(), httpClient) var tokenName = "" b, l, events, err := getBackendWithEvents(ctx) @@ -31,7 +34,7 @@ func TestWithServiceAccountGroup(t *testing.T) { "base_url": gitlabServiceAccountUrl, "auto_rotate_token": true, "auto_rotate_before": "24h", - "type": gitlab.TypeSelfManaged.String(), + "type": gitlab2.TypeSelfManaged.String(), }, }) @@ -61,9 +64,9 @@ func TestWithServiceAccountGroup(t *testing.T) { Data: map[string]any{ "path": fmt.Sprintf("%s/%s", gid, sa.UserName), "name": `vault-generated-{{ .token_type }}-token`, - "token_type": gitlab.TokenTypeGroupServiceAccount.String(), + "token_type": token2.TypeGroupServiceAccount.String(), "ttl": gitlab.DefaultAccessTokenMinTTL, - "scopes": gitlab.ValidGroupServiceAccountTokenScopes, + "scopes": token2.ValidGroupServiceAccountTokenScopes, "gitlab_revokes_token": false, }, }) diff --git a/with_service_account_user_test.go b/with_service_account_user_test.go index be515e0..90fbfb1 100644 --- a/with_service_account_user_test.go +++ b/with_service_account_user_test.go @@ -12,11 +12,14 @@ import ( g "gitlab.com/gitlab-org/api/client-go" gitlab "github.com/ilijamt/vault-plugin-secrets-gitlab" + gitlab2 "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/gitlab" + token2 "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/token" + "github.com/ilijamt/vault-plugin-secrets-gitlab/internal/utils" ) func TestWithServiceAccountUser(t *testing.T) { httpClient, _ := getClient(t, "selfhosted") - ctx := gitlab.HttpClientNewContext(t.Context(), httpClient) + ctx := utils.HttpClientNewContext(t.Context(), httpClient) var tokenName = "" b, l, events, err := getBackendWithEvents(ctx) @@ -30,7 +33,7 @@ func TestWithServiceAccountUser(t *testing.T) { "base_url": gitlabServiceAccountUrl, "auto_rotate_token": true, "auto_rotate_before": "24h", - "type": gitlab.TypeSelfManaged.String(), + "type": gitlab2.TypeSelfManaged.String(), }, }) @@ -59,9 +62,9 @@ func TestWithServiceAccountUser(t *testing.T) { Data: map[string]any{ "path": usr.Username, "name": `vault-generated-{{ .token_type }}-token`, - "token_type": gitlab.TokenTypeUserServiceAccount.String(), + "token_type": token2.TypeUserServiceAccount.String(), "ttl": gitlab.DefaultAccessTokenMinTTL, - "scopes": gitlab.ValidUserServiceAccountTokenScopes, + "scopes": token2.ValidUserServiceAccountTokenScopes, "gitlab_revokes_token": false, }, })