From 8cd3f287348c6a147526ac3786302f9a2d58dcf8 Mon Sep 17 00:00:00 2001 From: Jonathan Gold Date: Sat, 4 Nov 2017 12:30:03 -0400 Subject: [PATCH] resources: aws: ec2: Authorize CloudWatch to publish to sns --- resources/aws_ec2.go | 99 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) diff --git a/resources/aws_ec2.go b/resources/aws_ec2.go index 828f165b..81aa087b 100644 --- a/resources/aws_ec2.go +++ b/resources/aws_ec2.go @@ -59,6 +59,16 @@ const ( SnsSubscriptionProto = "http" // SnsServerShutdownTimeout is the maximum number of seconds to wait for the http server to shutdown gracefully. SnsServerShutdownTimeout = 30 + // SnsPolicy is the topic attribute that defines the security policy for the topic. + SnsPolicy = "Policy" + // SnsPolicySid is the friendly name of the policy statement. + SnsPolicySid = CwePrefix + "publish" + // SnsPolicyEffect allows the action(s) defined in the policy statement. + SnsPolicyEffect = "Allow" + // SnsPolicyService is the cloudwatch events security principal that we are granting the permission to. + SnsPolicyService = "events.amazonaws.com" + // SnsPolicyAction is the specific permission we are granting in the policy. + SnsPolicyAction = "SNS:Publish" // CwePrefix gets prepended onto the cloudwatch rule name. CwePrefix = Ec2Prefix + "cw-" // CweRuleName is the name of the rule created by makeCloudWatchRule. @@ -146,6 +156,33 @@ type chanStruct struct { err error } +// snsPolicy denotes the structure of sns security policies. +type snsPolicy struct { + Version string `json:"Version"` + ID string `json:"Id"` + Statement []snsStatement `json:"Statement"` +} + +// snsStatement denotes the structure of sns security policy statements. +type snsStatement struct { + Sid string `json:"Sid"` + Effect string `json:"Effect"` + Principal snsPrincipal `json:"Principal"` + Action interface{} `json:"Action"` + Resource string `json:"Resource"` + Condition *struct { + StringEquals *struct { + AWSSourceOwner *string `json:"AWS:SourceOwner,omitempty"` + } `json:"StringEquals,omitempty"` + } `json:"Condition,omitempty"` +} + +// snsPrincipal describes the aws or service account principal. +type snsPrincipal struct { + AWS string `json:"AWS,omitempty"` + Service string `json:"Service,omitempty"` +} + // cloudWatchRule denotes the structure of cloudwatch rules. type cloudWatchRule struct { Source []string `json:"source"` @@ -289,6 +326,11 @@ func (obj *AwsEc2Res) Init() error { if err := obj.cweTargetRule(obj.snsTopicArn, CweTargetID, CweTargetJSON, CweRuleName); err != nil { return errwrap.Wrapf(err, "error targeting cloudwatch rule") } + // authorize cloudwatch to publish on sns + // This gets cleaned up in Close(), when the topic is deleted. + if err := obj.snsAuthorizeCloudWatch(obj.snsTopicArn); err != nil { + return errwrap.Wrapf(err, "error authorizing cloudwatch for sns") + } } return obj.BaseRes.Init() // call base init, b/c we're overriding @@ -1028,6 +1070,63 @@ func (obj *AwsEc2Res) snsConfirmSubscription(topicArn string, token string) erro return nil } +// snsAuthorize adds the necessary permission for cloudwatch to publish to the SNS topic. +func (obj *AwsEc2Res) snsAuthorizeCloudWatch(topicArn string) error { + // get the topic attributes, including the security policy + gaInput := &sns.GetTopicAttributesInput{ + TopicArn: aws.String(topicArn), + } + attrs, err := obj.snsClient.GetTopicAttributes(gaInput) + if err != nil { + return err + } + // get the existing security policy + pol := attrs.Attributes[SnsPolicy] + // unmarshal the current sns security policy + var policy snsPolicy + if err := json.Unmarshal([]byte(*pol), &policy); err != nil { + return err + } + // construct a policy statement + permission := snsStatement{ + Sid: SnsPolicySid, + Effect: SnsPolicyEffect, + Principal: snsPrincipal{ + Service: SnsPolicyService, + }, + Action: SnsPolicyAction, + Resource: topicArn, + } + // check if permissions have already been added + for _, statement := range policy.Statement { + if statement == permission { + // if it's already there, we're done + log.Printf("%s: Target Already Authorized", obj) + return nil + } + } + // add the new policy statement to the existing one(s) + policy.Statement = append(policy.Statement, permission) + // marshal the updated policy + newPolicyBytes, err := json.Marshal(policy) + if err != nil { + return err + } + // update topic attributes with the new policy + newPolicy := string(newPolicyBytes) + saInput := &sns.SetTopicAttributesInput{ + AttributeName: aws.String(SnsPolicy), + AttributeValue: aws.String(newPolicy), + TopicArn: aws.String(topicArn), + } + _, err = obj.snsClient.SetTopicAttributes(saInput) + if err != nil { + return err + } + log.Printf("%s: Authorized Target", obj) + return nil +} + // cweMakeEventPattern makes and encodes event patterns for cloudwatch rules. func (obj *AwsEc2Res) cweMakeEventPattern(source, detailType string, detail []string) (string, error) { pattern := cloudWatchRule{