go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/cli/config/aws-ssm-ps.go (about) 1 // Copyright (c) Mondoo, Inc. 2 // SPDX-License-Identifier: BUSL-1.1 3 4 package config 5 6 import ( 7 "context" 8 "io" 9 "path" 10 "regexp" 11 "strings" 12 13 "github.com/aws/aws-sdk-go-v2/aws" 14 "github.com/aws/aws-sdk-go-v2/config" 15 "github.com/aws/aws-sdk-go-v2/service/ssm" 16 "github.com/cockroachdb/errors" 17 "github.com/rs/zerolog/log" 18 "github.com/spf13/viper" 19 ) 20 21 const AWS_SSM_PARAMETERSTORE_PREFIX = "aws-ssm-ps://" 22 23 // loads the configuration from aws ssm parameter store 24 func loadAwsSSMParameterStore(key string) error { 25 viper.RemoteConfig = &awsSSMParamConfigFactory{} 26 viper.SupportedRemoteProviders = []string{"aws-ssm-ps"} 27 ssmKey := strings.TrimPrefix(key, AWS_SSM_PARAMETERSTORE_PREFIX) 28 log.Info().Str("key", ssmKey).Msg("look for configuration stored in aws ssm parameter store") 29 err := viper.AddRemoteProvider("aws-ssm-ps", "localhost", ssmKey) 30 if err != nil { 31 return errors.Wrap(err, "could not initialize gs provider") 32 } 33 viper.SetConfigType("yaml") 34 err = viper.ReadRemoteConfig() 35 if err != nil { 36 return errors.Wrapf(err, "could not read aws ssm parameter config from %s", ssmKey) 37 } 38 39 return nil 40 } 41 42 type awsSSMParamConfigFactory struct{} 43 44 func (a *awsSSMParamConfigFactory) Get(rp viper.RemoteProvider) (io.Reader, error) { 45 ssmParameter, err := parseSsmParameterPath(rp.Path()) 46 if err != nil { 47 return nil, err 48 } 49 ctx := context.Background() 50 51 cfg, err := config.LoadDefaultConfig(ctx) 52 if err != nil { 53 return nil, err 54 } 55 56 cfg.Region = ssmParameter.Region 57 ps := ssm.NewFromConfig(cfg) 58 59 out, err := ps.GetParameter(ctx, &ssm.GetParameterInput{ 60 Name: aws.String(ssmParameter.Parameter), 61 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 62 }) 63 if err != nil { 64 return nil, err 65 } 66 67 return strings.NewReader(*out.Parameter.Value), nil 68 } 69 70 func (g *awsSSMParamConfigFactory) Watch(rp viper.RemoteProvider) (io.Reader, error) { 71 return nil, errors.New("not implemented") 72 } 73 74 func (g *awsSSMParamConfigFactory) WatchChannel(rp viper.RemoteProvider) (<-chan *viper.RemoteResponse, chan bool) { 75 return nil, nil 76 } 77 78 type SsmParameter struct { 79 Parameter string 80 Region string 81 // todo: add optional decrypt and account arguments 82 } 83 84 func newSsmParameter(region string, parameter string) (*SsmParameter, error) { 85 if region == "" || parameter == "" { 86 return nil, errors.New("invalid parameter. region and parameter name required.") 87 } 88 return &SsmParameter{Region: region, Parameter: parameter}, nil 89 } 90 91 func (s *SsmParameter) String() string { 92 // e.g. region/us-east-2/parameter/MondooAgentConfig 93 return path.Join("region", s.Region, "parameter", s.Parameter) 94 } 95 96 func parseSsmParameterPath(path string) (*SsmParameter, error) { 97 if !isValidSSMParameterPath(path) { 98 return nil, errors.New("invalid parameter path. expected region/<region-val>/parameter/<parameter-name>") 99 } 100 keyValues := strings.Split(path, "/") 101 if len(keyValues) != 4 { 102 return nil, errors.New("invalid parameter path. expected region/<region-val>/parameter/<parameter-name>") 103 } 104 return newSsmParameter(keyValues[1], keyValues[3]) 105 } 106 107 var VALID_SSM_PARAMETER_PATH = regexp.MustCompile(`^region\/(us(-gov)?|ap|ca|cn|eu|sa)-(central|(north|south)?(east|west)?)-\d\/parameter\/.+$`) 108 109 func isValidSSMParameterPath(path string) bool { 110 return VALID_SSM_PARAMETER_PATH.MatchString(path) 111 }