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!: Add store configuration support for SSM store #532

Merged
merged 1 commit into from
Aug 1, 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
10 changes: 10 additions & 0 deletions store/nullstore.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,16 @@
return &NullStore{}
}

func (s *NullStore) Config(ctx context.Context) (StoreConfig, error) {
return StoreConfig{
Version: LatestStoreConfigVersion,
}, nil

Check warning on line 19 in store/nullstore.go

View check run for this annotation

Codecov / codecov/patch

store/nullstore.go#L16-L19

Added lines #L16 - L19 were not covered by tests
}

func (s *NullStore) SetConfig(ctx context.Context, config StoreConfig) error {
return errors.New("SetConfig is not implemented for Null Store")

Check warning on line 23 in store/nullstore.go

View check run for this annotation

Codecov / codecov/patch

store/nullstore.go#L22-L23

Added lines #L22 - L23 were not covered by tests
}

func (s *NullStore) Write(ctx context.Context, id SecretId, value string) error {
return errors.New("Write is not implemented for Null Store")
}
Expand Down
10 changes: 10 additions & 0 deletions store/s3store.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,16 @@
}, nil
}

func (s *S3Store) Config(ctx context.Context) (StoreConfig, error) {
return StoreConfig{
Version: LatestStoreConfigVersion,
}, nil

Check warning on line 78 in store/s3store.go

View check run for this annotation

Codecov / codecov/patch

store/s3store.go#L75-L78

Added lines #L75 - L78 were not covered by tests
}

func (s *S3Store) SetConfig(ctx context.Context, config StoreConfig) error {
return errors.New("Not implemented for S3 Store")

Check warning on line 82 in store/s3store.go

View check run for this annotation

Codecov / codecov/patch

store/s3store.go#L81-L82

Added lines #L81 - L82 were not covered by tests
}

func (s *S3Store) Write(ctx context.Context, id SecretId, value string) error {
index, err := s.readLatest(ctx, id.Service)
if err != nil {
Expand Down
10 changes: 10 additions & 0 deletions store/secretsmanagerstore.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,16 @@
}, nil
}

func (s *SecretsManagerStore) Config(ctx context.Context) (StoreConfig, error) {
return StoreConfig{
Version: LatestStoreConfigVersion,
}, nil
}

func (s *SecretsManagerStore) SetConfig(ctx context.Context, config StoreConfig) error {
return errors.New("Not implemented for Secrets Manager Store")

Check warning on line 124 in store/secretsmanagerstore.go

View check run for this annotation

Codecov / codecov/patch

store/secretsmanagerstore.go#L123-L124

Added lines #L123 - L124 were not covered by tests
}

// Write writes a given value to a secret identified by id. If the secret
// already exists, then write a new version.
func (s *SecretsManagerStore) Write(ctx context.Context, id SecretId, value string) error {
Expand Down
10 changes: 10 additions & 0 deletions store/secretsmanagerstore_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -477,3 +477,13 @@ func uniqueID() string {
_, _ = rand.Read(uuid)
return fmt.Sprintf("%x", uuid)
}

func TestSecretsManagerStoreConfig(t *testing.T) {
store := &SecretsManagerStore{}

config, err := store.Config(context.Background())

assert.NoError(t, err)
assert.Equal(t, LatestStoreConfigVersion, config.Version)
assert.Empty(t, config.RequiredTags)
}
44 changes: 44 additions & 0 deletions store/ssmstore.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import (
"context"
"encoding/json"
"errors"
"fmt"
"os"
Expand Down Expand Up @@ -93,6 +94,49 @@
return fromEnv
}

const (
storeConfigKey = "store-config"
)

var (
storeConfigID = SecretId{
Service: ChamberService,
Key: storeConfigKey,
}
)

func (s *SSMStore) Config(ctx context.Context) (StoreConfig, error) {
configSecret, err := s.readLatest(ctx, storeConfigID)
if err != nil {
if err == ErrSecretNotFound {
return StoreConfig{
Version: LatestStoreConfigVersion,
}, nil
} else {
return StoreConfig{}, err

Check warning on line 116 in store/ssmstore.go

View check run for this annotation

Codecov / codecov/patch

store/ssmstore.go#L116

Added line #L116 was not covered by tests
}
}

var config StoreConfig
if err := json.Unmarshal([]byte(*configSecret.Value), &config); err != nil {
return StoreConfig{}, fmt.Errorf("failed to unmarshal store config: %w", err)

Check warning on line 122 in store/ssmstore.go

View check run for this annotation

Codecov / codecov/patch

store/ssmstore.go#L122

Added line #L122 was not covered by tests
}
return config, nil
}

func (s *SSMStore) SetConfig(ctx context.Context, config StoreConfig) error {
configBytes, err := json.Marshal(config)
if err != nil {
return fmt.Errorf("failed to marshal store config: %w", err)

Check warning on line 130 in store/ssmstore.go

View check run for this annotation

Codecov / codecov/patch

store/ssmstore.go#L130

Added line #L130 was not covered by tests
}

err = s.write(ctx, storeConfigID, string(configBytes), nil)
if err != nil {
return fmt.Errorf("failed to write store config: %w", err)

Check warning on line 135 in store/ssmstore.go

View check run for this annotation

Codecov / codecov/patch

store/ssmstore.go#L135

Added line #L135 was not covered by tests
}
return nil
}

// Write writes a given value to a secret identified by id. If the secret
// already exists, then write a new version.
func (s *SSMStore) Write(ctx context.Context, id SecretId, value string) error {
Expand Down
57 changes: 57 additions & 0 deletions store/ssmstore_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package store
import (
"context"
"errors"
"fmt"
"os"
"sort"
"strings"
Expand Down Expand Up @@ -853,3 +854,59 @@ type ByKeyRaw []RawSecret
func (a ByKeyRaw) Len() int { return len(a) }
func (a ByKeyRaw) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a ByKeyRaw) Less(i, j int) bool { return a[i].Key < a[j].Key }

func TestSSMStoreConfig(t *testing.T) {
storeConfigName := fmt.Sprintf("/%s/%s", ChamberService, storeConfigKey)
parameters := map[string]mockParameter{
storeConfigName: {
currentParam: &types.Parameter{
Name: aws.String(storeConfigName),
Type: types.ParameterTypeSecureString,
Value: aws.String(`{"version":"2","requiredTags":["key1", "key2"]}`),
},
meta: &types.ParameterMetadata{
Name: aws.String(storeConfigName),
Description: aws.String("1"),
LastModifiedDate: aws.Time(time.Now()),
LastModifiedUser: aws.String("test"),
},
},
}
store := NewTestSSMStore(parameters)

config, err := store.Config(context.Background())

assert.NoError(t, err)
assert.Equal(t, "2", config.Version)
assert.Equal(t, []string{"key1", "key2"}, config.RequiredTags)
}

func TestSSMStoreConfig_Missing(t *testing.T) {
parameters := map[string]mockParameter{}
store := NewTestSSMStore(parameters)

config, err := store.Config(context.Background())

assert.NoError(t, err)
assert.Equal(t, LatestStoreConfigVersion, config.Version)
assert.Empty(t, config.RequiredTags)
}

func TestSSMStoreSetConfig(t *testing.T) {
parameters := map[string]mockParameter{}
store := NewTestSSMStore(parameters)

config := StoreConfig{
Version: "2.1",
RequiredTags: []string{"key1.1", "key2.1"},
}
err := store.SetConfig(context.Background(), config)

assert.NoError(t, err)

config, err = store.Config(context.Background())

assert.NoError(t, err)
assert.Equal(t, "2.1", config.Version)
assert.Equal(t, []string{"key1.1", "key2.1"}, config.RequiredTags)
}
14 changes: 14 additions & 0 deletions store/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,18 @@ func ReservedService(service string) bool {
return service == ChamberService
}

const (
LatestStoreConfigVersion = "1"
)

// StoreConfig holds configuration information for a store. WARNING: Despite
// its public visibility, the contents of this struct are subject to change at
// any time, and are not part of the public interface for chamber.
type StoreConfig struct {
Version string `json:"version"`
RequiredTags []string `json:"requiredTags,omitempty"`
}

type ChangeEventType int

const (
Expand Down Expand Up @@ -73,6 +85,8 @@ type ChangeEvent struct {

// Store is an interface for a secret store.
type Store interface {
Config(ctx context.Context) (StoreConfig, error)
SetConfig(ctx context.Context, config StoreConfig) error
Write(ctx context.Context, id SecretId, value string) error
WriteWithTags(ctx context.Context, id SecretId, value string, tags map[string]string) error
Read(ctx context.Context, id SecretId, version int) (Secret, error)
Expand Down
Loading