CloudTrail logs API calls to your AWS account, but it doesn’t inherently make them auditable without a bit of help.
Let’s see it in action. Imagine you’ve got an ECS service that’s supposed to be running a specific Docker image. You want to know exactly when that image was last changed and who did it.
Here’s a sample CloudTrail event for an UpdateService API call:
{
"eventVersion": "1.08",
"userIdentity": {
"type": "IAMUser",
"principalId": "AIDAJ4XXXXXXEXAMPLE",
"arn": "arn:aws:iam::123456789012:user/Alice",
"accountId": "123456789012",
"accessKeyId": "AKIAIOSFODNN7EXAMPLE",
"userName": "Alice",
"sessionContext": {
"sessionIssuer": {
"type": "IAMUser",
"principalId": "AIDAJ4XXXXXXEXAMPLE",
"arn": "arn:aws:iam::123456789012:user/Alice",
"accountId": "123456789012",
"userName": "Alice"
},
"webIdFederationData": {},
"attributes": {
"creationDate": "2023-10-27T10:00:00Z",
"mfaAuthenticated": "false"
}
}
},
"eventTime": "2023-10-27T10:15:00Z",
"eventSource": "ecs.amazonaws.com",
"eventName": "UpdateService",
"awsRegion": "us-east-1",
"sourceIPAddress": "203.0.113.1",
"userAgent": "aws-cli/2.13.17 Python/3.11.5 Darwin/23.0.0 source/x86_64 prompt/off command/ecs update-service",
"requestParameters": {
"cluster": "my-ecs-cluster",
"service": "my-web-app",
"taskDefinition": "arn:aws:ecs:us-east-1:123456789012:task-definition/my-web-app:2"
},
"responseElements": {
"service": {
"serviceArn": "arn:aws:ecs:us-east-1:123456789012:service/my-ecs-cluster/my-web-app",
"desiredCount": 3,
"runningCount": 3,
"pendingCount": 0,
"taskDefinition": "arn:aws:ecs:us-east-1:123456789012:task-definition/my-web-app:2",
"status": "ACTIVE",
"deployments": [
{
"id": "d-XXXXXXXXXX",
"status": "ACTIVE",
"taskDefinition": "arn:aws:ecs:us-east-1:123456789012:task-definition/my-web-app:2",
"desiredCount": 3,
"runningCount": 3,
"pendingCount": 0,
"launchType": "FARGATE",
"platformVersion": "1.4.0",
"createdAt": "2023-10-27T10:15:00Z",
"updatedAt": "2023-10-27T10:15:00Z",
"rollbackReason": null,
"networkConfiguration": {
"awsvpcConfiguration": {
"subnets": [
"subnet-xxxxxxxxxxxxxxxxx",
"subnet-yyyyyyyyyyyyyyyyy"
],
"securityGroups": [
"sg-zzzzzzzzzzzzzzzzzz"
],
"assignPublicIp": "DISABLED"
}
}
}
],
"serviceName": "my-web-app",
"createdAt": "2023-10-26T08:00:00Z"
}
},
"requestID": "a1b2c3d4-e5f6-7890-1234-567890abcdef",
"eventCategory": "Data",
"tlsDetails": {
"certifcateId": "abcdefg1234567890",
"sslEnabled": true
}
}
The problem CloudTrail solves is providing a historical record of API activity. The challenge is making that record useful for auditing specific actions like ECS service updates. To do this, you need to understand how CloudTrail captures these events and how to query them effectively.
At its core, CloudTrail is a service that records API calls made on your AWS account. It captures who made the call, when it happened, from where, and what parameters were used. For ECS, this means every CreateService, UpdateService, DeleteService, RegisterTaskDefinition, DeregisterTaskDefinition, and even RunTask calls are logged.
To make this auditable, you need to ensure CloudTrail is enabled for all regions and configured to log management events. For more granular auditing, you can enable data events for specific S3 buckets or Lambda functions, though this is less common for direct ECS API auditing. The real power comes from analyzing these logs. You’ll typically send CloudTrail logs to Amazon S3 for long-term storage and then use Amazon Athena to query them.
Here’s how you might query for all UpdateService events for a specific ECS cluster named my-ecs-cluster in the us-east-1 region, showing who made the change and when:
SELECT
eventTime,
userIdentity.arn AS user_arn,
userIdentity.userName AS user_name,
sourceIPAddress,
requestParameters.cluster,
requestParameters.service,
requestParameters.taskDefinition AS new_task_definition_arn
FROM
"your_cloudtrail_database"."your_cloudtrail_table"
WHERE
eventSource = 'ecs.amazonaws.com'
AND eventName = 'UpdateService'
AND requestParameters.cluster = 'my-ecs-cluster'
AND awsRegion = 'us-east-1'
ORDER BY
eventTime DESC;
This Athena query, run against your CloudTrail logs stored in S3, will give you a clear, chronological list of who updated which ECS service on which cluster, along with the new task definition ARN they specified. This is the raw material for auditing compliance, tracking unauthorized changes, or simply understanding the deployment history of your applications.
The most surprising thing about CloudTrail for auditing is that by default, it logs all API calls, including sensitive ones like UpdateService, but it doesn’t automatically tag or categorize these events in a way that makes them easily searchable for compliance without explicit querying. You have to know what you’re looking for and how to ask for it.
Let’s break down the UpdateService event further:
userIdentity: This is crucial. It tells you who performed the action. Was it an IAM user (likeAlicein the example), an IAM role assumed by another service, or a root user? ThearnanduserName(orroleName) are your primary identifiers.eventTime: The exact timestamp of the API call. Essential for correlating events.eventSource: Identifies the AWS service the API call was made to. Here, it’secs.amazonaws.com.eventName: The specific API operation.UpdateServicein this case.requestParameters: This section details what was changed. ForUpdateService, you see thecluster, theservicename, and importantly, thetaskDefinitionARN that the service was updated to. This is how you track image version changes.awsRegion: The AWS region where the action occurred.sourceIPAddress: The IP address from which the API call was made. Useful for detecting unusual access patterns.responseElements: Provides details about the state of the resource after the operation. While not always directly used for auditing the change, it can confirm the outcome.
The requestParameters.taskDefinition field in an UpdateService event is where you’ll find the specific task definition ARN that was applied. This ARN, like arn:aws:ecs:us-east-1:123456789012:task-definition/my-web-app:2, contains the revision number (the :2 part), which directly corresponds to a specific container image version and configuration. By querying for changes in this field over time, you can audit when and by whom your application’s container images were updated.
The userAgent field often contains details about the client that made the request, such as the AWS CLI version or SDK used. While not always directly auditable, it can provide context about how the API call was initiated, differentiating between automated deployments and manual changes.
When you’re looking at CloudTrail logs in S3 and querying with Athena, remember that each log file is a JSON object, and Athena parses these into a table structure. The requestParameters and responseElements fields are nested JSON objects themselves. To access values within them, you use dot notation (e.g., requestParameters.cluster). If a field might be missing (e.g., not all API calls have rollbackReason), you’ll want to use COALESCE or IS NOT NULL checks in your queries to handle those variations gracefully.
The next step after effectively auditing ECS API calls with CloudTrail is often setting up Amazon GuardDuty for threat detection, which can identify suspicious API activity based on behavioral analysis, complementing your explicit audit trails.