Last active
November 27, 2020 00:35
-
-
Save chrisj-au/3e98bd19bcffa4ea80aa4d553eac12f7 to your computer and use it in GitHub Desktop.
Provide auditing on CodePipeline approval gate
This file contains hidden or 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
# CodePipeline does not record gate approval outside of CloudTrail. Includes Lambda to write to S3. Not fully self contained, missing parameters, conditions. | |
Resources: | |
PipelineApprovalTrackingBucket: | |
Type: AWS::S3::Bucket | |
Condition: ShouldCreateAudit | |
Properties: | |
AccessControl: Private | |
BucketName: | |
!Sub | |
- '${LocalFindInMapAccountName}-${ProjectName}-pipeline-approval-tracking' | |
- { LocalFindInMapAccountName: !FindInMap [ Environments, !Ref Environment, accountName ] } | |
VersioningConfiguration: | |
Status: Enabled | |
Tags: | |
- Key: repository | |
Value: !Ref repo | |
- Key: environment | |
Value: !Ref Environment | |
LambdaNotificationTopic: | |
Type: 'AWS::SNS::Topic' | |
Condition: ShouldCreateAudit | |
Properties: | |
TopicName: !Sub '${CodeBuildTerraformAction}-pipeline-approval-gate-tracking-topic' | |
Subscription: !If | |
- SnsEmailAddressExists | |
- | |
- Endpoint: !Ref SnsTopicEmailAddress | |
Protocol: email | |
- !Ref AWS::NoValue | |
Tags: | |
- Key: repository | |
Value: !Ref repo | |
- Key: environment | |
Value: !Ref Environment | |
LambdaNotificationTopicPolicy: | |
Type: 'AWS::SNS::TopicPolicy' | |
Condition: ShouldCreateAudit | |
Properties: | |
Topics: | |
- !Ref LambdaNotificationTopic | |
PolicyDocument: | |
Version: 2012-10-17 | |
Id: __default_policy_ID | |
Statement: | |
Sid: AWSLambda | |
Effect: Allow | |
Principal: | |
Service: | |
- lambda.amazonaws.com | |
- events.amazonaws.com | |
Action: sns:Publish | |
Resource: !Ref LambdaNotificationTopic | |
CloudWatchApproval: | |
Type: AWS::Events::Rule | |
Condition: ShouldCreateAudit | |
Properties: | |
Description: Event Rule that tracks whenever someone approves/rejects an approval gate in a pipeline | |
EventPattern: | |
source: | |
- aws.codepipeline | |
detail-type: | |
- AWS API Call via CloudTrail | |
detail: | |
requestParameters: | |
pipelineName: | |
!If | |
- ShouldCreateDestroy | |
- - !Ref PipelineDestroy | |
- !Ref PipelineDeploy | |
- - !Ref PipelineDeploy | |
eventName: | |
- PutApprovalResult | |
Name: !Sub '${CodeBuildTerraformAction}-pipeline-approval-event' | |
State: ENABLED | |
Targets: | |
- | |
Arn: | |
Fn::GetAtt: | |
- "ApprovalGateEventTrackingLambda" | |
- "Arn" | |
Id: "TargetFunctionV1" | |
PermissionForEventsToInvokeLambda: | |
Type: AWS::Lambda::Permission | |
Condition: ShouldCreateAudit | |
Properties: | |
FunctionName: | |
!Ref ApprovalGateEventTrackingLambda | |
Action: "lambda:InvokeFunction" | |
Principal: "events.amazonaws.com" | |
SourceArn: | |
!GetAtt CloudWatchApproval.Arn | |
ApprovalGateEventTrackingLambda: | |
Type: "AWS::Lambda::Function" | |
Condition: ShouldCreateAudit | |
Properties: | |
Code: | |
ZipFile: | | |
import json | |
import boto3 | |
import os | |
import time | |
import datetime | |
from datetime import datetime | |
from dateutil import tz | |
sns = boto3.client('sns') | |
s3 = boto3.client('s3') | |
bucket = os.environ['BUCKETNAME'] | |
sns_topic_arn = os.environ['SNSTOPICARN'] | |
# Method for sending SNS notification messages | |
def send_sns_message(sns_topic_arn, sns_message, message_subject): | |
sns.publish( | |
TargetArn=sns_topic_arn, | |
Message=sns_message, | |
Subject=message_subject | |
) | |
# Method for tagging S3 objects with tag set | |
def tag_s3_object(bucket, key, tag_set): | |
s3.put_object_tagging( | |
Bucket=bucket, | |
Key=key, | |
Tagging={ | |
'TagSet': tag_set | |
} | |
) | |
# Lambda handler | |
def lambda_handler(event, context): | |
body = event["detail"] | |
# Record CloudTrail event body information to Lambda Function's CloudWatch log group | |
print(body) | |
iam_user_arn = body["userIdentity"]["arn"] | |
pipeline_name = body["requestParameters"]["pipelineName"] | |
approval_gate = body["requestParameters"]["stageName"] | |
approval_status = body["requestParameters"]["result"]["status"] | |
approval_summary = body["requestParameters"]["result"]["summary"] | |
approval_date = body["responseElements"]["approvedAt"] | |
account_id = body["userIdentity"]["accountId"] | |
region = body["awsRegion"] | |
# Change approval date time from UTC to EST | |
date_str = approval_date | |
from_zone = tz.gettz('UTC') | |
to_zone = tz.gettz('US/Eastern') | |
utc = datetime.strptime(date_str, '%b %d, %Y %I:%M:%S %p') | |
utc = utc.replace(tzinfo=from_zone) | |
est_time = utc.astimezone(to_zone) | |
est_time_approval_date = datetime.strftime(est_time, '%b %d, %Y %I:%M:%S %p') | |
year = est_time.strftime("%Y") | |
month = est_time.strftime("%m") | |
day = est_time.strftime("%d") | |
time_of_day = est_time.strftime("%I:%M:%S %p").replace(" ", "-") | |
# Create SNS message body | |
sns_message = (f"Approval Gate {approval_gate} \ | |
for Pipeline {pipeline_name} \ | |
was {approval_status} \ | |
by {iam_user_arn} \ | |
on {est_time_approval_date} \ | |
inside the {region} region \ | |
in AWS Account {account_id}. \ | |
Approval Gate Response Summary: \ | |
{approval_summary}") | |
message_subject = 'Pipeline Approval Gate Information' | |
# Send SNS message containing information about the approval gate action | |
send_sns_message(sns_topic_arn, sns_message, message_subject) | |
# Open up temp file and write the CloudTrail JSON event body information to JSON file. | |
# This JSON file will be the S3 object that gets uploaded and stored in S3. | |
with open(f"/tmp/{approval_gate}__{est_time_approval_date}.txt".replace(" ", "-").replace(",", ""), 'w') as outfile: | |
json.dump(body, outfile) | |
file = (f"/tmp/{approval_gate}__{est_time_approval_date}.txt").replace(" ", "-").replace(",", "") | |
object_key = (f"PipelineApprovalGateActions/{pipeline_name}/{year}/{month}/{day}/{approval_gate}-{approval_status.upper()}-{time_of_day}.txt") | |
s3.upload_file(file, bucket, object_key) | |
# Sleep for 5 seconds to allow object upload to complete. | |
time.sleep(5) | |
# Create S3 object tag set | |
s3_tag_set = [] | |
s3_tag_set.append({"Key": "Pipeline Name", "Value": pipeline_name}) | |
s3_tag_set.append({"Key": "Approval Gate", "Value": approval_gate}) | |
s3_tag_set.append({"Key": "Approval Gate Status", "Value": approval_status}) | |
s3_tag_set.append({"Key": "IAM Approver", "Value": iam_user_arn}) | |
# Tag newly upload S3 object | |
tag_s3_object(bucket, object_key, s3_tag_set) | |
FunctionName: !Sub '${CodeBuildTerraformAction}-pipeline-approval-gate-tracking' | |
Handler: index.lambda_handler | |
Environment: | |
Variables: | |
SNSTOPICARN: !Ref LambdaNotificationTopic | |
BUCKETNAME: !Ref PipelineApprovalTrackingBucket | |
MemorySize: 128 | |
Role: !GetAtt AuditTrackingEventLambdaRole.Arn | |
Runtime: python3.6 | |
Timeout: 10 | |
Tags: | |
- Key: repository | |
Value: !Ref GHCBRepo | |
- Key: environment | |
Value: !Ref Environment | |
- Key: project_name | |
Value: !Ref ProjectName | |
- Key: documentation | |
Value: See Repository readme.md | |
- Key: technical_owner | |
Value: [email protected] | |
- Key: iac | |
Value: CloudFormation | |
- Key: pipeline | |
Value: none | |
AuditTrackingEventLambdaRole: | |
Type: AWS::IAM::Role | |
Condition: ShouldCreateAudit | |
Properties: | |
AssumeRolePolicyDocument: | |
Statement: | |
- Sid: '' | |
Effect: Allow | |
Principal: | |
Service: | |
- lambda.amazonaws.com | |
Action: sts:AssumeRole | |
Path: "/" | |
Tags: | |
- Key: repository | |
Value: !Ref GHCBRepo | |
- Key: environment | |
Value: !Ref Environment | |
- Key: project_name | |
Value: !Ref ProjectName | |
- Key: documentation | |
Value: See Repository readme.md | |
- Key: technical_owner | |
Value: [email protected] | |
- Key: iac | |
Value: CloudFormation | |
- Key: pipeline | |
Value: none | |
Policies: | |
- PolicyName: | |
!Sub '${CodeBuildTerraformAction}-pipeline-approval-gate-tracking' | |
PolicyDocument: | |
Version: '2012-10-17' | |
Statement: | |
- Effect: Allow | |
Action: | |
- 'sns:Publish' | |
Resource: | |
- !Ref LambdaNotificationTopic | |
- Effect: Allow | |
Action: | |
- s3:PutObject | |
- s3:PutObjectTagging | |
Resource: | |
- !Sub arn:aws:s3:::${PipelineApprovalTrackingBucket} | |
- !Sub arn:aws:s3:::${PipelineApprovalTrackingBucket}/* | |
- Effect: Allow | |
Action: | |
- logs:CreateLogGroup | |
- logs:CreateLogStream | |
- logs:PutLogEvents | |
Resource: | |
- !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:* | |
- Effect: Allow | |
Action: | |
- 'cloudtrail:LookupEvents' | |
- 'cloudtrail:DescribeTrails' | |
Resource: | |
- '*' | |
- Effect: Allow | |
Action: | |
- 'cloudtrail:GetTrailStatus' | |
Resource: | |
- !Sub 'arn:aws:cloudtrail:${AWS::Region}:${AWS::AccountId}:trail/*' |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment