diff --git a/cmd/sidecred-lambda/main.go b/cmd/sidecred-lambda/main.go index 0b632a8..06dd0ed 100644 --- a/cmd/sidecred-lambda/main.go +++ b/cmd/sidecred-lambda/main.go @@ -7,6 +7,7 @@ import ( "os" "github.com/telia-oss/sidecred" + "github.com/telia-oss/sidecred/config" "github.com/telia-oss/sidecred/internal/cli" "github.com/alecthomas/kingpin" @@ -15,7 +16,6 @@ import ( "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/s3" environment "github.com/telia-oss/aws-env" - "sigs.k8s.io/yaml" ) var version string @@ -55,7 +55,7 @@ type Event struct { func runFunc(configBucket *string) func(*sidecred.Sidecred, sidecred.StateBackend) error { return func(s *sidecred.Sidecred, backend sidecred.StateBackend) error { lambda.Start(func(event Event) error { - config, err := loadConfig(*configBucket, event.ConfigPath) + cfg, err := loadConfig(*configBucket, event.ConfigPath) if err != nil { return fmt.Errorf("failed to load config: %s", err) } @@ -64,13 +64,13 @@ func runFunc(configBucket *string) func(*sidecred.Sidecred, sidecred.StateBacken return fmt.Errorf("failed to load state: %s", err) } defer backend.Save(event.StatePath, state) - return s.Process(config, state) + return s.Process(cfg, state) }) return nil } } -func loadConfig(bucket, key string) (*sidecred.Config, error) { +func loadConfig(bucket, key string) (sidecred.Config, error) { sess, err := session.NewSession() if err != nil { return nil, err @@ -91,9 +91,5 @@ func loadConfig(bucket, key string) (*sidecred.Config, error) { return nil, err } - var config sidecred.Config - if err := yaml.UnmarshalStrict(b.Bytes(), &config); err != nil { - return nil, err - } - return &config, nil + return config.Parse(b.Bytes()) } diff --git a/cmd/sidecred/main.go b/cmd/sidecred/main.go index 4d6610d..359ffc6 100644 --- a/cmd/sidecred/main.go +++ b/cmd/sidecred/main.go @@ -6,10 +6,10 @@ import ( "os" "github.com/telia-oss/sidecred" + "github.com/telia-oss/sidecred/config" "github.com/telia-oss/sidecred/internal/cli" "github.com/alecthomas/kingpin" - "sigs.k8s.io/yaml" ) var version string @@ -25,21 +25,21 @@ func main() { kingpin.MustParse(app.Parse(os.Args[1:])) } -func runFunc(namespace *string, config *string, statePath *string) func(*sidecred.Sidecred, sidecred.StateBackend) error { +func runFunc(namespace *string, cfg *string, statePath *string) func(*sidecred.Sidecred, sidecred.StateBackend) error { return func(s *sidecred.Sidecred, backend sidecred.StateBackend) error { - b, err := ioutil.ReadFile(*config) + b, err := ioutil.ReadFile(*cfg) if err != nil { return fmt.Errorf("failed to read config: %s", err) } - var config sidecred.Config - if err := yaml.UnmarshalStrict(b, &config); err != nil { - return fmt.Errorf("failed to unmarshal config: %s", err) + cfg, err := config.Parse(b) + if err != nil { + return fmt.Errorf("failed to parse config: %s", err) } state, err := backend.Load(*statePath) if err != nil { return fmt.Errorf("failed to load state: %s", err) } defer backend.Save(*statePath, state) - return s.Process(&config, state) + return s.Process(cfg, state) } } diff --git a/cmd/sidecred/main_test.go b/cmd/sidecred/main_test.go index 7610fa5..eb6ad31 100644 --- a/cmd/sidecred/main_test.go +++ b/cmd/sidecred/main_test.go @@ -4,9 +4,9 @@ import ( "io/ioutil" "testing" + "github.com/telia-oss/sidecred/config" + "github.com/stretchr/testify/require" - "github.com/telia-oss/sidecred" - "sigs.k8s.io/yaml" ) // Verify that the testdata referenced in README.md is valid. @@ -14,10 +14,9 @@ func TestUnmarshalTestData(t *testing.T) { b, err := ioutil.ReadFile("./testdata/config.yml") require.NoError(t, err) - var config sidecred.Config - err = yaml.UnmarshalStrict(b, &config) + cfg, err := config.Parse(b) require.NoError(t, err) - err = config.Validate() + err = cfg.Validate() require.NoError(t, err) } diff --git a/config.go b/config.go deleted file mode 100644 index c7b116f..0000000 --- a/config.go +++ /dev/null @@ -1,186 +0,0 @@ -package sidecred - -import ( - "bytes" - "encoding/json" - "fmt" - "reflect" -) - -// NoConfig represents an empty JSON Configuration (used for testing). -var NoConfig = []byte("{}") - -// Config represents the user-defined configuration that should be passed to the sidecred.Sidecred.Process method. -type Config struct { - Version int `json:"version"` - Namespace string `json:"namespace"` - Stores []*StoreConfig `json:"stores"` - Requests []*RequestConfig `json:"requests"` -} - -// Validate the configuration. -func (c *Config) Validate() error { - if c.Version != 1 { - return fmt.Errorf("invalid configuration version: %d", c.Version) - } - if c.Namespace == "" { - return fmt.Errorf("%q must be defined", "namespace") - } - if len(c.Stores) == 0 { - return fmt.Errorf("%q must be defined", "stores") - } - - stores := make(map[string]struct{}, len(c.Stores)) - for i, s := range c.Stores { - switch s.Type { - case Inprocess, SSM, SecretsManager, GithubSecrets: - default: - return fmt.Errorf("stores[%d]: unknown type %q", i, string(s.Type)) - } - if _, found := stores[s.alias()]; found { - return fmt.Errorf("stores[%d]: duplicate store %q", i, s.alias()) - } - stores[s.alias()] = struct{}{} - } - - type requestsKey struct{ store, name string } - requests := make(map[requestsKey]struct{}, len(c.Requests)) - - for i, request := range c.Requests { - if _, found := stores[request.Store]; !found { - return fmt.Errorf("requests[%d]: undefined store %q", i, request.Store) - } - for ii, cred := range request.Creds { - if err := cred.validate(); err != nil { - return fmt.Errorf("requests[%d]: creds[%d]: %s", i, ii, err) - } - for _, r := range cred.flatten() { - switch r.Type { - case AWSSTS, GithubAccessToken, GithubDeployKey, ArtifactoryAccessToken, Randomized: - default: - return fmt.Errorf("requests[%d]: creds[%d]: unknown type %q", i, ii, string(r.Type)) - } - key := requestsKey{store: request.Store, name: r.Name} - if _, found := requests[key]; found { - return fmt.Errorf("requests[%d]: creds[%d]: duplicated request %+v", i, ii, key) - } - requests[key] = struct{}{} - } - } - } - return nil -} - -// StoreConfig is used to define the secret stores in the configuration for Sidecred. -type StoreConfig struct { - Type StoreType `json:"type"` - Name string `json:"name"` - Config json.RawMessage `json:"config,omitempty"` -} - -// alias returns a name that can be used to identify configured store. defaults to the StoreType. -func (c *StoreConfig) alias() string { - if c.Name != "" { - return c.Name - } - return string(c.Type) -} - -// RequestConfig maps credential requests to a secret store, and is part of the configuration format for Sidecred. -type RequestConfig struct { - Store string `json:"store"` - Creds []*CredentialRequestConfig `json:"creds"` -} - -// CredentialRequests returns the flattened list of CredentialRequest's. -func (c *RequestConfig) CredentialRequests() (requests []*CredentialRequest) { - for _, cred := range c.Creds { - requests = append(requests, cred.flatten()...) - } - return requests -} - -// CredentialRequestConfig extends sidecred.CredentialRequest by allowing it to be defined in two ways: -// 1. As a regular CredentialRequest. -// 2. As a list of requests that share a CredentialType (nested credential requests should omit "type"): -// -// - type: aws:sts -// list: -// - name: credential1 -// config ... -// - name: credential2 -// config ... -// -type CredentialRequestConfig struct { - *CredentialRequest `json:",inline"` - List []*CredentialRequest `json:"list,omitempty"` -} - -// validate the configRequest. -func (c *CredentialRequestConfig) validate() error { - if len(c.List) == 0 { - return nil // config.Validate covers the inlined request. - } - if c.CredentialRequest.Name != "" { - return fmt.Errorf("%q should not be specified for lists", "name") - } - if len(c.CredentialRequest.Config) > 0 { - return fmt.Errorf("%q should not be specified for lists", "config") - } - for i, r := range c.List { - if r.Type != "" { - return fmt.Errorf("list entry[%d]: request should not include %q", i, "type") - } - } - return nil -} - -// flatten returns the flattened list of credential requests. -func (c *CredentialRequestConfig) flatten() []*CredentialRequest { - if len(c.List) == 0 { - return []*CredentialRequest{c.CredentialRequest} - } - var requests []*CredentialRequest - for _, r := range c.List { - r.Type = c.CredentialRequest.Type - requests = append(requests, r) - } - return c.List -} - -// UnmarshalConfig is a convenience method for performing a strict unmarshalling of a JSON config into a provided -// structure. If config is empty, no operation is performed by this function. -func UnmarshalConfig(config json.RawMessage, target interface{}) error { - if len(config) == 0 { - return nil - } - d := json.NewDecoder(bytes.NewReader(config)) - d.DisallowUnknownFields() - return d.Decode(target) -} - -// isEqualConfig is a convenience function for unmarshalling the JSON config -// from the request and resource structures, and performing a logical deep -// equality check instead of a byte equality check. This avoids errors due to -// structural (but non-logical) changes due to (de)serialization. -func isEqualConfig(b1, b2 []byte) bool { - var o1 interface{} - var o2 interface{} - - // Allow the configurations to both be empty - if len(b1) == 0 && len(b2) == 0 { - return true - } - - err := json.Unmarshal(b1, &o1) - if err != nil { - return false - } - - err = json.Unmarshal(b2, &o2) - if err != nil { - return false - } - - return reflect.DeepEqual(o1, o2) -} diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..c4fd9ec --- /dev/null +++ b/config/config.go @@ -0,0 +1,176 @@ +package config + +import ( + "fmt" + + "github.com/telia-oss/sidecred" + "sigs.k8s.io/yaml" +) + +// Parse a YAML (or JSON) representation of sidecred.Config. +func Parse(b []byte) (cfg sidecred.Config, err error) { + var t struct { + Version *int `json:"version"` + } + err = yaml.Unmarshal(b, &t) + if err != nil { + return nil, fmt.Errorf("unmarshal version: %s", err) + } + if t.Version == nil { + return nil, fmt.Errorf("%q must be defined", "version") + } + switch *t.Version { + case 1: + var v1 *v1 + err = yaml.UnmarshalStrict(b, &v1) + cfg = v1 + default: + return nil, fmt.Errorf("unknown configuration version: %d", t.Version) + } + if err != nil { + return nil, fmt.Errorf("unmarshal config (version %d): %s", t.Version, err) + } + return cfg, nil +} + +var ( + _ sidecred.Config = &v1{} +) + +type v1 struct { + Version int `json:"version"` + CredentialNamespace string `json:"namespace"` + CredentialStores []*sidecred.StoreConfig `json:"stores"` + CredentialRequests []*requestV1 `json:"requests"` +} + +// Namespace implements sidecred.Config. +func (c *v1) Namespace() string { + return c.CredentialNamespace +} + +// Stores implements sidecred.Config. +func (c *v1) Stores() []*sidecred.StoreConfig { + return c.CredentialStores +} + +// Requests implements sidecred.Config. +func (c *v1) Requests() (out []*sidecred.Request) { + for _, r := range c.CredentialRequests { + out = append(out, r.asRequest()) + } + return out +} + +// Validate implements sidecred.Config. +func (c *v1) Validate() error { + if c.CredentialNamespace == "" { + return fmt.Errorf("%q must be defined", "namespace") + } + if len(c.CredentialStores) == 0 { + return fmt.Errorf("%q must be defined", "stores") + } + + stores := make(map[string]struct{}, len(c.CredentialStores)) + for i, s := range c.CredentialStores { + switch s.Type { + case sidecred.Inprocess, sidecred.SSM, sidecred.SecretsManager, sidecred.GithubSecrets: + default: + return fmt.Errorf("stores[%d]: unknown type %q", i, string(s.Type)) + } + if _, found := stores[s.Alias()]; found { + return fmt.Errorf("stores[%d]: duplicate store %q", i, s.Alias()) + } + stores[s.Alias()] = struct{}{} + } + + type requestsKey struct{ store, name string } + requests := make(map[requestsKey]struct{}, len(c.CredentialRequests)) + + for i, request := range c.CredentialRequests { + if _, found := stores[request.Store]; !found { + return fmt.Errorf("requests[%d]: undefined store %q", i, request.Store) + } + for ii, cred := range request.Creds { + if err := cred.validate(); err != nil { + return fmt.Errorf("requests[%d]: creds[%d]: %s", i, ii, err) + } + for _, r := range cred.flatten() { + switch r.Type { + case sidecred.AWSSTS, sidecred.GithubAccessToken, sidecred.GithubDeployKey, sidecred.ArtifactoryAccessToken, sidecred.Randomized: + default: + return fmt.Errorf("requests[%d]: creds[%d]: unknown type %q", i, ii, string(r.Type)) + } + key := requestsKey{store: request.Store, name: r.Name} + if _, found := requests[key]; found { + return fmt.Errorf("requests[%d]: creds[%d]: duplicated request %+v", i, ii, key) + } + requests[key] = struct{}{} + } + } + } + return nil +} + +type requestV1 struct { + Store string `json:"store"` + Creds []*credentialRequest `json:"creds"` +} + +func (c *requestV1) asRequest() *sidecred.Request { + r := &sidecred.Request{ + Store: c.Store, + } + for _, cred := range c.Creds { + r.Credentials = append(r.Credentials, cred.flatten()...) + } + return r +} + +// credentialRequest extends sidecred.CredentialRequest by allowing it to be defined in two ways: +// 1. As a regular CredentialRequest. +// 2. As a list of requests that share a CredentialType (nested credential requests should omit "type"): +// +// - type: aws:sts +// list: +// - name: credential1 +// config ... +// - name: credential2 +// config ... +// +type credentialRequest struct { + *sidecred.CredentialRequest `json:",inline"` + List []*sidecred.CredentialRequest `json:"list,omitempty"` +} + +// validate the configRequest. +func (c *credentialRequest) validate() error { + if len(c.List) == 0 { + return nil // config.Validate covers the inlined request. + } + if c.CredentialRequest.Name != "" { + return fmt.Errorf("%q should not be specified for lists", "name") + } + if len(c.CredentialRequest.Config) > 0 { + return fmt.Errorf("%q should not be specified for lists", "config") + } + for i, r := range c.List { + if r.Type != "" { + return fmt.Errorf("list entry[%d]: request should not include %q", i, "type") + } + } + return nil +} + +// flatten returns the flattened list of credential requests. +func (c *credentialRequest) flatten() []*sidecred.CredentialRequest { + if len(c.List) == 0 { + return []*sidecred.CredentialRequest{c.CredentialRequest} + } + var requests []*sidecred.CredentialRequest + for _, r := range c.List { + r.Type = c.CredentialRequest.Type + requests = append(requests, r) + } + return requests +} diff --git a/config_test.go b/config/config_test.go similarity index 89% rename from config_test.go rename to config/config_test.go index 5344328..20b88de 100644 --- a/config_test.go +++ b/config/config_test.go @@ -1,17 +1,17 @@ -package sidecred_test +package config_test import ( "strings" "testing" "github.com/telia-oss/sidecred" + "github.com/telia-oss/sidecred/config" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "sigs.k8s.io/yaml" ) -func TestConfig(t *testing.T) { +func TestV1Config(t *testing.T) { tests := []struct { description string config string @@ -123,22 +123,22 @@ requests: for _, tc := range tests { t.Run(tc.description, func(t *testing.T) { var ( - config *sidecred.Config + cfg sidecred.Config actual string err error ) - err = yaml.Unmarshal([]byte(tc.config), &config) + cfg, err = config.Parse([]byte(tc.config)) require.NoError(t, err) - err = config.Validate() + err = cfg.Validate() if err != nil { actual = err.Error() } assert.Equal(t, tc.expected, actual) - assert.Equal(t, tc.expectedRequestCount, len(config.Requests)) + assert.Equal(t, tc.expectedRequestCount, len(cfg.Requests())) for i, expectedCount := range tc.expectedCountPerRequest { - assert.Equal(t, expectedCount, len(config.Requests[i].CredentialRequests())) + assert.Equal(t, expectedCount, len(cfg.Requests()[i].Credentials)) } }) } diff --git a/internal/cli/cli_test.go b/internal/cli/cli_test.go index f3c73d4..54e43f1 100644 --- a/internal/cli/cli_test.go +++ b/internal/cli/cli_test.go @@ -8,6 +8,7 @@ import ( "github.com/telia-oss/sidecred" "github.com/telia-oss/sidecred/backend/s3" "github.com/telia-oss/sidecred/backend/s3/s3fakes" + "github.com/telia-oss/sidecred/config" "github.com/telia-oss/sidecred/internal/cli" "github.com/telia-oss/sidecred/provider/sts" "github.com/telia-oss/sidecred/provider/sts/stsfakes" @@ -22,7 +23,6 @@ import ( "go.uber.org/zap" "go.uber.org/zap/zapcore" "go.uber.org/zap/zaptest" - "sigs.k8s.io/yaml" ) func testAWSClientFactory() (s3.S3API, sts.STSAPI, ssm.SSMAPI, secretsmanager.SecretsManagerAPI) { @@ -59,7 +59,7 @@ func TestCLI(t *testing.T) { return l, nil } - config := strings.TrimSpace(` + cfg := strings.TrimSpace(` --- version: 1 namespace: example @@ -77,11 +77,11 @@ requests: `) runFunc := func(s *sidecred.Sidecred, _ sidecred.StateBackend) error { - var c sidecred.Config - if err := yaml.UnmarshalStrict([]byte(config), &c); err != nil { - return fmt.Errorf("failed to unmarshal config: %s", err) + c, err := config.Parse([]byte(cfg)) + if err != nil { + return fmt.Errorf("failed to parse config: %s", err) } - return s.Process(&c, &sidecred.State{}) + return s.Process(c, &sidecred.State{}) } app := kingpin.New("test", "").Terminate(nil) diff --git a/sidecred.go b/sidecred.go index 00b0d01..fcf0627 100644 --- a/sidecred.go +++ b/sidecred.go @@ -1,8 +1,10 @@ package sidecred import ( + "bytes" "encoding/json" "fmt" + "reflect" "strconv" "strings" "text/template" @@ -11,6 +13,20 @@ import ( "go.uber.org/zap" ) +// Config represents the user-defined configuration that should be passed to the sidecred.Sidecred.Process method. +type Config interface { + Namespace() string + Stores() []*StoreConfig + Requests() []*Request + Validate() error +} + +// Request ... +type Request struct { + Store string + Credentials []*CredentialRequest +} + // CredentialRequest is the root datastructure used to request credentials in Sidecred. type CredentialRequest struct { // Type identifies the type of credential (and provider) for a request. @@ -43,7 +59,6 @@ func (r *CredentialRequest) UnmarshalConfig(target interface{}) error { // hasValidCredentials returns true if there are already valid credentials // for the request. This is determined by the last resource state. func (r *CredentialRequest) hasValidCredentials(resource *Resource, rotationWindow time.Duration) bool { - if resource.Deposed { return false } @@ -53,19 +68,53 @@ func (r *CredentialRequest) hasValidCredentials(resource *Resource, rotationWind if !isEqualConfig(r.Config, resource.Config) { return false } - rotation := rotationWindow if r.RotationWindow != nil { rotation = r.RotationWindow.Duration } - if resource.Expiration.Add(-rotation).Before(time.Now()) { return false } - return true } +// UnmarshalConfig is a convenience method for performing a strict unmarshalling of a JSON config into a provided +// structure. If config is empty, no operation is performed by this function. +func UnmarshalConfig(config json.RawMessage, target interface{}) error { + if len(config) == 0 { + return nil + } + d := json.NewDecoder(bytes.NewReader(config)) + d.DisallowUnknownFields() + return d.Decode(target) +} + +// isEqualConfig is a convenience function for unmarshalling the JSON config +// from the request and resource structures, and performing a logical deep +// equality check instead of a byte equality check. This avoids errors due to +// structural (but non-logical) changes due to (de)serialization. +func isEqualConfig(b1, b2 []byte) bool { + var o1 interface{} + var o2 interface{} + + // Allow the configurations to both be empty + if len(b1) == 0 && len(b2) == 0 { + return true + } + + err := json.Unmarshal(b1, &o1) + if err != nil { + return false + } + + err = json.Unmarshal(b2, &o2) + if err != nil { + return false + } + + return reflect.DeepEqual(o1, o2) +} + // Duration implements JSON (un)marshal for time.Duration. type Duration struct { time.Duration @@ -175,6 +224,21 @@ const ( // StoreType ... type StoreType string +// StoreConfig is used to define the secret stores in the configuration for Sidecred. +type StoreConfig struct { + Type StoreType `json:"type"` + Name string `json:"name"` + Config json.RawMessage `json:"config,omitempty"` +} + +// Alias returns a name that can be used to identify configured store. defaults to the StoreType. +func (c *StoreConfig) Alias() string { + if c.Name != "" { + return c.Name + } + return string(c.Type) +} + // SecretStore is implemented by store backends for secrets. type SecretStore interface { // Type returns the store type. @@ -239,22 +303,22 @@ type Sidecred struct { } // Process a single sidecred.Request. -func (s *Sidecred) Process(config *Config, state *State) error { - log := s.logger.With(zap.String("namespace", config.Namespace)) - log.Info("starting sidecred", zap.Int("requests", len(config.Requests))) +func (s *Sidecred) Process(config Config, state *State) error { + log := s.logger.With(zap.String("namespace", config.Namespace())) + log.Info("starting sidecred", zap.Int("requests", len(config.Requests()))) if err := config.Validate(); err != nil { return fmt.Errorf("invalid config: %s", err) } RequestLoop: - for _, request := range config.Requests { + for _, request := range config.Requests() { var ( store SecretStore storeConfig *StoreConfig ) - for _, sc := range config.Stores { - if sc.alias() == request.Store { + for _, sc := range config.Stores() { + if sc.Alias() == request.Store { storeConfig = sc } } @@ -269,7 +333,7 @@ RequestLoop: } CredentialLoop: - for _, r := range request.CredentialRequests() { + for _, r := range request.Credentials { log := log.With(zap.String("type", string(r.Type)), zap.String("store", request.Store)) if r.Name == "" { log.Warn("missing name in request") @@ -282,7 +346,7 @@ RequestLoop: } log.Info("processing request", zap.String("name", r.Name)) - for _, resource := range state.GetResourcesByID(r.Type, r.Name, storeConfig.alias()) { + for _, resource := range state.GetResourcesByID(r.Type, r.Name, storeConfig.Alias()) { if r.hasValidCredentials(resource, s.rotationWindow) { log.Info("found existing credentials", zap.String("name", r.Name)) continue CredentialLoop @@ -298,11 +362,11 @@ RequestLoop: log.Error("no credentials returned by provider") continue CredentialLoop } - state.AddResource(newResource(r, storeConfig.alias(), creds[0].Expiration, metadata)) + state.AddResource(newResource(r, storeConfig.Alias(), creds[0].Expiration, metadata)) log.Info("created new credentials", zap.Int("count", len(creds))) for _, c := range creds { - path, err := store.Write(config.Namespace, c, storeConfig.Config) + path, err := store.Write(config.Namespace(), c, storeConfig.Config) if err != nil { log.Error("store credential", zap.String("name", c.Name), zap.Error(err)) continue diff --git a/sidecred_test.go b/sidecred_test.go index 2bd7b66..bda6995 100644 --- a/sidecred_test.go +++ b/sidecred_test.go @@ -6,12 +6,12 @@ import ( "time" "github.com/telia-oss/sidecred" + "github.com/telia-oss/sidecred/config" "github.com/telia-oss/sidecred/store/inprocess" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap/zaptest" - "sigs.k8s.io/yaml" ) var ( @@ -328,11 +328,10 @@ requests: s, err := sidecred.New([]sidecred.Provider{provider}, []sidecred.SecretStore{store}, 10*time.Minute, logger) require.NoError(t, err) - var config *sidecred.Config - err = yaml.UnmarshalStrict([]byte(tc.config), &config) + cfg, err := config.Parse([]byte(tc.config)) require.NoError(t, err) - err = s.Process(config, state) + err = s.Process(cfg, state) require.NoError(t, err) assert.Equal(t, tc.expectedCreateCalls, provider.CreateCallCount(), "create calls") assert.Equal(t, tc.expectedDestroyCalls, provider.DestroyCallCount(), "destroy calls") @@ -342,7 +341,7 @@ requests: } for k, v := range tc.expectedSecrets { - value, found, err := store.Read(k, sidecred.NoConfig) + value, found, err := store.Read(k, []byte("{}")) assert.NoError(t, err) assert.True(t, found, "secret exists") assert.Equal(t, v, value) @@ -430,11 +429,10 @@ stores: s, err := sidecred.New([]sidecred.Provider{provider}, []sidecred.SecretStore{store}, 10*time.Minute, logger) require.NoError(t, err) - var config *sidecred.Config - err = yaml.UnmarshalStrict([]byte(tc.config), &config) + cfg, err := config.Parse([]byte(tc.config)) require.NoError(t, err) - err = s.Process(config, state) + err = s.Process(cfg, state) require.NoError(t, err) assert.Equal(t, tc.expectedDestroyCalls, provider.DestroyCallCount(), "destroy calls")