From 4cbfae786637f867266e5667dd156c7517bb9d99 Mon Sep 17 00:00:00 2001 From: Jack Date: Thu, 6 Jun 2024 14:45:29 -0700 Subject: [PATCH] Validate that SSM params have leading '/' A value like 'ssm://foo/bar' will currently get picked up as a parameter named 'foo/bar', but then the lookup in AWS will fail. These changes add a simple validation that the path has a leading '/'. --- main.go | 5 ++++ main_test.go | 76 ++++++++++++++++++++++++++++++++-------------------- 2 files changed, 52 insertions(+), 29 deletions(-) diff --git a/main.go b/main.go index ae9c3d1..cbdcc80 100644 --- a/main.go +++ b/main.go @@ -187,6 +187,11 @@ func (e *expander) expandEnviron(decrypt bool, nofail bool) error { } if parameter != nil { + // Ensure that this is a valid SSM parameter that we can actually resolve. + if !strings.HasPrefix(*parameter, "/") { + return fmt.Errorf("SSM parameters must have a leading '/' (ssm:///): %s", envvar) + } + uniqNames[*parameter] = true ssmVars = append(ssmVars, ssmVar{k, *parameter}) } diff --git a/main_test.go b/main_test.go index bd52d01..aba4bbd 100644 --- a/main_test.go +++ b/main_test.go @@ -45,14 +45,14 @@ func TestExpandEnviron_SimpleSSMParameter(t *testing.T) { batchSize: defaultBatchSize, } - os.Setenv("SUPER_SECRET", "ssm://secret") + os.Setenv("SUPER_SECRET", "ssm:///secret") c.On("GetParameters", &ssm.GetParametersInput{ - Names: []*string{aws.String("secret")}, + Names: []*string{aws.String("/secret")}, WithDecryption: aws.Bool(true), }).Return(&ssm.GetParametersOutput{ Parameters: []*ssm.Parameter{ - {Name: aws.String("secret"), Value: aws.String("hehe")}, + {Name: aws.String("/secret"), Value: aws.String("hehe")}, }, }, nil) @@ -80,14 +80,14 @@ func TestExpandEnviron_VersionedSSMParameter(t *testing.T) { batchSize: defaultBatchSize, } - os.Setenv("SUPER_SECRET", "ssm://secret:1") + os.Setenv("SUPER_SECRET", "ssm:///secret:1") c.On("GetParameters", &ssm.GetParametersInput{ - Names: []*string{aws.String("secret:1")}, + Names: []*string{aws.String("/secret:1")}, WithDecryption: aws.Bool(true), }).Return(&ssm.GetParametersOutput{ Parameters: []*ssm.Parameter{ - {Name: aws.String("secret"), Value: aws.String("versioned"), Selector: aws.String(":1")}, + {Name: aws.String("/secret"), Value: aws.String("versioned"), Selector: aws.String(":1")}, }, }, nil) @@ -109,20 +109,20 @@ func TestExpandEnviron_CustomTemplate(t *testing.T) { os := newFakeEnviron() c := new(mockSSM) e := expander{ - t: template.Must(parseTemplate(`{{ if eq .Name "SUPER_SECRET" }}secret{{end}}`)), + t: template.Must(parseTemplate(`{{ if eq .Name "SUPER_SECRET" }}/secret{{end}}`)), os: os, ssm: c, batchSize: defaultBatchSize, } - os.Setenv("SUPER_SECRET", "ssm://secret") + os.Setenv("SUPER_SECRET", "ssm:///secret") c.On("GetParameters", &ssm.GetParametersInput{ - Names: []*string{aws.String("secret")}, + Names: []*string{aws.String("/secret")}, WithDecryption: aws.Bool(true), }).Return(&ssm.GetParametersOutput{ Parameters: []*ssm.Parameter{ - {Name: aws.String("secret"), Value: aws.String("hehe")}, + {Name: aws.String("/secret"), Value: aws.String("hehe")}, }, }, nil) @@ -150,15 +150,15 @@ func TestExpandEnviron_DuplicateSSMParameter(t *testing.T) { batchSize: defaultBatchSize, } - os.Setenv("SUPER_SECRET_A", "ssm://secret") - os.Setenv("SUPER_SECRET_B", "ssm://secret") + os.Setenv("SUPER_SECRET_A", "ssm:///secret") + os.Setenv("SUPER_SECRET_B", "ssm:///secret") c.On("GetParameters", &ssm.GetParametersInput{ - Names: []*string{aws.String("secret")}, + Names: []*string{aws.String("/secret")}, WithDecryption: aws.Bool(false), }).Return(&ssm.GetParametersOutput{ Parameters: []*ssm.Parameter{ - {Name: aws.String("secret"), Value: aws.String("hehe")}, + {Name: aws.String("/secret"), Value: aws.String("hehe")}, }, }, nil) @@ -177,7 +177,7 @@ func TestExpandEnviron_DuplicateSSMParameter(t *testing.T) { c.AssertExpectations(t) } -func TestExpandEnviron_InvalidParameters(t *testing.T) { +func TestExpandEnviron_MalformedParameters(t *testing.T) { os := newFakeEnviron() c := new(mockSSM) e := expander{ @@ -189,17 +189,35 @@ func TestExpandEnviron_InvalidParameters(t *testing.T) { os.Setenv("SUPER_SECRET", "ssm://secret") + decrypt := false + nofail := false + err := e.expandEnviron(decrypt, nofail) + assert.Containsf(t, err.Error(), "SSM parameters must have a leading '/'", "") +} + +func TestExpandEnviron_InvalidParameters(t *testing.T) { + os := newFakeEnviron() + c := new(mockSSM) + e := expander{ + t: template.Must(parseTemplate(DefaultTemplate)), + os: os, + ssm: c, + batchSize: defaultBatchSize, + } + + os.Setenv("SUPER_SECRET", "ssm:///bad.secret") + c.On("GetParameters", &ssm.GetParametersInput{ - Names: []*string{aws.String("secret")}, + Names: []*string{aws.String("/bad.secret")}, WithDecryption: aws.Bool(false), }).Return(&ssm.GetParametersOutput{ - InvalidParameters: []*string{aws.String("secret")}, + InvalidParameters: []*string{aws.String("/bad.secret")}, }, nil) decrypt := false nofail := false err := e.expandEnviron(decrypt, nofail) - assert.Equal(t, &invalidParametersError{InvalidParameters: []string{"secret"}}, err) + assert.Equal(t, &invalidParametersError{InvalidParameters: []string{"/bad.secret"}}, err) c.AssertExpectations(t) } @@ -214,23 +232,23 @@ func TestExpandEnviron_InvalidParametersNoFail(t *testing.T) { batchSize: defaultBatchSize, } - os.Setenv("SUPER_SECRET", "ssm://secret") + os.Setenv("SUPER_SECRET", "ssm:///secret") c.On("GetParameters", &ssm.GetParametersInput{ - Names: []*string{aws.String("secret")}, + Names: []*string{aws.String("/secret")}, WithDecryption: aws.Bool(false), }).Return(&ssm.GetParametersOutput{ - InvalidParameters: []*string{aws.String("secret")}, + InvalidParameters: []*string{aws.String("/secret")}, }, nil) decrypt := false nofail := true err := e.expandEnviron(decrypt, nofail) - assert.NoError(t, err) + assert.NoError(t, err) assert.Equal(t, []string{ "SHELL=/bin/bash", - "SUPER_SECRET=ssm://secret", + "SUPER_SECRET=ssm:///secret", "TERM=screen-256color", }, os.Environ()) @@ -247,24 +265,24 @@ func TestExpandEnviron_BatchParameters(t *testing.T) { batchSize: 1, } - os.Setenv("SUPER_SECRET_A", "ssm://secret-a") - os.Setenv("SUPER_SECRET_B", "ssm://secret-b") + os.Setenv("SUPER_SECRET_A", "ssm:///secret-a") + os.Setenv("SUPER_SECRET_B", "ssm:///secret-b") c.On("GetParameters", &ssm.GetParametersInput{ - Names: []*string{aws.String("secret-a")}, + Names: []*string{aws.String("/secret-a")}, WithDecryption: aws.Bool(false), }).Return(&ssm.GetParametersOutput{ Parameters: []*ssm.Parameter{ - {Name: aws.String("secret-a"), Value: aws.String("val-a")}, + {Name: aws.String("/secret-a"), Value: aws.String("val-a")}, }, }, nil) c.On("GetParameters", &ssm.GetParametersInput{ - Names: []*string{aws.String("secret-b")}, + Names: []*string{aws.String("/secret-b")}, WithDecryption: aws.Bool(false), }).Return(&ssm.GetParametersOutput{ Parameters: []*ssm.Parameter{ - {Name: aws.String("secret-b"), Value: aws.String("val-b")}, + {Name: aws.String("/secret-b"), Value: aws.String("val-b")}, }, }, nil)