From 410e539ef2514417bdeb6f7adfbe36f342715472 Mon Sep 17 00:00:00 2001 From: Mark van Holsteijn Date: Tue, 24 Mar 2020 12:26:35 +0100 Subject: [PATCH] added chmod feature --- README.md | 1 + main.go | 20 ++++++++++- test.sh | 105 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 125 insertions(+), 1 deletion(-) create mode 100755 test.sh diff --git a/README.md b/README.md index 5f6ee77..43431ec 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ The utility supports the following query parameters: - default - value if the value could not be retrieved from the parameter store. - destination - the filename to write the value to. value replaced with file: url. +- chmod - file permissions of the destination, left to default if not specified. recommended 0600. - template - the template to use for writing the value, defaults to '{{.}}' If no default nor destination is specified and the parameter is not found, the utility will return an error. diff --git a/main.go b/main.go index 71d4a04..b84a388 100644 --- a/main.go +++ b/main.go @@ -29,6 +29,7 @@ import ( "strings" "syscall" "text/template" + "strconv" ) var verbose bool @@ -54,6 +55,7 @@ type SSMParameterRef struct { parameter_name *string // in the parameter store default_value *string // if one is specified destination *string // to write the value to, otherwise "" + fileMode os.FileMode // file permissions template *template.Template // to use, defaults to '{{.}}' } @@ -85,8 +87,17 @@ func environmentToSSMParameterReferences(environ []string) ([]SSMParameterRef, e return nil, fmt.Errorf("environment variable %s has an invalid template syntax, %s", name, err) } } + var fileMode os.FileMode + chmod := values.Get("chmod") + if chmod != "" { + if mode, err := strconv.ParseUint(chmod, 8, 32); err != nil { + return nil, fmt.Errorf("chmod '%s' is not valid, %s", chmod, err) + } else { + fileMode = os.FileMode(mode) + } + } result = append(result, SSMParameterRef{&name, &uri.Path, - &defaultValue, &destination, tpl}) + &defaultValue, &destination, os.FileMode(fileMode), tpl}) } } return result, nil @@ -180,6 +191,13 @@ func writeParameterValues(refs []SSMParameterRef, env map[string]string) error { if err != nil { return fmt.Errorf("failed to close file %s, %s", *ref.destination, err) } + + if ref.fileMode != 0 { + err := os.Chmod(*ref.destination, ref.fileMode) + if err != nil { + return fmt.Errorf("failed to chmod file %s to %s, %s", *ref.destination, ref.fileMode, err) + } + } } } return nil diff --git a/test.sh b/test.sh new file mode 100755 index 0000000..5ec39b7 --- /dev/null +++ b/test.sh @@ -0,0 +1,105 @@ +#!/bin/bash + +export AWS_PROFILE=integration-test +export AWS_REGION=eu-central-1 + +function generate_password { + head /dev/urandom | LC_ALL=C tr -dc A-Za-z0-9 | head -c 20 +} + +function ssm_get_parameter { + go run main.go "$@" +} +function assert_equals { + if [[ $1 == $2 ]] ; then + echo "INFO: ${FUNCNAME[1]} ok" >&2 + else + echo "ERROR: ${FUNCNAME[1]} expected '$1' got '$2'" >&2 + fi +} + +function test_simple_get { + local result expect + expect=$(generate_password) + aws ssm put-parameter --name /mysql/root/password --value "$expect" --type SecureString --overwrite > /dev/null + result=$(ssm_get_parameter --name /mysql/root/password) + assert_equals $expect $result +} + +function test_get_via_env { + local result expect + expect=$(generate_password) + aws ssm put-parameter --name /mysql/root/password --value "$expect" --type SecureString --overwrite > /dev/null + result=$(MYSQL_PASSWORD=ssm:///mysql/root/password ssm_get_parameter bash -c 'echo $MYSQL_PASSWORD') + assert_equals $expect $result +} + +function test_get_via_env_default { + local result expect + expect=$(generate_password) + result=$(MYSQL_PASSWORD="ssm:///there-is-no-such-parameter-in-the-store-is-there?default=$expect" ssm_get_parameter bash -c 'echo $MYSQL_PASSWORD') + assert_equals $expect $result +} + +function test_template_format { + local result expect password + password=$(generate_password) + aws ssm put-parameter --name /postgres/kong/password --value "$password" --type SecureString --overwrite > /dev/null + expect="localhost:5432:kong:kong:${password}" + + result=$(TMP=/tmp \ + PGPASSFILE=ssm:///postgres/kong/password?template='localhost:5432:kong:kong:{{.}}%0A&destination=$TMP/.pgpass' \ + ssm_get_parameter bash -c 'cat $PGPASSFILE') + assert_equals $expect $result +} + +function test_env_substitution { + local result expect + expect=$(generate_password) + aws ssm put-parameter --name /$expect/mysql/root/password --value "$expect" --type SecureString --overwrite > /dev/null + result=$(ENV=$expect \ + PASSWORD='ssm:///${ENV}/mysql/root/password' \ + ssm_get_parameter bash -c 'echo $PASSWORD') + assert_equals $expect $result +} + +function test_destination { + local result expect filename + expect=$(generate_password) + filename=/tmp/password-$$ + aws ssm put-parameter --name /postgres/kong/password --value "$expect" --type SecureString --overwrite > /dev/null + + result=$(FILENAME=$filename \ + PASSWORD_FILE='ssm:///postgres/kong/password?destination=$FILENAME&chmod=0600' \ + ssm_get_parameter bash -c 'echo $PASSWORD_FILE') + assert_equals $filename $result + assert_equals $expect $(<$filename) + assert_equals 600 $(stat -f %A $filename) + rm $filename +} + +function test_destination_default { + local result expect filename + expect=$(generate_password) + filename=/tmp/password-$$ + echo -n "$expect" > $filename + result=$(FILENAME=$filename \ + PASSWORD_FILE='ssm:///there-is-no-such-parameter-in-the-store-is-there?destination=$FILENAME&chmod=0600' \ + ssm_get_parameter bash -c 'echo $PASSWORD_FILE') + assert_equals $filename $result + assert_equals $expect $(<$filename) + assert_equals 600 $(stat -f %A $filename) + rm $filename +} + +function main { + test_simple_get + test_get_via_env + test_get_via_env_default + test_destination + test_destination_default + test_env_substitution + test_template_format +} + +main