-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
⭐ cnquery can read config file from aws ssm parameter store
- Loading branch information
Showing
3 changed files
with
165 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
package config | ||
|
||
import ( | ||
"context" | ||
"io" | ||
"path" | ||
"regexp" | ||
"strings" | ||
|
||
"github.com/aws/aws-sdk-go-v2/aws" | ||
|
||
"github.com/aws/aws-sdk-go-v2/config" | ||
"github.com/aws/aws-sdk-go-v2/service/ssm" | ||
"github.com/cockroachdb/errors" | ||
"github.com/rs/zerolog/log" | ||
"github.com/spf13/viper" | ||
) | ||
|
||
const AWS_SSM_PARAMETERSTORE_PREFIX = "aws-ssm-ps://" | ||
|
||
// loads the configuration from aws ssm parameter store | ||
func loadAwsSSMParameterStore(key string) error { | ||
viper.RemoteConfig = &awsSSMParamConfigFactory{} | ||
viper.SupportedRemoteProviders = []string{"aws-ssm-ps"} | ||
ssmKey := strings.TrimPrefix(key, AWS_SSM_PARAMETERSTORE_PREFIX) | ||
log.Info().Str("key", ssmKey).Msg("look for configuration stored in aws ssm parameter store") | ||
err := viper.AddRemoteProvider("aws-ssm-ps", "localhost", ssmKey) | ||
if err != nil { | ||
return errors.Wrap(err, "could not initialize gs provider") | ||
} | ||
viper.SetConfigType("yaml") | ||
err = viper.ReadRemoteConfig() | ||
if err != nil { | ||
return errors.Wrapf(err, "could not read aws ssm parameter config from %s", ssmKey) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
type awsSSMParamConfigFactory struct{} | ||
|
||
func (a *awsSSMParamConfigFactory) Get(rp viper.RemoteProvider) (io.Reader, error) { | ||
ssmParameter, err := ParseSSMParameterPath(rp.Path()) | ||
if err != nil { | ||
return nil, err | ||
} | ||
ctx := context.Background() | ||
|
||
cfg, err := config.LoadDefaultConfig(ctx) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
cfg.Region = ssmParameter.Region | ||
ps := ssm.NewFromConfig(cfg) | ||
|
||
out, err := ps.GetParameter(ctx, &ssm.GetParameterInput{ | ||
Name: aws.String(ssmParameter.Parameter), | ||
WithDecryption: aws.Bool(true), // this field is ignored if the parameter is a string or stringlist, so it's ok to have it on by default | ||
}) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return strings.NewReader(*out.Parameter.Value), nil | ||
} | ||
|
||
func (g *awsSSMParamConfigFactory) Watch(rp viper.RemoteProvider) (io.Reader, error) { | ||
return nil, errors.New("not implemented") | ||
} | ||
|
||
func (g *awsSSMParamConfigFactory) WatchChannel(rp viper.RemoteProvider) (<-chan *viper.RemoteResponse, chan bool) { | ||
return nil, nil | ||
} | ||
|
||
type SsmParameter struct { | ||
Parameter string | ||
Region string | ||
// todo: add optional decrypt and account arguments | ||
} | ||
|
||
func NewSSMParameter(region string, parameter string) (*SsmParameter, error) { | ||
if region == "" || parameter == "" { | ||
return nil, errors.New("invalid parameter. region and parameter name required.") | ||
} | ||
return &SsmParameter{Region: region, Parameter: parameter}, nil | ||
} | ||
|
||
func (s *SsmParameter) String() string { | ||
// e.g. region/us-east-2/parameter/MondooAgentConfig | ||
return path.Join("region", s.Region, "parameter", s.Parameter) | ||
} | ||
|
||
func ParseSSMParameterPath(path string) (*SsmParameter, error) { | ||
if !IsValidSSMParameterPath(path) { | ||
return nil, errors.New("invalid parameter path. expected region/<region-val>/parameter/<parameter-name>") | ||
} | ||
keyValues := strings.Split(path, "/") | ||
if len(keyValues) != 4 { | ||
return nil, errors.New("invalid parameter path. expected region/<region-val>/parameter/<parameter-name>") | ||
} | ||
return NewSSMParameter(keyValues[1], keyValues[3]) | ||
} | ||
|
||
var VALID_SSM_PARAMETER_PATH = regexp.MustCompile(`^region\/(us(-gov)?|ap|ca|cn|eu|sa)-(central|(north|south)?(east|west)?)-\d\/parameter\/.+$`) | ||
|
||
func IsValidSSMParameterPath(path string) bool { | ||
return VALID_SSM_PARAMETER_PATH.MatchString(path) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package config | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
// aws-ssm-ps://region/us-east-2/parameter/MondooAgentConfig?decrypt=true&account=12345678 | ||
|
||
func TestNewSSMParameter(t *testing.T) { | ||
param, err := NewSSMParameter("us-west-1", "test-name") | ||
require.NoError(t, err) | ||
assert.Equal(t, &SsmParameter{Region: "us-west-1", Parameter: "test-name"}, param) | ||
assert.Equal(t, "region/us-west-1/parameter/test-name", param.String()) | ||
} | ||
|
||
func TestParseSSMParameterPath(t *testing.T) { | ||
ssmParam, err := ParseSSMParameterPath("region/us-west-2/parameter/test-param-name") | ||
require.NoError(t, err) | ||
assert.Equal(t, &SsmParameter{Parameter: "test-param-name", Region: "us-west-2"}, ssmParam) | ||
} | ||
|
||
func TestNewSSMParameterPathReturnsErrWhenNoRegion(t *testing.T) { | ||
_, err := NewSSMParameter("", "test-name") | ||
require.Error(t, err) | ||
assert.EqualError(t, err, "invalid parameter. region and parameter name required.") | ||
} | ||
|
||
func TestParseSSMParameterPathBadPathReturnsError(t *testing.T) { | ||
_, err := ParseSSMParameterPath("region/us-west-2/parameter") | ||
require.Error(t, err) | ||
assert.EqualError(t, err, "invalid parameter path. expected region/<region-val>/parameter/<parameter-name>") | ||
_, err = ParseSSMParameterPath("region//parameter/testname") | ||
require.Error(t, err) | ||
assert.EqualError(t, err, "invalid parameter path. expected region/<region-val>/parameter/<parameter-name>") | ||
_, err = ParseSSMParameterPath("region/us-west-1/parameter/") | ||
require.Error(t, err) | ||
assert.EqualError(t, err, "invalid parameter path. expected region/<region-val>/parameter/<parameter-name>") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters