Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow you to patch every config property as needed #118

Merged
merged 4 commits into from
Oct 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,12 +174,31 @@ Key Value
auto_rotate_before 48h0m0s
auto_rotate_token false
base_url https://gitlab.example.com
token_id 107
token_id 1
token_expires_at 2025-03-29T00:00:00Z
token_sha1_hash 1014647cd9bbf359d926fcacdf78e184db9dbedc
token_sha1_hash 9441e6e07d77a2d5601ab5d7cac5868d358d885c
type self-managed
```

After initial setup should you wish to change any value you can do so by using the patch command for example

```shell
$ vault patch gitlab/config type=saas auto_rotate_token=true auto_rotate_before=64h token=glpat-secret-admin-token
Key Value
--- -----
auto_rotate_before 64h0m0s
auto_rotate_token true
base_url https://gitlab.example.com
scopes api, read_api, read_user, sudo, admin_mode, create_runner, k8s_proxy, read_repository, write_repository, ai_features, read_service_ping
token_created_at 2024-07-11T18:53:26Z
token_expires_at 2025-07-11T00:00:00Z
token_id 2
token_sha1_hash c6e762667cadb936f0c8439b0d240661a270eba1
type saas
```

All the config properties as defined above in the Config section can be patched.

You may also need to configure the Max/Default TTL for a token that can be issued by setting:

Max TTL: `1 year`
Expand Down
24 changes: 11 additions & 13 deletions backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,19 +93,17 @@ func (b *Backend) periodicFunc(ctx context.Context, request *logical.Request) er
var err error

b.lockClientMutex.Lock()
if config, err = getConfig(ctx, request.Storage); err != nil {
b.lockClientMutex.Unlock()
return err
}
b.lockClientMutex.Unlock()

if config == nil {
return nil
}

// If we need to autorotate the token, initiate the procedure to autorotate the token
if config.AutoRotateToken {
err = errors.Join(err, b.checkAndRotateConfigToken(ctx, request, config))
unlockLockClientMutex := sync.OnceFunc(func() { b.lockClientMutex.Unlock() })
defer unlockLockClientMutex()
if config, err = getConfig(ctx, request.Storage); err == nil {
unlockLockClientMutex()
if config == nil {
return nil
}
// If we need to autorotate the token, initiate the procedure to autorotate the token
if config.AutoRotateToken {
err = errors.Join(err, b.checkAndRotateConfigToken(ctx, request, config))
}
}

return err
Expand Down
155 changes: 130 additions & 25 deletions entry_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,138 @@ import (
"context"
"crypto/sha1"
"fmt"
"strconv"
"strings"
"time"

"github.com/hashicorp/go-multierror"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/logical"
)

type EntryConfig struct {
TokenId int `json:"token_id" yaml:"token_id" mapstructure:"token_id"`
BaseURL string `json:"base_url" structs:"base_url" mapstructure:"base_url"`
Token string `json:"token" structs:"token" mapstructure:"token"`
Token string `json:"token" structs:"token" mapstructure:"token" validate:"min=10,max=40"`
AutoRotateToken bool `json:"auto_rotate_token" structs:"auto_rotate_token" mapstructure:"auto_rotate_token"`
AutoRotateBefore time.Duration `json:"auto_rotate_before" structs:"auto_rotate_before" mapstructure:"auto_rotate_before"`
TokenCreatedAt time.Time `json:"token_created_at" structs:"token_created_at" mapstructure:"token_created_at"`
TokenExpiresAt time.Time `json:"token_expires_at" structs:"token_expires_at" mapstructure:"token_expires_at"`
Scopes []string `json:"scopes" structs:"scopes" mapstructure:"scopes"`
Type Type `json:"type" structs:"type" mapstructure:"type"`
Type Type `json:"type" structs:"type" mapstructure:"type" validate:"gitlab-type"`
}

func (e EntryConfig) Response() *logical.Response {
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))
}

if err = data.Validate(); err != nil {
return warnings, changes, multierror.Append(err)
}

changes = make(map[string]string)

if val, ok := data.GetOk("auto_rotate_token"); ok {
e.AutoRotateToken = val.(bool)
changes["auto_rotate_token"] = strconv.FormatBool(e.AutoRotateToken)
}

if typ, ok := data.GetOk("type"); ok {
var pType Type
if pType, er = TypeParse(typ.(string)); er != nil {
err = multierror.Append(err, er)
} else {
e.Type = pType
changes["type"] = pType.String()
}
}

if _, ok := data.GetOk("auto_rotate_before"); ok {
w, er := e.updateAutoRotateBefore(data)
if er != nil {
err = multierror.Append(err, er.Errors...)
} else {
changes["auto_rotate_before"] = e.AutoRotateBefore.String()
}
warnings = append(warnings, w...)
}

if val, ok := data.GetOk("base_url"); ok && len(val.(string)) > 0 {
e.BaseURL = val.(string)
changes["base_url"] = e.BaseURL
}

if val, ok := data.GetOk("token"); ok && len(val.(string)) > 0 {
e.Token = val.(string)
changes["token"] = strings.Repeat("*", len(e.Token))
}

return warnings, changes, err
}

func (e *EntryConfig) updateAutoRotateBefore(data *framework.FieldData) (warnings []string, err *multierror.Error) {
if val, ok := data.GetOk("auto_rotate_before"); ok {
atr, _ := 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))
} 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))
} else {
e.AutoRotateBefore = time.Duration(atr) * time.Second
}
} else {
e.AutoRotateBefore = DefaultAutoRotateBeforeMinTTL
warnings = append(warnings, fmt.Sprintf("auto_rotate_token not specified setting to %s", DefaultAutoRotateBeforeMinTTL))
}
return warnings, err
}

func (e *EntryConfig) UpdateFromFieldData(data *framework.FieldData) (warnings []string, err error) {
if data == nil {
return warnings, multierror.Append(fmt.Errorf("data: %w", ErrNilValue))
}

if err = data.Validate(); err != nil {
return warnings, multierror.Append(err)
}

var er error
e.AutoRotateToken = data.Get("auto_rotate_token").(bool)

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))
}

if typ, ok := data.GetOk("type"); ok {
if e.Type, er = TypeParse(typ.(string)); er != nil {
err = multierror.Append(err, er)
}
} else {
err = multierror.Append(err, fmt.Errorf("gitlab type: %w", 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))
}

{
w, er := e.updateAutoRotateBefore(data)
if er != nil {
err = multierror.Append(err, er.Errors...)
}
warnings = append(warnings, w...)
}

return warnings, err
}

func (e *EntryConfig) Response() *logical.Response {
return &logical.Response{
Secret: &logical.Secret{
LeaseOptions: logical.LeaseOptions{},
Expand All @@ -35,7 +148,7 @@ func (e EntryConfig) Response() *logical.Response {
}
}

func (e EntryConfig) LogicalResponseData() map[string]any {
func (e *EntryConfig) LogicalResponseData() map[string]any {
var tokenExpiresAt, tokenCreatedAt = "", ""
if !e.TokenExpiresAt.IsZero() {
tokenExpiresAt = e.TokenExpiresAt.Format(time.RFC3339)
Expand All @@ -57,33 +170,25 @@ func (e EntryConfig) LogicalResponseData() map[string]any {
}
}

func getConfig(ctx context.Context, s logical.Storage) (*EntryConfig, error) {
func getConfig(ctx context.Context, s logical.Storage) (cfg *EntryConfig, err error) {
if s == nil {
return nil, fmt.Errorf("%w: local.Storage", ErrNilValue)
}
entry, err := s.Get(ctx, PathConfigStorage)
if err != nil {
return nil, err
var entry *logical.StorageEntry
if entry, err = s.Get(ctx, PathConfigStorage); err == nil {
if entry == nil {
return nil, nil
}
cfg = new(EntryConfig)
_ = entry.DecodeJSON(cfg)
}

if entry == nil {
return nil, nil
}

cfg := new(EntryConfig)
if err := entry.DecodeJSON(cfg); err != nil {
return nil, err
}
return cfg, nil
return cfg, err
}

func saveConfig(ctx context.Context, config EntryConfig, s logical.Storage) error {
var err error
func saveConfig(ctx context.Context, config EntryConfig, s logical.Storage) (err error) {
var storageEntry *logical.StorageEntry
storageEntry, err = logical.StorageEntryJSON(PathConfigStorage, config)
if err != nil {
return nil
if storageEntry, err = logical.StorageEntryJSON(PathConfigStorage, config); err == nil {
err = s.Put(ctx, storageEntry)
}

return s.Put(ctx, storageEntry)
return err
}
Loading