github.com/mweagle/Sparta@v1.15.0/lambda_permissions.go (about) 1 package sparta 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "strings" 7 8 "github.com/aws/aws-sdk-go/service/s3" 9 spartaCF "github.com/mweagle/Sparta/aws/cloudformation" 10 cfCustomResources "github.com/mweagle/Sparta/aws/cloudformation/resources" 11 gocf "github.com/mweagle/go-cloudformation" 12 "github.com/pkg/errors" 13 "github.com/sirupsen/logrus" 14 ) 15 16 //////////////////////////////////////////////////////////////////////////////// 17 // Types to handle permissions & push source configuration 18 type descriptionNode struct { 19 Name string 20 Relation string 21 Color string 22 } 23 24 // LambdaPermissionExporter defines an interface for polymorphic collection of 25 // Permission entries that support specialization for additional resource generation. 26 type LambdaPermissionExporter interface { 27 // Export the permission object to a set of CloudFormation resources 28 // in the provided resources param. The targetLambdaFuncRef 29 // interface represents the Fn::GetAtt "Arn" JSON value 30 // of the parent Lambda target 31 export(serviceName string, 32 lambdaFunctionDisplayName string, 33 lambdaLogicalCFResourceName string, 34 template *gocf.Template, 35 S3Bucket string, 36 S3Key string, 37 logger *logrus.Logger) (string, error) 38 // Return a `describe` compatible output for the given permission. Return 39 // value is a list of tuples for node, edgeLabel 40 descriptionInfo() ([]descriptionNode, error) 41 } 42 43 //////////////////////////////////////////////////////////////////////////////// 44 // START - BasePermission 45 // 46 47 // BasePermission (http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-permission.html) 48 // type for common AWS Lambda permission data. 49 type BasePermission struct { 50 // The AWS account ID (without hyphens) of the source owner 51 SourceAccount string `json:"SourceAccount,omitempty"` 52 // The ARN of a resource that is invoking your function. 53 SourceArn interface{} `json:"SourceArn,omitempty"` 54 } 55 56 func (perm *BasePermission) sourceArnExpr(joinParts ...gocf.Stringable) *gocf.StringExpr { 57 if perm.SourceArn == nil { 58 return nil 59 } 60 stringARN, stringARNOk := perm.SourceArn.(string) 61 if stringARNOk && strings.Contains(stringARN, "arn:aws:") { 62 return gocf.String(stringARN) 63 } 64 65 var parts []gocf.Stringable 66 if nil != joinParts { 67 parts = append(parts, joinParts...) 68 } 69 parts = append(parts, 70 spartaCF.DynamicValueToStringExpr(perm.SourceArn), 71 ) 72 return gocf.Join("", parts...) 73 } 74 75 func (perm BasePermission) export(principal *gocf.StringExpr, 76 arnPrefixParts []gocf.Stringable, 77 lambdaFunctionDisplayName string, 78 lambdaLogicalCFResourceName string, 79 template *gocf.Template, 80 S3Bucket string, 81 S3Key string, 82 logger *logrus.Logger) (string, error) { 83 84 lambdaPermission := gocf.LambdaPermission{ 85 Action: gocf.String("lambda:InvokeFunction"), 86 FunctionName: gocf.GetAtt(lambdaLogicalCFResourceName, "Arn"), 87 Principal: principal, 88 } 89 // If the Arn isn't the wildcard value, then include it. 90 if nil != perm.SourceArn { 91 switch typedARN := perm.SourceArn.(type) { 92 case string: 93 // Don't be smart if the Arn value is a user supplied literal 94 if typedARN != "*" { 95 lambdaPermission.SourceArn = gocf.String(typedARN) 96 } 97 default: 98 lambdaPermission.SourceArn = perm.sourceArnExpr(arnPrefixParts...) 99 } 100 } 101 102 if perm.SourceAccount != "" { 103 lambdaPermission.SourceAccount = gocf.String(perm.SourceAccount) 104 } 105 106 arnLiteral, arnLiteralErr := json.Marshal(lambdaPermission.SourceArn) 107 if nil != arnLiteralErr { 108 return "", arnLiteralErr 109 } 110 resourceName := CloudFormationResourceName("LambdaPerm%s", 111 principal.Literal, 112 string(arnLiteral), 113 lambdaLogicalCFResourceName) 114 template.AddResource(resourceName, lambdaPermission) 115 return resourceName, nil 116 } 117 118 // 119 // END - BasePermission 120 //////////////////////////////////////////////////////////////////////////////// 121 122 //////////////////////////////////////////////////////////////////////////////// 123 // START - S3Permission 124 // 125 var s3SourceArnParts = []gocf.Stringable{ 126 gocf.String("arn:aws:s3:::"), 127 } 128 129 // S3Permission struct implies that the S3 BasePermission.SourceArn should be 130 // updated (via PutBucketNotificationConfiguration) to automatically push 131 // events to the owning Lambda. 132 // See http://docs.aws.amazon.com/lambda/latest/dg/intro-core-components.html#intro-core-components-event-sources 133 // for more information. 134 type S3Permission struct { 135 BasePermission 136 // S3 events to register for (eg: `[]string{s3:GetObjectObjectCreated:*", "s3:ObjectRemoved:*"}`). 137 Events []string `json:"Events,omitempty"` 138 // S3.NotificationConfigurationFilter 139 // to scope event forwarding. See 140 // http://docs.aws.amazon.com/AmazonS3/latest/dev/NotificationHowTo.html 141 // for more information. 142 Filter s3.NotificationConfigurationFilter `json:"Filter,omitempty"` 143 } 144 145 func (perm S3Permission) export(serviceName string, 146 lambdaFunctionDisplayName string, 147 lambdaLogicalCFResourceName string, 148 template *gocf.Template, 149 S3Bucket string, 150 S3Key string, 151 logger *logrus.Logger) (string, error) { 152 153 targetLambdaResourceName, err := perm.BasePermission.export(gocf.String("s3.amazonaws.com"), 154 s3SourceArnParts, 155 lambdaFunctionDisplayName, 156 lambdaLogicalCFResourceName, 157 template, 158 S3Bucket, 159 S3Key, 160 logger) 161 162 if nil != err { 163 return "", errors.Wrap(err, "Failed to export S3 permission") 164 } 165 166 // Make sure the custom lambda that manages s3 notifications is provisioned. 167 sourceArnExpression := perm.BasePermission.sourceArnExpr(s3SourceArnParts...) 168 configuratorResName, err := EnsureCustomResourceHandler(serviceName, 169 cfCustomResources.S3LambdaEventSource, 170 sourceArnExpression, 171 []string{}, 172 template, 173 S3Bucket, 174 S3Key, 175 logger) 176 177 if nil != err { 178 return "", errors.Wrap(err, "Exporting S3 permission") 179 } 180 181 // Add a custom resource invocation for this configuration 182 ////////////////////////////////////////////////////////////////////////////// 183 newResource, newResourceError := newCloudFormationResource(cfCustomResources.S3LambdaEventSource, 184 logger) 185 if nil != newResourceError { 186 return "", newResourceError 187 } 188 // Setup the reqest for the S3 action 189 s3Resource, s3ResourceOK := newResource.(*cfCustomResources.S3LambdaEventSourceResource) 190 if !s3ResourceOK { 191 return "", fmt.Errorf("failed to access typed S3CustomResource") 192 } 193 s3Resource.ServiceToken = gocf.GetAtt(configuratorResName, "Arn") 194 s3Resource.BucketArn = sourceArnExpression 195 s3Resource.LambdaTargetArn = gocf.GetAtt(lambdaLogicalCFResourceName, "Arn") 196 s3Resource.Events = perm.Events 197 if nil != perm.Filter.Key { 198 s3Resource.Filter = &perm.Filter 199 } 200 201 // Name? 202 resourceInvokerName := CloudFormationResourceName("ConfigS3", 203 lambdaLogicalCFResourceName, 204 perm.BasePermission.SourceAccount, 205 fmt.Sprintf("%#v", s3Resource.Filter)) 206 207 // Add it 208 cfResource := template.AddResource(resourceInvokerName, s3Resource) 209 cfResource.DependsOn = append(cfResource.DependsOn, 210 targetLambdaResourceName, 211 configuratorResName) 212 return "", nil 213 } 214 215 func (perm S3Permission) descriptionInfo() ([]descriptionNode, error) { 216 s3Events := "" 217 for _, eachEvent := range perm.Events { 218 s3Events = fmt.Sprintf("%s\n%s", eachEvent, s3Events) 219 } 220 nodes := make([]descriptionNode, 0) 221 if perm.Filter.Key == nil || len(perm.Filter.Key.FilterRules) == 0 { 222 nodes = append(nodes, descriptionNode{ 223 Name: describeInfoValue(perm.SourceArn), 224 Relation: s3Events, 225 }) 226 } else { 227 for _, eachFilter := range perm.Filter.Key.FilterRules { 228 filterRel := fmt.Sprintf("%s (%s = %s)", 229 s3Events, 230 *eachFilter.Name, 231 *eachFilter.Value) 232 nodes = append(nodes, descriptionNode{ 233 Name: describeInfoValue(perm.SourceArn), 234 Relation: filterRel, 235 }) 236 } 237 } 238 239 return nodes, nil 240 } 241 242 // END - S3Permission 243 /////////////////////////////////////////////////////////////////////////////////// 244 245 //////////////////////////////////////////////////////////////////////////////// 246 // SNSPermission - START 247 var snsSourceArnParts = []gocf.Stringable{} 248 249 // SNSPermission struct implies that the BasePermisison.SourceArn should be 250 // configured for subscriptions as part of this stacks provisioning. 251 // See http://docs.aws.amazon.com/lambda/latest/dg/intro-core-components.html#intro-core-components-event-sources 252 // for more information. 253 type SNSPermission struct { 254 BasePermission 255 } 256 257 func (perm SNSPermission) export(serviceName string, 258 lambdaFunctionDisplayName string, 259 lambdaLogicalCFResourceName string, 260 template *gocf.Template, 261 S3Bucket string, 262 S3Key string, 263 logger *logrus.Logger) (string, error) { 264 sourceArnExpression := perm.BasePermission.sourceArnExpr(snsSourceArnParts...) 265 266 targetLambdaResourceName, err := perm.BasePermission.export(gocf.String(SNSPrincipal), 267 snsSourceArnParts, 268 lambdaFunctionDisplayName, 269 lambdaLogicalCFResourceName, 270 template, 271 S3Bucket, 272 S3Key, 273 logger) 274 if nil != err { 275 return "", errors.Wrap(err, "Failed to export SNS permission") 276 } 277 278 // Make sure the custom lambda that manages s3 notifications is provisioned. 279 configuratorResName, err := EnsureCustomResourceHandler(serviceName, 280 cfCustomResources.SNSLambdaEventSource, 281 sourceArnExpression, 282 []string{}, 283 template, 284 S3Bucket, 285 S3Key, 286 logger) 287 288 if nil != err { 289 return "", errors.Wrap(err, "Exporing SNS permission handler") 290 } 291 292 // Add a custom resource invocation for this configuration 293 ////////////////////////////////////////////////////////////////////////////// 294 newResource, newResourceError := newCloudFormationResource(cfCustomResources.SNSLambdaEventSource, 295 logger) 296 if nil != newResourceError { 297 return "", newResourceError 298 } 299 customResource := newResource.(*cfCustomResources.SNSLambdaEventSourceResource) 300 customResource.ServiceToken = gocf.GetAtt(configuratorResName, "Arn") 301 customResource.LambdaTargetArn = gocf.GetAtt(lambdaLogicalCFResourceName, "Arn") 302 customResource.SNSTopicArn = sourceArnExpression 303 304 // Name? 305 resourceInvokerName := CloudFormationResourceName("ConfigSNS", 306 lambdaLogicalCFResourceName, 307 perm.BasePermission.SourceAccount) 308 309 // Add it 310 cfResource := template.AddResource(resourceInvokerName, customResource) 311 cfResource.DependsOn = append(cfResource.DependsOn, 312 targetLambdaResourceName, 313 configuratorResName) 314 return "", nil 315 } 316 317 func (perm SNSPermission) descriptionInfo() ([]descriptionNode, error) { 318 nodes := []descriptionNode{ 319 { 320 Name: describeInfoValue(perm.SourceArn), 321 Relation: "", 322 }, 323 } 324 return nodes, nil 325 } 326 327 // 328 // END - SNSPermission 329 //////////////////////////////////////////////////////////////////////////////// 330 331 //////////////////////////////////////////////////////////////////////////////// 332 // MessageBodyStorageOptions - START 333 334 // MessageBodyStorageOptions define additional options for storing SES 335 // message body content. By default, all rules associated with the owning 336 // SESPermission object will store message bodies if the MessageBodyStorage 337 // field is non-nil. Message bodies are by default prefixed with 338 // `ServiceName/RuleName/`, which can be overridden by specifying a non-empty 339 // ObjectKeyPrefix value. A rule can opt-out of message body storage 340 // with the DisableStorage field. See 341 // http://docs.aws.amazon.com/ses/latest/DeveloperGuide/receiving-email-action-s3.html 342 // for additional field documentation. 343 // The message body is saved as MIME (https://tools.ietf.org/html/rfc2045) 344 type MessageBodyStorageOptions struct { 345 ObjectKeyPrefix string 346 KmsKeyArn string 347 TopicArn string 348 DisableStorage bool 349 } 350 351 // 352 // END - MessageBodyStorageOptions 353 //////////////////////////////////////////////////////////////////////////////// 354 355 //////////////////////////////////////////////////////////////////////////////// 356 // MessageBodyStorage - START 357 358 // MessageBodyStorage represents either a new S3 bucket or an existing S3 bucket 359 // to which SES message bodies should be stored. 360 // NOTE: New MessageBodyStorage create S3 buckets which will be orphaned after your 361 // service is deleted. 362 type MessageBodyStorage struct { 363 logicalBucketName string 364 bucketNameExpr *gocf.StringExpr 365 cloudFormationS3BucketResourceName string 366 } 367 368 // BucketArn returns an Arn value that can be used as an 369 // lambdaFn.RoleDefinition.Privileges `Resource` value. 370 func (storage *MessageBodyStorage) BucketArn() *gocf.StringExpr { 371 return gocf.Join("", 372 gocf.String("arn:aws:s3:::"), 373 storage.bucketNameExpr) 374 } 375 376 // BucketArnAllKeys returns an Arn value that can be used 377 // lambdaFn.RoleDefinition.Privileges `Resource` value. It includes 378 // the trailing `/*` wildcard to support item acccess 379 func (storage *MessageBodyStorage) BucketArnAllKeys() *gocf.StringExpr { 380 return gocf.Join("", 381 gocf.String("arn:aws:s3:::"), 382 storage.bucketNameExpr, 383 gocf.String("/*")) 384 } 385 386 func (storage *MessageBodyStorage) export(serviceName string, 387 lambdaFunctionDisplayName string, 388 lambdaLogicalCFResourceName string, 389 template *gocf.Template, 390 S3Bucket string, 391 S3Key string, 392 logger *logrus.Logger) (string, error) { 393 394 if storage.cloudFormationS3BucketResourceName != "" { 395 s3Bucket := &gocf.S3Bucket{ 396 Tags: &gocf.TagList{ 397 gocf.Tag{ 398 Key: gocf.String("sparta:logicalBucketName"), 399 Value: gocf.String(storage.logicalBucketName), 400 }, 401 }, 402 } 403 cfResource := template.AddResource(storage.cloudFormationS3BucketResourceName, s3Bucket) 404 cfResource.DeletionPolicy = "Retain" 405 406 lambdaResource, lambdaResourceExists := template.Resources[lambdaLogicalCFResourceName] 407 if !lambdaResourceExists { 408 safeAppendDependency(lambdaResource, storage.cloudFormationS3BucketResourceName) 409 } 410 411 logger.WithFields(logrus.Fields{ 412 "LogicalResourceName": storage.cloudFormationS3BucketResourceName, 413 }).Info("Service will orphan S3 Bucket on deletion") 414 415 // Save the output 416 template.Outputs[storage.cloudFormationS3BucketResourceName] = &gocf.Output{ 417 Description: "SES Message Body Bucket", 418 Value: gocf.Ref(storage.cloudFormationS3BucketResourceName), 419 } 420 } 421 // Add the S3 Access policy 422 s3BodyStoragePolicy := &gocf.S3BucketPolicy{ 423 Bucket: storage.bucketNameExpr, 424 PolicyDocument: ArbitraryJSONObject{ 425 "Version": "2012-10-17", 426 "Statement": []ArbitraryJSONObject{ 427 { 428 "Sid": "PermitSESServiceToSaveEmailBody", 429 "Effect": "Allow", 430 "Principal": ArbitraryJSONObject{ 431 "Service": "ses.amazonaws.com", 432 }, 433 "Action": []string{"s3:PutObjectAcl", "s3:PutObject"}, 434 "Resource": gocf.Join("", 435 gocf.String("arn:aws:s3:::"), 436 storage.bucketNameExpr, 437 gocf.String("/*")), 438 "Condition": ArbitraryJSONObject{ 439 "StringEquals": ArbitraryJSONObject{ 440 "aws:Referer": gocf.Ref("AWS::AccountId"), 441 }, 442 }, 443 }, 444 }, 445 }, 446 } 447 448 s3BucketPolicyResourceName := CloudFormationResourceName("SESMessageBodyBucketPolicy", 449 fmt.Sprintf("%#v", storage.bucketNameExpr)) 450 template.AddResource(s3BucketPolicyResourceName, s3BodyStoragePolicy) 451 452 // Return the name of the bucket policy s.t. the configurator resource 453 // is properly sequenced. The configurator will fail iff the Bucket Policies aren't 454 // applied b/c the SES Rule Actions check PutObject access to S3 buckets 455 return s3BucketPolicyResourceName, nil 456 } 457 458 // Return a function that 459 460 // 461 // END - MessageBodyStorage 462 //////////////////////////////////////////////////////////////////////////////// 463 464 //////////////////////////////////////////////////////////////////////////////// 465 // ReceiptRule - START 466 467 // ReceiptRule represents an SES ReceiptRule 468 // (http://docs.aws.amazon.com/ses/latest/DeveloperGuide/receiving-email-receipt-rules.html) 469 // value. To store message bodies, provide a non-nil MessageBodyStorage value 470 // to the owning SESPermission object 471 type ReceiptRule struct { 472 Name string 473 Disabled bool 474 Recipients []string 475 ScanDisabled bool 476 TLSPolicy string 477 TopicArn string 478 InvocationType string 479 BodyStorageOptions MessageBodyStorageOptions 480 } 481 482 func (rule *ReceiptRule) toResourceRule(serviceName string, 483 functionArnRef interface{}, 484 messageBodyStorage *MessageBodyStorage) *cfCustomResources.SESLambdaEventSourceResourceRule { 485 486 resourceRule := &cfCustomResources.SESLambdaEventSourceResourceRule{ 487 Name: gocf.String(rule.Name), 488 ScanEnabled: gocf.Bool(!rule.ScanDisabled), 489 Enabled: gocf.Bool(!rule.Disabled), 490 Actions: make([]*cfCustomResources.SESLambdaEventSourceResourceAction, 0), 491 Recipients: make([]*gocf.StringExpr, 0), 492 } 493 for _, eachRecipient := range rule.Recipients { 494 resourceRule.Recipients = append(resourceRule.Recipients, gocf.String(eachRecipient)) 495 } 496 if rule.TLSPolicy != "" { 497 resourceRule.TLSPolicy = gocf.String(rule.TLSPolicy) 498 } 499 500 // If there is a MessageBodyStorage reference, push that S3Action 501 // to the head of the Actions list 502 if nil != messageBodyStorage && !rule.BodyStorageOptions.DisableStorage { 503 s3Action := &cfCustomResources.SESLambdaEventSourceResourceAction{ 504 ActionType: gocf.String("S3Action"), 505 ActionProperties: map[string]interface{}{ 506 "BucketName": messageBodyStorage.bucketNameExpr, 507 }, 508 } 509 if rule.BodyStorageOptions.ObjectKeyPrefix != "" { 510 s3Action.ActionProperties["ObjectKeyPrefix"] = rule.BodyStorageOptions.ObjectKeyPrefix 511 } 512 if rule.BodyStorageOptions.KmsKeyArn != "" { 513 s3Action.ActionProperties["KmsKeyArn"] = rule.BodyStorageOptions.KmsKeyArn 514 } 515 if rule.BodyStorageOptions.TopicArn != "" { 516 s3Action.ActionProperties["TopicArn"] = rule.BodyStorageOptions.TopicArn 517 } 518 resourceRule.Actions = append(resourceRule.Actions, s3Action) 519 } 520 // There's always a lambda action 521 lambdaAction := &cfCustomResources.SESLambdaEventSourceResourceAction{ 522 ActionType: gocf.String("LambdaAction"), 523 ActionProperties: map[string]interface{}{ 524 "FunctionArn": functionArnRef, 525 }, 526 } 527 lambdaAction.ActionProperties["InvocationType"] = rule.InvocationType 528 if rule.InvocationType == "" { 529 lambdaAction.ActionProperties["InvocationType"] = "Event" 530 } 531 if rule.TopicArn != "" { 532 lambdaAction.ActionProperties["TopicArn"] = rule.TopicArn 533 } 534 resourceRule.Actions = append(resourceRule.Actions, lambdaAction) 535 return resourceRule 536 } 537 538 // 539 // END - ReceiptRule 540 //////////////////////////////////////////////////////////////////////////////// 541 542 //////////////////////////////////////////////////////////////////////////////// 543 // SESPermission - START 544 545 // SES doesn't use ARNs to scope access 546 var sesSourcePartArn = []gocf.Stringable{wildcardArn} 547 548 // SESPermission struct implies that the SES verified domain should be 549 // updated (via createReceiptRule) to automatically request or push events 550 // to the parent lambda 551 // See http://docs.aws.amazon.com/lambda/latest/dg/intro-core-components.html#intro-core-components-event-sources 552 // for more information. See http://docs.aws.amazon.com/ses/latest/DeveloperGuide/receiving-email-concepts.html 553 // for setting up email receiving. 554 type SESPermission struct { 555 BasePermission 556 InvocationType string /* RequestResponse, Event */ 557 ReceiptRules []ReceiptRule 558 MessageBodyStorage *MessageBodyStorage 559 } 560 561 // NewMessageBodyStorageResource provisions a new S3 bucket to store message body 562 // content. 563 func (perm *SESPermission) NewMessageBodyStorageResource(bucketLogicalName string) (*MessageBodyStorage, error) { 564 if len(bucketLogicalName) <= 0 { 565 return nil, errors.New("NewMessageBodyStorageResource requires a unique, non-empty `bucketLogicalName` parameter ") 566 } 567 store := &MessageBodyStorage{ 568 logicalBucketName: bucketLogicalName, 569 } 570 store.cloudFormationS3BucketResourceName = CloudFormationResourceName("SESMessageStoreBucket", bucketLogicalName) 571 store.bucketNameExpr = gocf.Ref(store.cloudFormationS3BucketResourceName).String() 572 return store, nil 573 } 574 575 // NewMessageBodyStorageReference uses a pre-existing S3 bucket for MessageBody storage. 576 // Sparta assumes that prexistingBucketName exists and will add an S3::BucketPolicy 577 // to enable SES PutObject access. 578 func (perm *SESPermission) NewMessageBodyStorageReference(prexistingBucketName string) (*MessageBodyStorage, error) { 579 store := &MessageBodyStorage{} 580 store.bucketNameExpr = gocf.String(prexistingBucketName) 581 return store, nil 582 } 583 584 func (perm SESPermission) export(serviceName string, 585 lambdaFunctionDisplayName string, 586 lambdaLogicalCFResourceName string, 587 template *gocf.Template, 588 S3Bucket string, 589 S3Key string, 590 logger *logrus.Logger) (string, error) { 591 592 sourceArnExpression := perm.BasePermission.sourceArnExpr(snsSourceArnParts...) 593 594 targetLambdaResourceName, err := perm.BasePermission.export(gocf.String(SESPrincipal), 595 sesSourcePartArn, 596 lambdaFunctionDisplayName, 597 lambdaLogicalCFResourceName, 598 template, 599 S3Bucket, 600 S3Key, 601 logger) 602 if nil != err { 603 return "", errors.Wrap(err, "Failed to export SES permission") 604 } 605 606 // MessageBody storage? 607 var dependsOn []string 608 if nil != perm.MessageBodyStorage { 609 s3Policy, s3PolicyErr := perm.MessageBodyStorage.export(serviceName, 610 lambdaFunctionDisplayName, 611 lambdaLogicalCFResourceName, 612 template, 613 S3Bucket, 614 S3Key, 615 logger) 616 if nil != s3PolicyErr { 617 return "", s3PolicyErr 618 } 619 if s3Policy != "" { 620 dependsOn = append(dependsOn, s3Policy) 621 } 622 } 623 624 // Make sure the custom lambda that manages SNS notifications is provisioned. 625 configuratorResName, err := EnsureCustomResourceHandler(serviceName, 626 cfCustomResources.SESLambdaEventSource, 627 sourceArnExpression, 628 dependsOn, 629 template, 630 S3Bucket, 631 S3Key, 632 logger) 633 634 if nil != err { 635 return "", errors.Wrap(err, "Ensuring custom resource handler for SES") 636 } 637 638 // Add a custom resource invocation for this configuration 639 ////////////////////////////////////////////////////////////////////////////// 640 newResource, newResourceError := newCloudFormationResource(cfCustomResources.SESLambdaEventSource, logger) 641 if nil != newResourceError { 642 return "", newResourceError 643 } 644 customResource := newResource.(*cfCustomResources.SESLambdaEventSourceResource) 645 customResource.ServiceToken = gocf.GetAtt(configuratorResName, "Arn") 646 // The shared ruleset name used by all Sparta applications 647 customResource.RuleSetName = gocf.String("RuleSet") 648 649 /////////////////// 650 // Build up the Rules 651 // If there aren't any rules, make one that forwards everything... 652 sesLength := 0 653 if perm.ReceiptRules == nil { 654 sesLength = 1 655 } else { 656 sesLength = len(perm.ReceiptRules) 657 } 658 sesRules := make([]*cfCustomResources.SESLambdaEventSourceResourceRule, sesLength) 659 if nil == perm.ReceiptRules { 660 sesRules[0] = &cfCustomResources.SESLambdaEventSourceResourceRule{ 661 Name: gocf.String("Default"), 662 Actions: make([]*cfCustomResources.SESLambdaEventSourceResourceAction, 0), 663 ScanEnabled: gocf.Bool(false), 664 Enabled: gocf.Bool(true), 665 Recipients: []*gocf.StringExpr{}, 666 TLSPolicy: gocf.String("Optional"), 667 } 668 } else { 669 // Append all the user defined ones 670 for eachIndex, eachReceiptRule := range perm.ReceiptRules { 671 sesRules[eachIndex] = eachReceiptRule.toResourceRule( 672 serviceName, 673 gocf.GetAtt(lambdaLogicalCFResourceName, "Arn"), 674 perm.MessageBodyStorage) 675 } 676 } 677 678 customResource.Rules = sesRules 679 // Name? 680 resourceInvokerName := CloudFormationResourceName("ConfigSNS", 681 lambdaLogicalCFResourceName, 682 perm.BasePermission.SourceAccount) 683 684 // Add it 685 cfResource := template.AddResource(resourceInvokerName, customResource) 686 cfResource.DependsOn = append(cfResource.DependsOn, 687 targetLambdaResourceName, 688 configuratorResName) 689 return "", nil 690 } 691 692 func (perm SESPermission) descriptionInfo() ([]descriptionNode, error) { 693 nodes := []descriptionNode{ 694 { 695 Name: "SimpleEmailService", 696 Relation: "All verified domain(s) email", 697 }, 698 } 699 return nodes, nil 700 } 701 702 // 703 // END - SESPermission 704 //////////////////////////////////////////////////////////////////////////////// 705 706 //////////////////////////////////////////////////////////////////////////////// 707 // START - CloudWatchEventsRuleTarget 708 // 709 710 // CloudWatchEventsRuleTarget specifies additional input and JSON selection 711 // paths to apply prior to forwarding the event to a lambda function 712 type CloudWatchEventsRuleTarget struct { 713 Input string 714 InputPath string 715 } 716 717 // 718 // END - CloudWatchEventsRuleTarget 719 //////////////////////////////////////////////////////////////////////////////// 720 721 //////////////////////////////////////////////////////////////////////////////// 722 // START - CloudWatchEventsRule 723 // 724 725 // CloudWatchEventsRule defines parameters for invoking a lambda function 726 // in response to specific CloudWatchEvents or cron triggers 727 type CloudWatchEventsRule struct { 728 Description string 729 // ArbitraryJSONObject filter for events as documented at 730 // http://docs.aws.amazon.com/AmazonCloudWatch/latest/DeveloperGuide/CloudWatchEventsandEventPatterns.html 731 // Rules matches should use the JSON representation (NOT the string form). Sparta will serialize 732 // the map[string]interface{} to a string form during CloudFormation Template 733 // marshalling. 734 EventPattern map[string]interface{} `json:"EventPattern,omitempty"` 735 // Schedule pattern per http://docs.aws.amazon.com/AmazonCloudWatch/latest/DeveloperGuide/ScheduledEvents.html 736 ScheduleExpression string 737 RuleTarget *CloudWatchEventsRuleTarget `json:"RuleTarget,omitempty"` 738 } 739 740 // MarshalJSON customizes the JSON representation used when serializing to the 741 // CloudFormation template representation. 742 func (rule CloudWatchEventsRule) MarshalJSON() ([]byte, error) { 743 ruleJSON := map[string]interface{}{} 744 745 if rule.Description != "" { 746 ruleJSON["Description"] = rule.Description 747 } 748 if nil != rule.EventPattern { 749 eventPatternString, err := json.Marshal(rule.EventPattern) 750 if nil != err { 751 return nil, err 752 } 753 ruleJSON["EventPattern"] = string(eventPatternString) 754 } 755 if rule.ScheduleExpression != "" { 756 ruleJSON["ScheduleExpression"] = rule.ScheduleExpression 757 } 758 if nil != rule.RuleTarget { 759 ruleJSON["RuleTarget"] = rule.RuleTarget 760 } 761 return json.Marshal(ruleJSON) 762 } 763 764 // 765 // END - CloudWatchEventsRule 766 //////////////////////////////////////////////////////////////////////////////// 767 768 //////////////////////////////////////////////////////////////////////////////// 769 // START - CloudWatchEventsPermission 770 // 771 var cloudformationEventsSourceArnParts = []gocf.Stringable{} 772 773 // CloudWatchEventsPermission struct implies that the CloudWatchEvent sources 774 // should be configured as part of provisioning. The BasePermission.SourceArn 775 // isn't considered for this configuration. Each CloudWatchEventsRule struct 776 // in the Rules map is used to register for push based event notifications via 777 // `putRule` and `deleteRule`. 778 // See http://docs.aws.amazon.com/lambda/latest/dg/intro-core-components.html#intro-core-components-event-sources 779 // for more information. 780 type CloudWatchEventsPermission struct { 781 BasePermission 782 // Map of rule names to events that trigger the lambda function 783 Rules map[string]CloudWatchEventsRule 784 } 785 786 func (perm CloudWatchEventsPermission) export(serviceName string, 787 lambdaFunctionDisplayName string, 788 lambdaLogicalCFResourceName string, 789 template *gocf.Template, 790 S3Bucket string, 791 S3Key string, 792 logger *logrus.Logger) (string, error) { 793 794 // There needs to be at least one rule to apply 795 if len(perm.Rules) <= 0 { 796 return "", fmt.Errorf("function %s CloudWatchEventsPermission does not specify any expressions", lambdaFunctionDisplayName) 797 } 798 799 // Tell the user we're ignoring any Arns provided, since it doesn't make sense for this. 800 if nil != perm.BasePermission.SourceArn && 801 perm.BasePermission.sourceArnExpr(cloudformationEventsSourceArnParts...).String() != wildcardArn.String() { 802 logger.WithFields(logrus.Fields{ 803 "Arn": perm.BasePermission.sourceArnExpr(cloudformationEventsSourceArnParts...), 804 }).Warn("CloudWatchEvents do not support literal ARN values") 805 } 806 807 arnPermissionForRuleName := func(ruleName string) *gocf.StringExpr { 808 return gocf.Join("", 809 gocf.String("arn:aws:events:"), 810 gocf.Ref("AWS::Region"), 811 gocf.String(":"), 812 gocf.Ref("AWS::AccountId"), 813 gocf.String(":rule/"), 814 gocf.String(ruleName)) 815 } 816 817 // Add the permission to invoke the lambda function 818 uniqueRuleNameMap := make(map[string]int) 819 for eachRuleName, eachRuleDefinition := range perm.Rules { 820 821 // We need a stable unique name s.t. the permission is properly configured... 822 uniqueRuleName := CloudFormationResourceName(eachRuleName, lambdaFunctionDisplayName, serviceName) 823 uniqueRuleNameMap[uniqueRuleName]++ 824 825 // Add the permission 826 basePerm := BasePermission{ 827 SourceArn: arnPermissionForRuleName(uniqueRuleName), 828 } 829 _, exportErr := basePerm.export(gocf.String(CloudWatchEventsPrincipal), 830 cloudformationEventsSourceArnParts, 831 lambdaFunctionDisplayName, 832 lambdaLogicalCFResourceName, 833 template, 834 S3Bucket, 835 S3Key, 836 logger) 837 838 if nil != exportErr { 839 return "", exportErr 840 } 841 842 cwEventsRuleTargetList := gocf.EventsRuleTargetList{} 843 cwEventsRuleTargetList = append(cwEventsRuleTargetList, 844 gocf.EventsRuleTarget{ 845 Arn: gocf.GetAtt(lambdaLogicalCFResourceName, "Arn"), 846 ID: gocf.String(uniqueRuleName), 847 }, 848 ) 849 850 // Add the rule 851 eventsRule := &gocf.EventsRule{ 852 Name: gocf.String(uniqueRuleName), 853 Description: gocf.String(eachRuleDefinition.Description), 854 Targets: &cwEventsRuleTargetList, 855 } 856 if nil != eachRuleDefinition.EventPattern && eachRuleDefinition.ScheduleExpression != "" { 857 return "", fmt.Errorf("rule %s CloudWatchEvents specifies both EventPattern and ScheduleExpression", eachRuleName) 858 } 859 if nil != eachRuleDefinition.EventPattern { 860 eventsRule.EventPattern = eachRuleDefinition.EventPattern 861 } else if eachRuleDefinition.ScheduleExpression != "" { 862 eventsRule.ScheduleExpression = gocf.String(eachRuleDefinition.ScheduleExpression) 863 } 864 cloudWatchLogsEventResName := CloudFormationResourceName(fmt.Sprintf("%s-CloudWatchEventsRule", eachRuleName), 865 lambdaLogicalCFResourceName, 866 lambdaFunctionDisplayName) 867 template.AddResource(cloudWatchLogsEventResName, eventsRule) 868 } 869 // Validate it 870 for _, eachCount := range uniqueRuleNameMap { 871 if eachCount != 1 { 872 return "", fmt.Errorf("integrity violation for CloudWatchEvent Rulenames: %#v", uniqueRuleNameMap) 873 } 874 } 875 return "", nil 876 } 877 878 func (perm CloudWatchEventsPermission) descriptionInfo() ([]descriptionNode, error) { 879 var ruleTriggers = " " 880 for eachName, eachRule := range perm.Rules { 881 filter := eachRule.ScheduleExpression 882 if filter == "" && eachRule.EventPattern != nil { 883 filter = fmt.Sprintf("%v", eachRule.EventPattern["source"]) 884 } 885 ruleTriggers = fmt.Sprintf("%s-(%s)\n%s", eachName, filter, ruleTriggers) 886 } 887 nodes := []descriptionNode{ 888 { 889 Name: "CloudWatch Events", 890 Relation: ruleTriggers, 891 }, 892 } 893 return nodes, nil 894 } 895 896 // 897 // END - CloudWatchEventsPermission 898 //////////////////////////////////////////////////////////////////////////////// 899 900 //////////////////////////////////////////////////////////////////////////////// 901 // START - EventBridgeRule 902 // 903 904 // EventBridgeRule defines parameters for invoking a lambda function 905 // in response to specific EventBridge triggers 906 type EventBridgeRule struct { 907 Description string 908 EventBusName string 909 // ArbitraryJSONObject filter for events as documented at 910 // https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-events-rule.html#cfn-events-rule-eventpattern 911 // Rules matches should use the JSON representation (NOT the string form). Sparta will serialize 912 // the map[string]interface{} to a string form during CloudFormation Template 913 // marshalling. 914 EventPattern map[string]interface{} `json:"EventPattern,omitempty"` 915 // Schedule pattern per 916 // https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-events-rule.html#cfn-events-rule-scheduleexpression 917 ScheduleExpression string 918 } 919 920 // MarshalJSON customizes the JSON representation used when serializing to the 921 // CloudFormation template representation. 922 func (rule EventBridgeRule) MarshalJSON() ([]byte, error) { 923 ruleJSON := map[string]interface{}{} 924 925 ruleJSON["Description"] = marshalString(rule.Description) 926 ruleJSON["EventBusName"] = marshalString(rule.EventBusName) 927 if rule.EventPattern != nil { 928 ruleJSON["EventPattern"] = marshalInterface(rule.EventPattern) 929 } 930 if rule.ScheduleExpression != "" { 931 ruleJSON["ScheduleExpression"] = marshalString(rule.ScheduleExpression) 932 } 933 return json.Marshal(ruleJSON) 934 } 935 936 // 937 // END - EventBridgeRule 938 //////////////////////////////////////////////////////////////////////////////// 939 940 //////////////////////////////////////////////////////////////////////////////// 941 // START - EventBridgePermission 942 // 943 944 // EventBridgePermission struct implies that the EventBridge sources 945 // should be configured as part of provisioning. The BasePermission.SourceArn 946 // isn't considered for this configuration. Each EventBridge Rule or Schedule struct 947 // in the Rules map is used to register for push based event notifications via 948 // `putRule` and `deleteRule`. 949 // See http://docs.aws.amazon.com/lambda/latest/dg/intro-core-components.html#intro-core-components-event-sources 950 // for more information. 951 type EventBridgePermission struct { 952 BasePermission 953 // EventBridgeRule for this permission 954 Rule *EventBridgeRule 955 } 956 957 func (perm EventBridgePermission) export(serviceName string, 958 lambdaFunctionDisplayName string, 959 lambdaLogicalCFResourceName string, 960 template *gocf.Template, 961 S3Bucket string, 962 S3Key string, 963 logger *logrus.Logger) (string, error) { 964 965 // There needs to be at least one rule to apply 966 if perm.Rule == nil { 967 return "", fmt.Errorf("function %s EventBridgePermission does not specify any EventBridgeRule", 968 lambdaFunctionDisplayName) 969 } 970 971 // Name for the rule... 972 eventBridgeRuleResourceName := CloudFormationResourceName(fmt.Sprintf("EventBridge-%s", lambdaLogicalCFResourceName), 973 lambdaFunctionDisplayName) 974 975 // Tell the user we're ignoring any Arns provided, since it doesn't make sense for this. 976 if nil != perm.BasePermission.SourceArn && 977 perm.BasePermission.sourceArnExpr(cloudformationEventsSourceArnParts...).String() != wildcardArn.String() { 978 logger.WithFields(logrus.Fields{ 979 "Arn": perm.BasePermission.sourceArnExpr(cloudformationEventsSourceArnParts...), 980 }).Warn("EventBridge Events do not support literal ARN values") 981 } 982 983 // Add the permission 984 basePerm := BasePermission{ 985 SourceArn: gocf.GetAtt(eventBridgeRuleResourceName, "Arn"), 986 } 987 _, exportErr := basePerm.export(gocf.String(EventBridgePrincipal), 988 cloudformationEventsSourceArnParts, 989 lambdaFunctionDisplayName, 990 lambdaLogicalCFResourceName, 991 template, 992 S3Bucket, 993 S3Key, 994 logger) 995 996 if nil != exportErr { 997 return "", exportErr 998 } 999 1000 eventBridgeRuleTargetList := gocf.EventsRuleTargetList{} 1001 eventBridgeRuleTargetList = append(eventBridgeRuleTargetList, 1002 gocf.EventsRuleTarget{ 1003 Arn: gocf.GetAtt(lambdaLogicalCFResourceName, "Arn"), 1004 ID: gocf.String(serviceName), 1005 }, 1006 ) 1007 if nil != perm.Rule.EventPattern && 1008 perm.Rule.ScheduleExpression != "" { 1009 return "", fmt.Errorf("rule %s EventBridge specifies both EventPattern and ScheduleExpression", 1010 perm.Rule) 1011 } 1012 1013 // Add the rule 1014 eventsRule := &gocf.EventsRule{ 1015 Targets: &eventBridgeRuleTargetList, 1016 } 1017 if perm.Rule.EventBusName != "" { 1018 eventsRule.EventBusName = marshalString(perm.Rule.EventBusName) 1019 } 1020 // Setup the description placeholder...we'll set it in a bit... 1021 ruleDescription := "" 1022 if perm.Rule.EventPattern != nil { 1023 eventsRule.EventPattern = marshalInterface(perm.Rule.EventPattern) 1024 ruleDescription = fmt.Sprintf("%s (Stack: %s) event pattern subscriber", 1025 lambdaFunctionDisplayName, 1026 serviceName) 1027 } else if perm.Rule.ScheduleExpression != "" { 1028 eventsRule.ScheduleExpression = marshalString(perm.Rule.ScheduleExpression) 1029 ruleDescription = fmt.Sprintf("%s (Stack: %s) scheduled subscriber", 1030 lambdaFunctionDisplayName, 1031 serviceName) 1032 } 1033 eventsRule.Description = marshalString(ruleDescription) 1034 template.AddResource(eventBridgeRuleResourceName, eventsRule) 1035 return "", nil 1036 } 1037 1038 func (perm EventBridgePermission) descriptionInfo() ([]descriptionNode, error) { 1039 var ruleTriggers = " " 1040 1041 filter := perm.Rule.ScheduleExpression 1042 if filter == "" && perm.Rule.EventPattern != nil { 1043 filter = fmt.Sprintf("%v", perm.Rule.EventPattern) 1044 } 1045 ruleTriggers = fmt.Sprintf("EventBridge-(%s)\n%s", filter, ruleTriggers) 1046 1047 nodes := []descriptionNode{ 1048 { 1049 Name: "EventBridge Event", 1050 Relation: ruleTriggers, 1051 }, 1052 } 1053 return nodes, nil 1054 } 1055 1056 // 1057 // END - CloudWatchEventsPermission 1058 //////////////////////////////////////////////////////////////////////////////// 1059 1060 //////////////////////////////////////////////////////////////////////////////// 1061 // START - CloudWatchLogsPermission 1062 // 1063 1064 // CloudWatchLogsSubscriptionFilter represents the CloudWatch Log filter 1065 // information 1066 type CloudWatchLogsSubscriptionFilter struct { 1067 FilterPattern string 1068 LogGroupName string 1069 } 1070 1071 var cloudformationLogsSourceArnParts = []gocf.Stringable{ 1072 gocf.String("arn:aws:logs:"), 1073 } 1074 1075 // CloudWatchLogsPermission struct implies that the corresponding 1076 // CloudWatchLogsSubscriptionFilter definitions should be configured during 1077 // stack provisioning. The BasePermission.SourceArn isn't considered for 1078 // this configuration operation. Configuration of the remote push source 1079 // is done via `putSubscriptionFilter` and `deleteSubscriptionFilter`. 1080 // See http://docs.aws.amazon.com/lambda/latest/dg/intro-core-components.html#intro-core-components-event-sources 1081 // for more information. 1082 type CloudWatchLogsPermission struct { 1083 BasePermission 1084 // Map of filter names to the CloudWatchLogsSubscriptionFilter settings 1085 Filters map[string]CloudWatchLogsSubscriptionFilter 1086 } 1087 1088 func (perm CloudWatchLogsPermission) export(serviceName string, 1089 lambdaFunctionDisplayName string, 1090 lambdaLogicalCFResourceName string, 1091 template *gocf.Template, 1092 S3Bucket string, 1093 S3Key string, 1094 logger *logrus.Logger) (string, error) { 1095 1096 // If there aren't any expressions to register with? 1097 if len(perm.Filters) <= 0 { 1098 return "", fmt.Errorf("function %s CloudWatchLogsPermission does not specify any filters", lambdaFunctionDisplayName) 1099 } 1100 1101 // The principal is region specific, so build that up... 1102 regionalPrincipal := gocf.Join(".", 1103 gocf.String("logs"), 1104 gocf.Ref("AWS::Region"), 1105 gocf.String("amazonaws.com")) 1106 1107 // Tell the user we're ignoring any Arns provided, since it doesn't make sense for 1108 // this. 1109 if nil != perm.BasePermission.SourceArn && 1110 perm.BasePermission.sourceArnExpr(cloudformationLogsSourceArnParts...).String() != wildcardArn.String() { 1111 logger.WithFields(logrus.Fields{ 1112 "Arn": perm.BasePermission.sourceArnExpr(cloudformationEventsSourceArnParts...), 1113 }).Warn("CloudWatchLogs do not support literal ARN values") 1114 } 1115 1116 // Make sure we grant InvokeFunction privileges to CloudWatchLogs 1117 lambdaInvokePermission, err := perm.BasePermission.export(regionalPrincipal, 1118 cloudformationLogsSourceArnParts, 1119 lambdaFunctionDisplayName, 1120 lambdaLogicalCFResourceName, 1121 template, 1122 S3Bucket, 1123 S3Key, 1124 logger) 1125 if nil != err { 1126 return "", errors.Wrap(err, "Exporting regional CloudWatch log permission") 1127 } 1128 1129 // Then we need to uniqueify the rule names s.t. we prevent 1130 // collisions with other stacks. 1131 configurationResourceNames := make(map[string]int) 1132 // Store the last name. We'll do a uniqueness check when exiting the loop, 1133 // and if that passes, the last name will also be the unique one. 1134 var configurationResourceName string 1135 // Create the CustomResource entries 1136 globallyUniqueFilters := make(map[string]CloudWatchLogsSubscriptionFilter, len(perm.Filters)) 1137 for eachFilterName, eachFilter := range perm.Filters { 1138 filterPrefix := fmt.Sprintf("%s_%s", serviceName, eachFilterName) 1139 uniqueFilterName := CloudFormationResourceName(filterPrefix, lambdaLogicalCFResourceName) 1140 globallyUniqueFilters[uniqueFilterName] = eachFilter 1141 1142 // The ARN we supply to IAM is built up using the user supplied groupname 1143 cloudWatchLogsArn := gocf.Join("", 1144 gocf.String("arn:aws:logs:"), 1145 gocf.Ref("AWS::Region"), 1146 gocf.String(":"), 1147 gocf.Ref("AWS::AccountId"), 1148 gocf.String(":log-group:"), 1149 gocf.String(eachFilter.LogGroupName), 1150 gocf.String(":log-stream:*")) 1151 1152 lastConfigurationResourceName, ensureCustomHandlerError := EnsureCustomResourceHandler(serviceName, 1153 cfCustomResources.CloudWatchLogsLambdaEventSource, 1154 cloudWatchLogsArn, 1155 []string{}, 1156 template, 1157 S3Bucket, 1158 S3Key, 1159 logger) 1160 if nil != ensureCustomHandlerError { 1161 return "", errors.Wrap(err, "Ensuring CloudWatch permissions handler") 1162 } 1163 configurationResourceNames[configurationResourceName] = 1 1164 configurationResourceName = lastConfigurationResourceName 1165 } 1166 if len(configurationResourceNames) > 1 { 1167 return "", fmt.Errorf("internal integrity check failed. Multiple configurators (%d) provisioned for CloudWatchLogs", 1168 len(configurationResourceNames)) 1169 } 1170 1171 // Get the single configurator name from the 1172 1173 // Add the custom resource that uses this... 1174 ////////////////////////////////////////////////////////////////////////////// 1175 1176 newResource, newResourceError := newCloudFormationResource(cfCustomResources.CloudWatchLogsLambdaEventSource, logger) 1177 if nil != newResourceError { 1178 return "", newResourceError 1179 } 1180 customResource := newResource.(*cfCustomResources.CloudWatchLogsLambdaEventSourceResource) 1181 customResource.ServiceToken = gocf.GetAtt(configurationResourceName, "Arn") 1182 customResource.LambdaTargetArn = gocf.GetAtt(lambdaLogicalCFResourceName, "Arn") 1183 // Build up the filters... 1184 customResource.Filters = make([]*cfCustomResources.CloudWatchLogsLambdaEventSourceFilter, 0) 1185 for eachName, eachFilter := range globallyUniqueFilters { 1186 customResource.Filters = append(customResource.Filters, 1187 &cfCustomResources.CloudWatchLogsLambdaEventSourceFilter{ 1188 Name: gocf.String(eachName), 1189 Pattern: gocf.String(eachFilter.FilterPattern), 1190 LogGroupName: gocf.String(eachFilter.LogGroupName), 1191 }) 1192 1193 } 1194 1195 resourceInvokerName := CloudFormationResourceName("ConfigCloudWatchLogs", 1196 lambdaLogicalCFResourceName, 1197 perm.BasePermission.SourceAccount) 1198 // Add it 1199 cfResource := template.AddResource(resourceInvokerName, customResource) 1200 1201 cfResource.DependsOn = append(cfResource.DependsOn, 1202 lambdaInvokePermission, 1203 lambdaLogicalCFResourceName, 1204 configurationResourceName) 1205 return "", nil 1206 } 1207 1208 func (perm CloudWatchLogsPermission) descriptionInfo() ([]descriptionNode, error) { 1209 nodes := make([]descriptionNode, len(perm.Filters)) 1210 nodeIndex := 0 1211 for eachFilterName, eachFilterDef := range perm.Filters { 1212 nodes[nodeIndex] = descriptionNode{ 1213 Name: describeInfoValue(eachFilterDef.LogGroupName), 1214 Relation: fmt.Sprintf("%s (%s)", eachFilterName, eachFilterDef.FilterPattern), 1215 } 1216 nodeIndex++ 1217 } 1218 return nodes, nil 1219 } 1220 1221 // 1222 // END - CloudWatchLogsPermission 1223 /////////////////////////////////////////////////////////////////////////////////// 1224 1225 //////////////////////////////////////////////////////////////////////////////// 1226 // START - CodeCommitPermission 1227 // 1228 // arn:aws:codecommit:us-west-2:123412341234:myRepo 1229 var codeCommitSourceArnParts = []gocf.Stringable{ 1230 gocf.String("arn:aws:codecommit:"), 1231 gocf.Ref("AWS::Region"), 1232 gocf.String(":"), 1233 gocf.Ref("AWS::AccountId"), 1234 gocf.String(":"), 1235 } 1236 1237 // CodeCommitPermission struct encapsulates the data necessary 1238 // to trigger the owning LambdaFunction in response to 1239 // CodeCommit events 1240 type CodeCommitPermission struct { 1241 BasePermission 1242 // RepositoryName 1243 RepositoryName *gocf.StringExpr 1244 // Branches to register for 1245 Branches []string `json:"branches,omitempty"` 1246 // Events to subscribe to. Defaults to "all" if empty. 1247 Events []string `json:"events,omitempty"` 1248 } 1249 1250 func (perm CodeCommitPermission) export(serviceName string, 1251 lambdaFunctionDisplayName string, 1252 lambdaLogicalCFResourceName string, 1253 template *gocf.Template, 1254 S3Bucket string, 1255 S3Key string, 1256 logger *logrus.Logger) (string, error) { 1257 1258 principal := gocf.Join("", 1259 gocf.String("codecommit."), 1260 gocf.Ref("AWS::Region"), 1261 gocf.String(".amazonaws.com")) 1262 1263 sourceArnExpression := perm.BasePermission.sourceArnExpr(codeCommitSourceArnParts...) 1264 1265 targetLambdaResourceName, err := perm.BasePermission.export(principal, 1266 codeCommitSourceArnParts, 1267 lambdaFunctionDisplayName, 1268 lambdaLogicalCFResourceName, 1269 template, 1270 S3Bucket, 1271 S3Key, 1272 logger) 1273 1274 if nil != err { 1275 return "", errors.Wrap(err, "Failed to export CodeCommit permission") 1276 } 1277 1278 // Make sure that the handler that manages triggers is registered. 1279 configuratorResName, err := EnsureCustomResourceHandler(serviceName, 1280 cfCustomResources.CodeCommitLambdaEventSource, 1281 sourceArnExpression, 1282 []string{}, 1283 template, 1284 S3Bucket, 1285 S3Key, 1286 logger) 1287 1288 if nil != err { 1289 return "", errors.Wrap(err, "Exporing CodeCommit permission handler") 1290 } 1291 1292 // Add a custom resource invocation for this configuration 1293 ////////////////////////////////////////////////////////////////////////////// 1294 newResource, newResourceError := newCloudFormationResource(cfCustomResources.CodeCommitLambdaEventSource, 1295 logger) 1296 if nil != newResourceError { 1297 return "", newResourceError 1298 } 1299 repoEvents := perm.Events 1300 if len(repoEvents) <= 0 { 1301 repoEvents = []string{"all"} 1302 } 1303 customResource := newResource.(*cfCustomResources.CodeCommitLambdaEventSourceResource) 1304 customResource.ServiceToken = gocf.GetAtt(configuratorResName, "Arn") 1305 customResource.LambdaTargetArn = gocf.GetAtt(lambdaLogicalCFResourceName, "Arn") 1306 customResource.TriggerName = gocf.Ref(lambdaLogicalCFResourceName).String() 1307 customResource.RepositoryName = perm.RepositoryName 1308 customResource.Events = repoEvents 1309 customResource.Branches = perm.Branches 1310 1311 // Name? 1312 resourceInvokerName := CloudFormationResourceName("ConfigCodeCommit", 1313 lambdaLogicalCFResourceName, 1314 perm.BasePermission.SourceAccount) 1315 1316 // Add it 1317 cfResource := template.AddResource(resourceInvokerName, customResource) 1318 cfResource.DependsOn = append(cfResource.DependsOn, 1319 targetLambdaResourceName, 1320 configuratorResName) 1321 return "", nil 1322 } 1323 1324 func (perm CodeCommitPermission) descriptionInfo() ([]descriptionNode, error) { 1325 nodes := make([]descriptionNode, 0) 1326 if len(perm.Branches) <= 0 { 1327 nodes = append(nodes, descriptionNode{ 1328 Name: describeInfoValue(perm.SourceArn), 1329 Relation: "all", 1330 }) 1331 } else { 1332 for _, eachBranch := range perm.Branches { 1333 filterRel := fmt.Sprintf("%s (%#v)", 1334 eachBranch, 1335 perm.Events) 1336 nodes = append(nodes, descriptionNode{ 1337 Name: describeInfoValue(perm.SourceArn), 1338 Relation: filterRel, 1339 }) 1340 } 1341 } 1342 return nodes, nil 1343 } 1344 1345 // END - CodeCommitPermission 1346 ///////////////////////////////////////////////////////////////////////////////////