github.com/in4it/ecs-deploy@v0.0.42-0.20240508120354-ed77ff16df25/provider/ecs/paramstore.go (about) 1 package ecs 2 3 import ( 4 "errors" 5 "os" 6 "strings" 7 8 "github.com/aws/aws-sdk-go/aws" 9 "github.com/aws/aws-sdk-go/aws/awserr" 10 "github.com/aws/aws-sdk-go/aws/session" 11 "github.com/aws/aws-sdk-go/service/ssm" 12 "github.com/in4it/ecs-deploy/service" 13 "github.com/in4it/ecs-deploy/util" 14 "github.com/juju/loggo" 15 ) 16 17 // logging 18 var paramstoreLogger = loggo.GetLogger("paramstore") 19 20 // parameter type 21 type Parameter struct { 22 Arn string `json:"arn"` 23 Name string `json:"name"` 24 Type string `json:"type"` 25 Value string `json:"value"` 26 Version int64 `json:"version"` 27 } 28 29 // Paramstore struct 30 type Paramstore struct { 31 Parameters map[string]Parameter 32 SsmAssumingRole *ssm.SSM 33 } 34 35 func (p *Paramstore) IsEnabled() bool { 36 if util.GetEnv("PARAMSTORE_ENABLED", "no") == "yes" { 37 return true 38 } else { 39 return false 40 } 41 } 42 43 func (p *Paramstore) GetPrefix() string { 44 if util.GetEnv("AWS_ENV_PATH", "") != "" { 45 return util.GetEnv("AWS_ENV_PATH", "") 46 } else { 47 if util.GetEnv("PARAMSTORE_PREFIX", "") == "" { 48 return "" 49 } else { 50 return "/" + util.GetEnv("PARAMSTORE_PREFIX", "") + "-" + util.GetEnv("AWS_ACCOUNT_ENV", "") + "/ecs-deploy/" 51 } 52 } 53 } 54 func (p *Paramstore) GetPrefixForService(serviceName string) string { 55 if util.GetEnv("PARAMSTORE_PREFIX", "") == "" { 56 return "" 57 } else { 58 return "/" + util.GetEnv("PARAMSTORE_PREFIX", "") + "-" + util.GetEnv("AWS_ACCOUNT_ENV", "") + "/" + serviceName + "/" 59 } 60 } 61 func (p *Paramstore) AssumeRole(roleArn, roleSessionName, prevCreds string) (string, error) { 62 iam := IAM{} 63 creds, jsonCreds, err := iam.AssumeRole(roleArn, roleSessionName, prevCreds) 64 if err != nil { 65 return "", err 66 } 67 // assume role 68 sess := session.Must(session.NewSession()) 69 p.SsmAssumingRole = ssm.New(sess, &aws.Config{Credentials: creds}) 70 if p.SsmAssumingRole == nil { 71 return "", errors.New("Could not assume role") 72 } 73 paramstoreLogger.Debugf("Assumed role %v with roleSessionName %v", roleArn, roleSessionName) 74 75 return jsonCreds, nil 76 } 77 func (p *Paramstore) GetParameters(prefix string, withDecryption bool) error { 78 var svc *ssm.SSM 79 p.Parameters = make(map[string]Parameter) 80 if prefix == "" { 81 // no valid prefix - parameter store not in use 82 return nil 83 } 84 if p.SsmAssumingRole == nil { 85 svc = ssm.New(session.New()) 86 } else { 87 svc = p.SsmAssumingRole 88 } 89 input := &ssm.GetParametersByPathInput{ 90 Path: aws.String(prefix), 91 WithDecryption: aws.Bool(withDecryption), 92 } 93 94 pageNum := 0 95 err := svc.GetParametersByPathPages(input, 96 func(page *ssm.GetParametersByPathOutput, lastPage bool) bool { 97 pageNum++ 98 for _, param := range page.Parameters { 99 var value string 100 paramName := strings.Replace(*param.Name, prefix, "", -1) 101 paramstoreLogger.Debugf("Read parameter: %v", paramName) 102 if withDecryption || *param.Type != "SecureString" { 103 value = *param.Value 104 } else { 105 value = "***" 106 } 107 p.Parameters[paramName] = Parameter{ 108 Arn: aws.StringValue(param.ARN), 109 Name: aws.StringValue(param.Name), 110 Type: aws.StringValue(param.Type), 111 Value: value, 112 Version: aws.Int64Value(param.Version), 113 } 114 } 115 return pageNum <= 50 // 50 iterations max 116 }) 117 118 if err != nil { 119 if aerr, ok := err.(awserr.Error); ok { 120 paramstoreLogger.Errorf(aerr.Error()) 121 } else { 122 paramstoreLogger.Errorf(err.Error()) 123 } 124 return err 125 } 126 return nil 127 } 128 func (p *Paramstore) GetParameterValue(name string) (*string, error) { 129 if param, ok := p.Parameters[name]; ok { 130 if param.Value != "" { 131 return ¶m.Value, nil 132 } 133 } else { 134 return nil, errors.New("Tried getParameterValue on parameter that doesn't exist: " + name) 135 } 136 137 // val not found, but does exist, retrieve 138 svc := ssm.New(session.New()) 139 input := &ssm.GetParameterInput{ 140 Name: aws.String(p.GetPrefix() + name), 141 WithDecryption: aws.Bool(true), 142 } 143 144 result, err := svc.GetParameter(input) 145 if err != nil { 146 if aerr, ok := err.(awserr.Error); ok { 147 switch aerr.Code() { 148 case ssm.ErrCodeParameterNotFound: 149 paramstoreLogger.Errorf("%v: %v", ssm.ErrCodeParameterNotFound, aerr.Error()) 150 return nil, errors.New("ParameterNotFound") 151 default: 152 paramstoreLogger.Errorf(aerr.Error()) 153 } 154 } else { 155 paramstoreLogger.Errorf(err.Error()) 156 } 157 return nil, err 158 } 159 return result.Parameter.Value, nil 160 } 161 162 func (p *Paramstore) GetParamstoreIAMPolicy(path string) string { 163 iam := IAM{} 164 err := iam.GetAccountId() 165 accountId := iam.AccountId 166 if err != nil { 167 accountId = "" 168 } 169 policy := `{ 170 "Version": "2012-10-17", 171 "Statement": [ 172 { 173 "Action": [ 174 "ssm:GetParameterHistory", 175 "ssm:GetParameter", 176 "ssm:GetParameters", 177 "ssm:GetParametersByPath" 178 ], 179 "Resource": [ 180 "arn:aws:ssm:` + util.GetEnv("AWS_REGION", "") + `:` + accountId + `:parameter/` + util.GetEnv("PARAMSTORE_PREFIX", "") + `-` + util.GetEnv("AWS_ACCOUNT_ENV", "") + `/` + path + `/*" 181 ], 182 "Effect": "Allow" 183 }, 184 { 185 "Action": [ 186 "kms:Decrypt" 187 ], 188 "Resource": [ 189 "` + util.GetEnv("PARAMSTORE_KMS_ARN", "") + `" 190 ], 191 "Effect": "Allow" 192 } 193 ] 194 }` 195 return policy 196 } 197 func (p *Paramstore) PutParameter(serviceName string, parameter service.DeployServiceParameter) (*int64, error) { 198 var svc *ssm.SSM 199 if p.SsmAssumingRole == nil { 200 svc = ssm.New(session.New()) 201 } else { 202 svc = p.SsmAssumingRole 203 } 204 205 input := &ssm.PutParameterInput{ 206 Name: aws.String(p.GetPrefixForService(serviceName) + parameter.Name), 207 Value: aws.String(parameter.Value), 208 Overwrite: aws.Bool(true), 209 } 210 if parameter.Encrypted { 211 input.SetType("SecureString") 212 input.SetKeyId(util.GetEnv("PARAMSTORE_KMS_ARN", "")) 213 } else { 214 input.SetType("String") 215 } 216 217 result, err := svc.PutParameter(input) 218 if err != nil { 219 if aerr, ok := err.(awserr.Error); ok { 220 paramstoreLogger.Errorf(aerr.Error()) 221 } else { 222 paramstoreLogger.Errorf(err.Error()) 223 } 224 return nil, err 225 } 226 return result.Version, nil 227 } 228 func (p *Paramstore) DeleteParameter(serviceName, parameter string) error { 229 var svc *ssm.SSM 230 if p.SsmAssumingRole == nil { 231 svc = ssm.New(session.New()) 232 } else { 233 svc = p.SsmAssumingRole 234 } 235 236 input := &ssm.DeleteParameterInput{ 237 Name: aws.String(p.GetPrefixForService(serviceName) + parameter), 238 } 239 240 _, err := svc.DeleteParameter(input) 241 if err != nil { 242 if aerr, ok := err.(awserr.Error); ok { 243 paramstoreLogger.Errorf(aerr.Error()) 244 } else { 245 paramstoreLogger.Errorf(err.Error()) 246 } 247 return err 248 } 249 return nil 250 } 251 252 func (p *Paramstore) Bootstrap(serviceName, prefix string, environment string, parameters []service.DeployServiceParameter) error { 253 os.Setenv("PARAMSTORE_PREFIX", prefix) 254 os.Setenv("AWS_ACCOUNT_ENV", environment) 255 for _, v := range parameters { 256 p.PutParameter(serviceName, v) 257 } 258 return nil 259 } 260 261 func (p *Paramstore) RetrieveKeys() error { 262 if p.IsEnabled() { 263 err := p.GetParameters(p.GetPrefix(), true) 264 if err != nil { 265 return err 266 } 267 for k, v := range p.Parameters { 268 os.Setenv(k, v.Value) 269 } 270 } 271 return nil 272 }