Skip to content

Commit

Permalink
feat: Label support for SSMStore (#242)
Browse files Browse the repository at this point in the history
  • Loading branch information
rh-kpatel4 committed Feb 25, 2020
1 parent 5250276 commit efa34c9
Show file tree
Hide file tree
Showing 6 changed files with 110 additions and 7 deletions.
2 changes: 1 addition & 1 deletion cmd/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ func execRun(cmd *cobra.Command, args []string) error {
}

for _, service := range services {
if err := validateService(service); err != nil {
if err := validateServiceWithLabel(service); err != nil {
return errors.Wrap(err, "Failed to validate service")
}
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func init() {

func list(cmd *cobra.Command, args []string) error {
service := strings.ToLower(args[0])
if err := validateService(service); err != nil {
if err := validateServiceWithLabel(service); err != nil {
return errors.Wrap(err, "Failed to validate service")
}

Expand Down
23 changes: 20 additions & 3 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@ import (

// Regex's used to validate service and key names
var (
validKeyFormat = regexp.MustCompile(`^[\w\-\.]+$`)
validServiceFormat = regexp.MustCompile(`^[\w\-\.]+$`)
validServicePathFormat = regexp.MustCompile(`^[\w\-\.]+(\/[\w\-\.]+)*$`)
validKeyFormat = regexp.MustCompile(`^[\w\-\.]+$`)
validServiceFormat = regexp.MustCompile(`^[\w\-\.]+$`)
validServicePathFormat = regexp.MustCompile(`^[\w\-\.]+(\/[\w\-\.]+)*$`)
validServiceFormatWithLabel = regexp.MustCompile(`^[\w\-\.\:]+$`)
validServicePathFormatWithLabel = regexp.MustCompile(`^[\w\-\.]+(\/[\w\-\.]+)+(\:[\w\-\.]+)*$`)

This comment has been minimized.

Copy link
@sworisbreathing

sworisbreathing Feb 27, 2020

Contributor

This appears to be what's causing #245. I believe the line should be:

validServicePathFormatWithLabel = regexp.MustCompile(`^[\w\-\.]+((\/[\w\-\.]+)+(\:[\w\-\.]+)*)?$`)

verbose bool
numRetries int
Expand Down Expand Up @@ -110,6 +112,21 @@ func validateService(service string) error {
return nil
}

func validateServiceWithLabel(service string) error {
_, noPaths := os.LookupEnv("CHAMBER_NO_PATHS")
if noPaths {
if !validServiceFormatWithLabel.MatchString(service) {
return fmt.Errorf("Failed to validate service name '%s'. Only alphanumeric, dashes, fullstops and underscores are allowed for service names, and colon followed by a label name", service)
}
} else {
if !validServicePathFormatWithLabel.MatchString(service) {
return fmt.Errorf("Failed to validate service name '%s'. Only alphanumeric, dashes, forwardslashes, fullstops and underscores are allowed for service names, and colon followed by a label name", service)
}
}

return nil
}

func validateKey(key string) error {
if !validKeyFormat.MatchString(key) {
return fmt.Errorf("Failed to validate key name '%s'. Only alphanumeric, dashes, fullstops and underscores are allowed for key names", key)
Expand Down
31 changes: 31 additions & 0 deletions cmd/root_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,4 +107,35 @@ func TestValidations(t *testing.T) {
})
}
os.Unsetenv("CHAMBER_NO_PATHS")

// Test Service format with PATH and Label
validServicePathFormatWithLabel := []string{
"foo/bar:-current-",
"foo.bar/foo:current",
"foo-bar/foo:current",
"foo-bar/foo-bar:current",
"foo/bar/foo:current",
"foo/bar/foo-bar:current",
"foo/bar/foo-bar",
}

for _, k := range validServicePathFormatWithLabel {
t.Run("Service with PATH validation and label should return Nil", func(t *testing.T) {
result := validateServiceWithLabel(k)
assert.Nil(t, result)
})
}

invalidServicePathFormatWithLabel := []string{
"foo:current$",
"foo.:",
"foo.bar:cur|rent",
}

for _, k := range invalidServicePathFormatWithLabel {
t.Run("Service with PATH validation and label should return Error", func(t *testing.T) {
result := validateServiceWithLabel(k)
assert.Error(t, result)
})
}
}
35 changes: 33 additions & 2 deletions store/ssmstore.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ var validKeyFormat = regexp.MustCompile(`^[\w\-\.]+$`)
// ensure SSMStore confirms to Store interface
var _ Store = &SSMStore{}

// label check regexp
var labelMatchRegex = regexp.MustCompile(`^(\/[\w\-\.]+)+:(.+)$`)

// SSMStore implements the Store interface for storing secrets in SSM Parameter
// Store
type SSMStore struct {
Expand Down Expand Up @@ -296,11 +299,13 @@ func (s *SSMStore) ListServices(service string, includeSecretName bool) ([]strin
// List lists all secrets for a given service. If includeValues is true,
// then those secrets are decrypted and returned, otherwise only the metadata
// about a secret is returned.
func (s *SSMStore) List(service string, includeValues bool) ([]Secret, error) {
func (s *SSMStore) List(serviceName string, includeValues bool) ([]Secret, error) {
secrets := map[string]Secret{}

var describeParametersInput *ssm.DescribeParametersInput

service, _ := parseServiceLabel(serviceName)

if s.usePaths {
describeParametersInput = &ssm.DescribeParametersInput{
ParameterFilters: []*ssm.ParameterStringFilter{
Expand Down Expand Up @@ -372,13 +377,23 @@ func (s *SSMStore) List(service string, includeValues bool) ([]Secret, error) {
// ListRaw lists all secrets keys and values for a given service. Does not include any
// other meta-data. Uses faster AWS APIs with much higher rate-limits. Suitable for
// use in production environments.
func (s *SSMStore) ListRaw(service string) ([]RawSecret, error) {
func (s *SSMStore) ListRaw(serviceName string) ([]RawSecret, error) {
service, label := parseServiceLabel(serviceName)
if s.usePaths {
secrets := map[string]RawSecret{}
getParametersByPathInput := &ssm.GetParametersByPathInput{
Path: aws.String("/" + service + "/"),
WithDecryption: aws.Bool(true),
}
if label != "" {
getParametersByPathInput.ParameterFilters = []*ssm.ParameterStringFilter{
{
Key: aws.String("Label"),
Option: aws.String("Equals"),
Values: []*string{aws.String(label)},
},
}
}

err := s.svc.GetParametersByPathPages(getParametersByPathInput, func(resp *ssm.GetParametersByPathOutput, lastPage bool) bool {
for _, param := range resp.Parameters {
Expand Down Expand Up @@ -559,3 +574,19 @@ func getChangeType(version int) ChangeEventType {
}
return Updated
}

func parseServiceLabel(serviceAndLabel string) (string, string) {
if labelMatchRegex.MatchString(serviceAndLabel) {
i := strings.Index(serviceAndLabel, ":")

if i > -1 {
service := serviceAndLabel[:i]
label := serviceAndLabel[i+1:]
return service, label
}

return serviceAndLabel, ""
}

return serviceAndLabel, ""
}
24 changes: 24 additions & 0 deletions store/ssmstore_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -831,6 +831,30 @@ func TestValidations(t *testing.T) {
assert.False(t, result)
})
}

// Valid Path with Label
validPathWithLabelFormat := [][]string{
{"/foo", "/foo", ""},
{"/foo.", "/foo.", ""},
{"/foo.", "/foo.", ""},
{"/.foo:blue", "/.foo", "blue"},
{"/foo.bar:v30", "/foo.bar", "v30"},
{"/foo-bar:v90", "/foo-bar", "v90"},
{"/foo/bar:current", "/foo/bar", "current"},
{"/foo.bar/foo:yellow", "/foo.bar/foo", "yellow"},
{"/foo-bar/foo:v30:current", "/foo-bar/foo", "v30:current"},
{"/foo-bar/foo-bar:v10", "/foo-bar/foo-bar", "v10"},
{"/foo/bar/foo:90", "/foo/bar/foo", "90"},
{"/foo/bar/foo-bar:90-10", "/foo/bar/foo-bar", "90-10"},
}

for _, k := range validPathWithLabelFormat {
t.Run("Path Validation with Label should return true", func(t *testing.T) {
result, label := parseServiceLabel(k[0])
assert.Equal(t, result, k[1])
assert.Equal(t, label, k[2])
})
}
}

type ByKey []Secret
Expand Down

0 comments on commit efa34c9

Please sign in to comment.