-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Create org trail template and deploy action
- Loading branch information
Showing
2 changed files
with
358 additions
and
0 deletions.
There are no files selected for viewing
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
name: Deploy Organization Trail | ||
|
||
on: | ||
push: | ||
branches: | ||
- main | ||
paths: | ||
- trails/organization-trail/template.yml | ||
|
||
concurrency: | ||
group: ${{ github.workflow }} | ||
|
||
permissions: | ||
id-token: write | ||
contents: read | ||
|
||
jobs: | ||
deploy: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v4 | ||
|
||
- name: Configure AWS Credentials | ||
uses: aws-actions/configure-aws-credentials@v4 | ||
with: | ||
aws-region: us-east-2 | ||
role-to-assume: arn:aws:iam::048723829744:role/PRX-GHA-AccessRole | ||
role-session-name: gha-deploy-org-trail | ||
|
||
- name: Deploy to management account | ||
working-directory: trails/organization-trail | ||
run: | | ||
aws cloudformation deploy \ | ||
--region us-east-2 \ | ||
--stack-name organization-trail \ | ||
--template-file template.yml \ | ||
--no-fail-on-empty-changeset \ | ||
--role-arn arn:aws:iam::048723829744:role/PRX-GHA-ServiceRoleForCloudFormation |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,320 @@ | ||
# See: https://docs.aws.amazon.com/awscloudtrail/latest/userguide/creating-trail-organization.html | ||
# This stack creates an Organization Trail. Creating a single Organization Trail | ||
# somewhere within an AWS Organization will result in CloudTrail trails being | ||
# created in various accounts and regions throughout the organization. | ||
# | ||
# This stack should only be launched once within the organization. It is | ||
# intended to be launched in the *management account* of the organization. | ||
# | ||
# All CloudTrail log files, for all regions and accounts, will be captured in | ||
# the S3 bucket that is created by this stack. The objects will include | ||
# prefixes for account, region, etc. For example: | ||
# AWSLogs/o-aabbccdd1122/1234567890/CloudTrail/ap-northeast-1/2024/04/01/048723829744_CloudTrail_ap-northeast-1_20240401T0105Z_bOL7464b6HTXPi7x.json.gz | ||
|
||
AWSTemplateFormatVersion: "2010-09-09" | ||
Description: Creates a CloudTrail Organization trail | ||
|
||
Parameters: | ||
OrganizationId: | ||
Type: String | ||
Description: >- | ||
optional. An AWS Organization ID in the format: o-a1s2d3f4f5g. If | ||
included, the CloudTrail that is created will be a organization trail, | ||
and log activity from all accounts in the given organization. | ||
AllowedPattern: ^$|^(o-[a-z0-9]{4,32})$ | ||
ProjectionRegions: | ||
Type: CommaDelimitedList | ||
ProjectionAccounts: | ||
Type: CommaDelimitedList | ||
|
||
Resources: | ||
CloudTrailS3Bucket: | ||
Type: AWS::S3::Bucket | ||
DeletionPolicy: Retain | ||
UpdateReplacePolicy: Retain | ||
Properties: | ||
LifecycleConfiguration: | ||
Rules: | ||
- ExpirationInDays: 14 | ||
Status: Enabled | ||
OwnershipControls: | ||
Rules: | ||
# The IAM Access Analyzer requires that CloudTrail objects that it | ||
# analyzes to generate policies be owned by the bucket owner | ||
# https://docs.aws.amazon.com/IAM/latest/UserGuide/access-analyzer-policy-generation.html#access-analyzer-policy-generation-cross-account | ||
- ObjectOwnership: BucketOwnerPreferred | ||
Tags: | ||
- { Key: prx:meta:tagging-version, Value: "2021-04-07" } | ||
- { Key: prx:cloudformation:stack-name, Value: !Ref AWS::StackName } | ||
- { Key: prx:cloudformation:stack-id, Value: !Ref AWS::StackId } | ||
- { Key: prx:ops:environment, Value: Production } | ||
- { Key: prx:dev:application, Value: Security } | ||
CloudTrailS3BucketPolicy: | ||
Type: AWS::S3::BucketPolicy | ||
Properties: | ||
Bucket: !Ref CloudTrailS3Bucket | ||
PolicyDocument: | ||
Statement: | ||
- Sid: AWSCloudTrailAclCheck | ||
Effect: Allow | ||
Principal: | ||
Service: cloudtrail.amazonaws.com | ||
Action: s3:GetBucketAcl | ||
Resource: !GetAtt CloudTrailS3Bucket.Arn | ||
- Sid: AWSCloudTrailWrite | ||
Effect: Allow | ||
Principal: | ||
Service: cloudtrail.amazonaws.com | ||
Action: s3:PutObject | ||
Resource: !Sub ${CloudTrailS3Bucket.Arn}/AWSLogs/${AWS::AccountId}/* | ||
Condition: | ||
StringEquals: | ||
s3:x-amz-acl: bucket-owner-full-control | ||
- Sid: AWSOrgCloudTrailWrite | ||
Effect: Allow | ||
Principal: | ||
Service: cloudtrail.amazonaws.com | ||
Action: s3:PutObject | ||
Resource: !Sub ${CloudTrailS3Bucket.Arn}/AWSLogs/${OrganizationId}/* | ||
Condition: | ||
StringEquals: | ||
s3:x-amz-acl: bucket-owner-full-control | ||
- Sid: IamAccessAnalyzerRead | ||
Effect: Allow | ||
Principal: | ||
AWS: "*" | ||
Action: | ||
- s3:GetObject | ||
- s3:ListBucket | ||
Resource: | ||
- !GetAtt CloudTrailS3Bucket.Arn | ||
- !Sub ${CloudTrailS3Bucket.Arn}/AWSLogs/${OrganizationId}/${!aws:PrincipalAccount}/* | ||
Condition: | ||
StringEquals: | ||
aws:PrincipalOrgID: !Ref OrganizationId | ||
StringLike: | ||
aws:PrincipalArn: arn:aws:iam::${aws:PrincipalAccount}:role/service-role/AccessAnalyzerMonitorServiceRole* | ||
Version: "2012-10-17" | ||
|
||
CloudTrail: | ||
Type: AWS::CloudTrail::Trail | ||
DependsOn: | ||
- CloudTrailS3BucketPolicy | ||
Properties: | ||
IncludeGlobalServiceEvents: true | ||
IsLogging: true | ||
IsMultiRegionTrail: true | ||
IsOrganizationTrail: true | ||
S3BucketName: !Ref CloudTrailS3Bucket | ||
Tags: | ||
- { Key: prx:meta:tagging-version, Value: "2021-04-07" } | ||
- { Key: prx:cloudformation:stack-name, Value: !Ref AWS::StackName } | ||
- { Key: prx:cloudformation:stack-id, Value: !Ref AWS::StackId } | ||
- { Key: prx:ops:environment, Value: Production } | ||
- { Key: prx:dev:application, Value: Security } | ||
|
||
GlueDatabase: | ||
Type: AWS::Glue::Database | ||
Properties: | ||
CatalogId: !Ref AWS::AccountId | ||
DatabaseInput: | ||
Description: Database CloudTrail organization trail | ||
Name: !Ref AWS::StackName | ||
|
||
CommonTrailTable: | ||
Type: AWS::Glue::Table | ||
Properties: | ||
CatalogId: !Ref AWS::AccountId | ||
DatabaseName: !Ref GlueDatabase | ||
TableInput: | ||
Description: >- | ||
Organization trail logs partitioned for our most-used accounts | ||
and regions | ||
Name: common-accounts-and-regions | ||
Parameters: | ||
projection.enabled: "true" | ||
projection.date.type: date | ||
projection.date.range: 2021/01/01,NOW | ||
projection.date.format: yyyy/MM/dd | ||
projection.date.interval: "1" | ||
projection.date.interval.unit: DAYS | ||
projection.region.type: enum | ||
projection.region.values: !Join [",", !Ref ProjectionRegions] | ||
projection.account.type: enum | ||
projection.account.values: !Join [",", !Ref ProjectionAccounts] | ||
storage.location.template: !Sub s3://${CloudTrailS3Bucket}/AWSLogs/${OrganizationId}/${!account}/CloudTrail/${!region}/${!date} | ||
PartitionKeys: | ||
- Name: date | ||
Type: string | ||
- Name: region | ||
Type: string | ||
- Name: account | ||
Type: string | ||
StorageDescriptor: | ||
Columns: | ||
- Name: eventVersion | ||
Type: string | ||
- Name: userIdentity | ||
Type: struct<type:string,principalId:string,arn:string,accountId:string,invokedBy:string,accessKeyId:string,userName:string,sessionContext:struct<attributes:struct<mfaAuthenticated:string,creationDate:string>,sessionIssuer:struct<type:string,principalId:string,arn:string,accountId:string,userName:string>>> | ||
- Name: eventTime | ||
Type: string | ||
- Name: eventSource | ||
Type: string | ||
- Name: eventName | ||
Type: string | ||
- Name: awsRegion | ||
Type: string | ||
- Name: sourceIpAddress | ||
Type: string | ||
- Name: userAgent | ||
Type: string | ||
- Name: errorCode | ||
Type: string | ||
- Name: errorMessage | ||
Type: string | ||
- Name: requestParameters | ||
Type: string | ||
- Name: responseElements | ||
Type: string | ||
- Name: additionalEventData | ||
Type: string | ||
- Name: requestId | ||
Type: string | ||
- Name: eventId | ||
Type: string | ||
- Name: readOnly | ||
Type: string | ||
- Name: resources | ||
Type: array<struct<arn:string,accountId:string,type:string>> | ||
- Name: eventType | ||
Type: string | ||
- Name: apiVersion | ||
Type: string | ||
- Name: recipientAccountId | ||
Type: string | ||
- Name: serviceEventDetails | ||
Type: string | ||
- Name: sharedEventID | ||
Type: string | ||
- Name: vpcEndpointId | ||
Type: string | ||
InputFormat: com.amazon.emr.cloudtrail.CloudTrailInputFormat | ||
Location: !Sub s3://${CloudTrailS3Bucket}/AWSLogs/${OrganizationId} | ||
OutputFormat: org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat | ||
SerdeInfo: | ||
SerializationLibrary: com.amazon.emr.hive.serde.CloudTrailSerde | ||
TableType: EXTERNAL_TABLE | ||
ErrorQuery: | ||
Type: AWS::Athena::NamedQuery | ||
Properties: | ||
Database: !Ref GlueDatabase | ||
Description: >- | ||
Searches for events with any error code | ||
Name: Organization Trail error codes | ||
QueryString: !Sub | | ||
SELECT | ||
eventTime AS "Time", | ||
account AS "Trail Account", | ||
recipientAccountId AS "Recipient Account", | ||
userIdentity.accountId AS "Identity Account", | ||
awsRegion AS "Target Region", | ||
eventType AS "Type", | ||
eventSource AS "Service", | ||
eventName AS "Action", | ||
userIdentity.type AS "Identity Type", | ||
userIdentity.arn AS "Identity ARN", | ||
userIdentity.invokedBy AS "Invoked By", | ||
errorCode AS "Error Code", | ||
errorMessage AS "Error Message" | ||
FROM "${GlueDatabase}"."${CommonTrailTable}" | ||
WHERE "date" >= date_format(current_date - interval '1' day, '%Y/%m/%d') | ||
AND "eventtime" >= to_iso8601(current_timestamp - interval '1' hour) | ||
AND "account" = '000000000000' | ||
AND errorCode <> '' | ||
AND errorMessage <> 'No updates are to be performed.' | ||
AND errorMessage <> 'The specified log group already exists' | ||
ORDER BY eventTime DESC | ||
FullScanQuery: | ||
Type: AWS::Athena::NamedQuery | ||
Properties: | ||
Database: !Ref GlueDatabase | ||
Description: >- | ||
Scans all recent events for a given account | ||
Name: Organization Trail basic scan | ||
QueryString: !Sub | | ||
SELECT | ||
eventTime AS "Time", | ||
account AS "Trail Account", | ||
recipientAccountId AS "Recipient Account", | ||
userIdentity.accountId AS "Identity Account", | ||
awsRegion AS "Target Region", | ||
eventType AS "Type", | ||
eventSource AS "Service", | ||
eventName AS "Action", | ||
userIdentity.type AS "Identity Type", | ||
userIdentity.arn AS "Identity ARN", | ||
userIdentity.invokedBy AS "Invoked By" | ||
FROM "${GlueDatabase}"."${CommonTrailTable}" | ||
WHERE "date" >= date_format(current_date - interval '1' day, '%Y/%m/%d') | ||
AND "eventTime" >= to_iso8601(current_timestamp - interval '1' hour) | ||
AND "account" = '000000000000' | ||
ORDER BY eventTime ASC | ||
WriteOperationScanQuery: | ||
Type: AWS::Athena::NamedQuery | ||
Properties: | ||
Database: !Ref GlueDatabase | ||
Description: >- | ||
Scans for write operations (i.e., exclude read-only) for a given account | ||
Name: Organization Trail write operations scan | ||
QueryString: !Sub | | ||
SELECT | ||
eventTime AS "Time", | ||
account AS "Trail Account", | ||
recipientAccountId AS "Recipient Account", | ||
userIdentity.accountId AS "Identity Account", | ||
awsRegion AS "Target Region", | ||
eventType AS "Type", | ||
eventSource AS "Service", | ||
eventName AS "Action", | ||
userIdentity.type AS "Identity Type", | ||
userIdentity.arn AS "Identity ARN", | ||
userIdentity.invokedBy AS "Invoked By" | ||
FROM "${GlueDatabase}"."${CommonTrailTable}" | ||
WHERE "date" >= date_format(current_date - interval '1' day, '%Y/%m/%d') | ||
AND "eventTime" >= to_iso8601(current_timestamp - interval '1' hour) | ||
AND "readOnly" = 'false' | ||
AND "account" = '000000000000' | ||
-- Exclude some common events | ||
-- AND eventName NOT IN ('CreateLogStream') | ||
-- Include only event from SSO users (i.e., humans) | ||
-- AND userIdentity.arn LIKE '%AWSReservedSSO%' | ||
ORDER BY eventTime ASC | ||
ErrorReportQuery: | ||
Type: AWS::Athena::NamedQuery | ||
Properties: | ||
Database: !Ref GlueDatabase | ||
Description: >- | ||
Groups events by errorCode to find high volume errors | ||
Name: Organization Trail error code report | ||
QueryString: !Sub | | ||
SELECT | ||
count(*) AS "Event Count", | ||
errorCode AS "Error Code", | ||
recipientAccountId AS "Recipient Account", | ||
eventType AS "Type", | ||
eventSource AS "Service", | ||
eventName AS "Action", | ||
userIdentity.arn AS "Identity ARN" | ||
FROM "${GlueDatabase}"."${CommonTrailTable}" | ||
WHERE "date" >= date_format(current_date - interval '1' day, '%Y/%m/%d') | ||
AND "eventTime" >= to_iso8601(current_timestamp - interval '1' hour) | ||
AND "account" = '000000000000' | ||
AND errorCode <> '' | ||
GROUP BY | ||
recipientAccountId, | ||
eventType, | ||
eventName, | ||
eventSource, | ||
userIdentity.arn, | ||
errorCode | ||
ORDER BY "Event Count" DESC |