Logging in a multi-account AWS environment

Updated: Aug 30

Collecting and storing different types of logs are crucial for security and compliance, especially when we deal with such standards as HIPAA, PCI DSS and others.

When we build a secure multi-account infrastructure with AWS Control Tower, we get a “Log Archive” account in the initial setup. There are many AWS services that can generate logs, such as Cloudtrail logs, AWS Config logs, VPC Flow Logs, Access logs from ELB, API Gateway or CloudFront, DNS logs, WAF logs, application logs (lambdas, containers, servers, etc). These log files allow administrators and auditors to review actions and events that have occurred.


The “Log Archive” account works as a repository for logs of API activities and resource configurations from all accounts in the landing zone. Configuring cross-account logging may be not straightforward and takes some time. In this post we will look at various sources of logs, bucket policies for dedicated S3 buckets to allow cross-account access, log formats and some limitations. Automat-IT landing zone solution is a preconfigured solution using AWS and Automat-IT’s best practices. This allows launching a Landing Zone solution in days rather than weeks. Below is the high-level diagram of centralized logging in the Automat-IT Landing Zone solution.

AWS Cloudtrail and AWS Config logs

AWS Control Tower is integrated with AWS CloudTrail, a service that provides a record of actions taken by a user, role, or an AWS service in AWS Control Tower. CloudTrail captures actions for AWS Control Tower as events. If you create a trail, you can enable continuous delivery of CloudTrail events to an Amazon S3 bucket, including events for AWS Control Tower. Cloudtrail and AWS Config logs are automatically shipped from all accounts into Log Archive if you use AWS Control Tower. No extra effort is required.


Below is a policy for S3 bucket that allows only AWS Cloudtrail and AWS Config services to put log files:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AllowSSLRequestsOnly",
            "Effect": "Deny",
            "Principal": "*",
            "Action": "s3:*",
            "Resource": [
"arn:aws:s3:::aws-controltower-logs-3**********0-us-east-1",
"arn:aws:s3:::aws-controltower-logs-3**********0-us-east-1/*"
            ],
            "Condition": {
                "Bool": {
                    "aws:SecureTransport": "false"
                }
            }
        },
        {
            "Sid": "AWSBucketPermissionsCheck",
            "Effect": "Allow",
            "Principal": {
                "Service": [
                     "cloudtrail.amazonaws.com",
                     "config.amazonaws.com"
                ]
            },
            "Action": "s3:GetBucketAcl",
            "Resource": "arn:aws:s3:::aws-controltower-logs-3**********0-us-east-1"
        },
        {
            "Sid": "AWSConfigBucketExistenceCheck",
            "Effect": "Allow",
            "Principal": {
                "Service": [
                     "cloudtrail.amazonaws.com",
                     "config.amazonaws.com"
                ]
            },
            "Action": "s3:ListBucket",
            "Resource": "arn:aws:s3:::aws-controltower-logs-3**********0-us-east-1"
        },
        {
            "Sid": "AWSBucketDelivery",
            "Effect": "Allow",
            "Principal": {
                "Service": [
                     "cloudtrail.amazonaws.com",
                     "config.amazonaws.com"
                ]
            },
            "Action": "s3:PutObject",
            "Resource": "arn:aws:s3:::aws-controltower-logs-3**********0-us-east-1/o-r********y/AWSLogs/*/*"
        }
    ]
}

The following example shows a CloudTrail log entry that shows the structure of a typical log file entry for an event, including a record of the identity of the user/role/service which initiated the action.

{
   "Records":[
      {
         "eventVersion":"1.08",
         "userIdentity":{
            "type":"AWSService",
            "invokedBy":"eks.amazonaws.com"
         },
         "eventTime":"2021-12-02T00:05:13Z",
         "eventSource":"kms.amazonaws.com",
         "eventName":"Encrypt",
         "awsRegion":"ap-southeast-2",
         "sourceIPAddress":"eks.amazonaws.com",
         "userAgent":"eks.amazonaws.com",
         "requestParameters":{
            "keyId":"arn:aws:kms:ap-southeast-2:3**********9:key/6546e1ba-****",
            "encryptionContext":{
               "aws:eks:context":"ba04801b-***"
            },
            "encryptionAlgorithm":"SYMMETRIC_DEFAULT"
         },
         "responseElements":null,
         "requestID":"350ad2d3-***",
         "eventID":"b679d306-***",
         "readOnly":true,
         "resources":[
            {
               "accountId":"3**********9",
               "type":"AWS::KMS::Key",
               "ARN":"arn:aws:kms:ap-southeast-2:3**********9:key/6546e1ba-****"
            }
         ],
         "eventType":"AwsApiCall",
         "managementEvent":true,
         "recipientAccountId":"3**********9",
         "sharedEventID":"674d532c-***",
         "eventCategory":"Management"
      },
....

AWS Config logs contain a history of configuration changes and periodical compliance checks.


Amazon GuardDuty logs

Amazon GuardDuty master is usually deployed in an “Audit” account. Other AWS accounts within the organization send findings there and all collected logs are periodically exported to S3 bucket in “Log Archive” account.


Below is a policy for S3 bucket that allows only Amazon GuardDuty service to put log files:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AWSLogDeliveryWrite",
            "Effect": "Allow",
            "Principal": {
                "Service": "guardduty.amazonaws.com"
            },
            "Action": "s3:PutObject",
            "Resource": "arn:aws:s3:::aws-guardduty-logs-3**********3/*",
            "Condition": {
                "StringEquals": {
                    "s3:x-amz-acl": "bucket-owner-full-control"
                }
            }
        },
        {
            "Sid": "AWSLogDeliveryAclCheck",
            "Effect": "Allow",
            "Principal": {
                "Service": "guardduty.amazonaws.com"
            },
            "Action": "s3:GetBucketAcl",
            "Resource": "arn:aws:s3:::aws-guardduty-logs-3**********3"
        },
        {
            "Sid": "Allow GuardDuty to use the getBucketLocation operation",
            "Effect": "Allow",
            "Principal": {
                "Service": "guardduty.amazonaws.com"
            },
            "Action": "s3:GetBucketLocation",
            "Resource": "arn:aws:s3:::aws-guardduty-logs-3**********3"
        },
        {
            "Sid": "Deny non-HTTPS access",
            "Effect": "Deny",
            "Principal": "*",
            "Action": "s3:*",
            "Resource": "arn:aws:s3:::aws-guardduty-logs-3**********3/*",
            "Condition": {
                "Bool": {
                    "aws:SecureTransport": "false"
                }
            }
        },
        {
            "Sid": "Deny unencrypted object uploads",
            "Effect": "Deny",
            "Principal": {
                "Service": "guardduty.amazonaws.com"
            },
            "Action": "s3:PutObject",
            "Resource": "arn:aws:s3:::aws-guardduty-logs-3**********3/*",
            "Condition": {
                "StringNotEquals": {
                    "s3:x-amz-server-side-encryption": "aws:kms"
                }
            }
        }
    ]
}

A log format for a GuardDuty finding is following:

{
   "schemaVersion":"2.0",
   "accountId":"1**********4",
   "region":"us-east-1",
   "partition":"aws",
   "id":"f2bd80adf3df3ffb07810adf7b71a330",
   "arn":"arn:aws:guardduty:us-east-1:1**********4:detector/d2b9d***/finding/f2bd80a***",
   "type":"Policy:IAMUser/RootCredentialUsage",
   "resource":{
      "resourceType":"AccessKey",
      "accessKeyDetails":{
         "accessKeyId":"ASI*****************",
         "principalId":"1**********4",
         "userType":"Root",
         "userName":"research-user"
      }
   },
   "service":{
      "serviceName":"guardduty",
      "detectorId":"d2b9d***",
      "action":{
         "actionType":"AWS_API_CALL",
         "awsApiCallAction":{
            "api":"DescribeCheckSummaries",
            "serviceName":"trustedadvisor.amazonaws.com",
            "callerType":"Remote IP",
            "remoteIpDetails":{
               "ipAddressV4":"77.***.***.***",
               "organization":{
                  "asn":"12***",
                  "asnOrg":"***-Net internet services Ltd.",
                  "isp":"***net",
                  "org":"***net"
               },
               "country":{
                  "countryName":"Israel"
               },
               "city":{
                  "cityName":"Tel Aviv"
               },
               "geoLocation":{
                  "lat":32.****,
                  "lon":34.****
               }
            }