Skip to content

Commit f4a2955

Browse files
IevIeievgeniia ieromenko
andauthored
Updated CloudTrail solution to enable Delegated Administrator (#159)
* updated cloudtrail solution to enable delegated administrator * linting fixes --------- Co-authored-by: ievgeniia ieromenko <ieviero@amazon.com>
1 parent 4122519 commit f4a2955

File tree

9 files changed

+169
-17
lines changed

9 files changed

+169
-17
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## Table of Contents<!-- omit in toc -->
44

55
- [Introduction](#introduction)
6+
- [2023-07-01](#2023-07-07)
67
- [2023-07-01](#2023-07-01)
78
- [2023-06-21](#2023-06-21)
89
- [2023-06-20](#2023-06-20)
@@ -41,6 +42,12 @@ All notable changes to this project will be documented in this file.
4142

4243
---
4344

45+
## 2023-07-07
46+
47+
### Changed<!-- omit in toc -->
48+
49+
- Updated [CloudTrail](https://github.com/aws-samples/aws-security-reference-architecture-examples/tree/main/aws_sra_examples/solutions/cloudtrail/cloudtrail_org) solution to enable delegated administrator.
50+
4451
## 2023-07-01
4552

4653
### Changed<!-- omit in toc -->

aws_sra_examples/solutions/cloudtrail/cloudtrail_org/README.md

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@ Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. SPDX-License-
1212

1313
## Introduction
1414

15-
The Organization CloudTrail solution will create an Organization CloudTrail within the Organization Management Account that is encrypted with a Customer Managed KMS Key managed in the Audit Account and logs delivered to the Log Archive Account. An
16-
Organization CloudTrail logs all events for all AWS accounts in the AWS Organization.
15+
The Organization CloudTrail solution will create an Organization CloudTrail within the Organization Management Account and delegate administration to a member account (e.g. Audit or Security Tooling). The Organization CloudTrail is encrypted with a Customer Managed KMS Key managed in the Audit Account and logs delivered to the Log Archive Account. An Organization CloudTrail logs all events for all AWS accounts in the AWS Organization.
1716

1817
When you create an organization trail, a trail with the name that you give it will be created in every AWS account that belongs to your organization. Users with CloudTrail permissions in member accounts will be able to see this trail when they log
1918
into the AWS CloudTrail console from their AWS accounts, or when they run AWS CLI commands such as describe-trail. However, users in member accounts will not have sufficient permissions to delete the organization trail, turn logging on or off, change
@@ -39,27 +38,34 @@ The solution default configuration deploys an Organization CloudTrail enabling o
3938

4039
- The Lambda Function contains logic for configuring the AWS Organization CloudTrail within the `management account`.
4140

42-
#### 1.3 Lambda Execution IAM Role<!-- omit in toc -->
41+
#### 1.3 Lambda Layer<!-- omit in toc -->
42+
43+
- The python boto3 SDK lambda layer to enable capability for lambda to enable delegated administrator for CloudTrail service.
44+
- This is downloaded during the deployment process and packaged into a layer that is used by the lambda function in this solution.
45+
- The CloudTrail API available in the current lambda environment (as of 06/6/2023) is boto3-1.20.32, however, enhanced functionality of the CloudTrail API used in this solution requires at least 1.26.4 (see references below).
46+
- Note: Future revisions to this solution will remove this layer when boto3 is updated within the lambda environment.
47+
48+
#### 1.4 Lambda Execution IAM Role<!-- omit in toc -->
4349

4450
- The AWS Lambda Function Role allows the AWS Lambda service to assume the role and perform actions defined in the attached IAM policies.
4551

46-
#### 1.4 Lambda CloudWatch Log Group<!-- omit in toc -->
52+
#### 1.5 Lambda CloudWatch Log Group<!-- omit in toc -->
4753

4854
- All the `AWS Lambda Function` logs are sent to a CloudWatch Log Group `</aws/lambda/<LambdaFunctionName>` to help with debugging and traceability of the actions performed.
4955
- By default the `AWS Lambda Function` will create the CloudWatch Log Group with a `Retention` (14 days) and are encrypted with a CloudWatch Logs service managed encryption key.
5056

51-
#### 1.5 Organization CloudTrail<!-- omit in toc -->
57+
#### 1.6 Organization CloudTrail<!-- omit in toc -->
5258

5359
- AWS CloudTrail for all AWS Organization accounts
5460
- Member accounts are automatically added and cannot modify
5561
- Data events can be disabled via the parameters
5662
- CloudWatch logs can be disabled via the parameters
5763

58-
#### 1.6 Organization CloudTrail CloudWatch Log Group Role<!-- omit in toc -->
64+
#### 1.7 Organization CloudTrail CloudWatch Log Group Role<!-- omit in toc -->
5965

6066
- IAM role used to send CloudTrail logs to the CloudWatch log group
6167

62-
#### 1.7 Organization CloudTrail CloudWatch Log Group<!-- omit in toc -->
68+
#### 1.8 Organization CloudTrail CloudWatch Log Group<!-- omit in toc -->
6369

6470
- Contains the CloudTrail logs with a `Retention` (400 days)
6571

@@ -82,6 +88,10 @@ populated from the `SecurityAccountId` parameter within the `AWSControlTowerBP-B
8288

8389
- AWS Secrets Manager secret containing the customer managed KMS key ARN
8490

91+
#### 2.4 CloudTrail (Delegated admin)<!-- omit in toc -->
92+
93+
- Delegated administrator account has administrative permissions on all trails and event data stores in the organization.
94+
8595
---
8696

8797
### 3.0 Security Log Archive Account<!-- omit in toc -->
@@ -185,3 +195,6 @@ In the `management account (home region)`, launch an AWS CloudFormation **Stack*
185195

186196
- [Creating a CloudTrail for the Organization](https://docs.aws.amazon.com/awscloudtrail/latest/userguide/creating-trail-organization.html)
187197
- [Allowing Cross-Account Access to a KMS Key](https://docs.aws.amazon.com/kms/latest/developerguide/key-policy-modifying-external-accounts.html)
198+
- [Organization delegated administrator](https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-delegated-administrator.html)
199+
- [Lambda runtimes](https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html)
200+
- [Python Boto3 SDK changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
Loading

aws_sra_examples/solutions/cloudtrail/cloudtrail_org/lambda/src/app.py

Lines changed: 90 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from aws_lambda_typing.events import CloudFormationCustomResourceEvent
2424
from mypy_boto3_cloudtrail.client import CloudTrailClient
2525
from mypy_boto3_cloudtrail.type_defs import DataResourceTypeDef, EventSelectorTypeDef
26+
from mypy_boto3_organizations.client import OrganizationsClient
2627

2728
# Setup Default Logger
2829
LOGGER = logging.getLogger(__name__)
@@ -39,11 +40,80 @@
3940
try:
4041
management_account_session = boto3.Session()
4142
CLOUDTRAIL_CLIENT: CloudTrailClient = management_account_session.client("cloudtrail", config=BOTO3_CONFIG)
43+
ORG_CLIENT: OrganizationsClient = management_account_session.client("organizations")
4244
except Exception:
4345
LOGGER.exception(UNEXPECTED)
4446
raise ValueError("Unexpected error executing Lambda function. Review CloudWatch logs for details.") from None
4547

4648

49+
def list_delegated_administrator(delegated_admin_account_id: str, service_principal: str) -> None:
50+
"""Check if the delegated administrator account for the provided service principal exists.
51+
52+
Args:
53+
delegated_admin_account_id: Delegated Administrator Account ID
54+
service_principal: AWS Service Principal
55+
56+
Raises:
57+
ValueError: Error registering the delegated administrator account
58+
"""
59+
LOGGER.info(f"Checking if delegated administrator already registered for: {service_principal}")
60+
61+
try:
62+
delegated_administrators = ORG_CLIENT.list_delegated_administrators(ServicePrincipal=service_principal)
63+
64+
if not delegated_administrators:
65+
LOGGER.info(f"The delegated administrator {service_principal} was not registered")
66+
raise ValueError("Error registering the delegated administrator account")
67+
except ORG_CLIENT.exceptions.AccountAlreadyRegisteredException:
68+
LOGGER.debug(f"Account: {delegated_admin_account_id} already registered for {service_principal}")
69+
70+
71+
def set_delegated_admin(delegated_admin_account_id: str) -> None:
72+
"""Set the delegated admin account.
73+
74+
Args:
75+
delegated_admin_account_id: Admin account ID
76+
77+
Raises:
78+
Exception: raises exception as e
79+
"""
80+
try:
81+
delegated_admin_response = CLOUDTRAIL_CLIENT.register_organization_delegated_admin(MemberAccountId=delegated_admin_account_id)
82+
api_call_details = {"API_Call": "cloudtrail:RegisterOrganizationDelegatedAdmin", "API_Response": delegated_admin_response}
83+
LOGGER.info(api_call_details)
84+
LOGGER.info(f"Delegated admin ({delegated_admin_account_id}) enabled")
85+
except CLOUDTRAIL_CLIENT.exceptions.AccountRegisteredException:
86+
LOGGER.info("Delegated admin already registered")
87+
except Exception as e:
88+
LOGGER.error(f"Failed to enable delegated admin. {e}")
89+
raise
90+
91+
92+
def deregister_delegated_administrator(delegated_admin_account_id: str, service_principal: str) -> None:
93+
"""Deregister the delegated administrator account for the provided service principal.
94+
95+
Args:
96+
delegated_admin_account_id: Delegated Administrator Account ID
97+
service_principal: AWS Service Principal format: service_name.amazonaws.com
98+
99+
"""
100+
LOGGER.info(f"Deregistering AWS Service Access for: {service_principal}")
101+
102+
try:
103+
delegated_admin_response = CLOUDTRAIL_CLIENT.deregister_organization_delegated_admin(DelegatedAdminAccountId=delegated_admin_account_id)
104+
api_call_details = {"API_Call": "cloudtrail:DeregisterOrganizationDelegatedAdmin", "API_Response": delegated_admin_response}
105+
LOGGER.info(api_call_details)
106+
LOGGER.info(f"Delegated admin ({delegated_admin_account_id}) deregistered")
107+
delegated_administrators = ORG_CLIENT.list_delegated_administrators(ServicePrincipal=service_principal)
108+
109+
LOGGER.debug(str(delegated_administrators))
110+
111+
if not delegated_administrators:
112+
LOGGER.info(f"The deregister was successful for the {service_principal} delegated administrator")
113+
except ORG_CLIENT.exceptions.AccountNotRegisteredException:
114+
LOGGER.info(f"Account: {delegated_admin_account_id} not registered for {service_principal}")
115+
116+
47117
def get_data_event_config(
48118
aws_partition: str, enable_data_events_only: bool, enable_s3_data_events: bool, enable_lambda_data_events: bool
49119
) -> EventSelectorTypeDef:
@@ -72,19 +142,20 @@ def get_data_event_config(
72142
"IncludeManagementEvents": True,
73143
"DataResources": [],
74144
}
75-
145+
event_list: list = []
76146
if enable_s3_data_events:
77147
s3_data_resource: DataResourceTypeDef = {"Type": "AWS::S3::Object", "Values": [f"arn:{aws_partition}:s3:::"]}
78-
event_selectors["DataResources"].append(s3_data_resource)
148+
event_list.append(s3_data_resource)
79149
LOGGER.info("S3 Data Events Added to Event Selectors")
80150

81151
if enable_lambda_data_events:
82152
lambda_data_resource: DataResourceTypeDef = {
83153
"Type": "AWS::Lambda::Function",
84154
"Values": [f"arn:{aws_partition}:lambda"],
85155
}
86-
event_selectors["DataResources"].append(lambda_data_resource)
156+
event_list.append(lambda_data_resource)
87157
LOGGER.info("Lambda Data Events Added to Event Selectors")
158+
event_selectors["DataResources"] = event_list
88159

89160
return event_selectors
90161

@@ -173,9 +244,14 @@ def get_validated_parameters(event: CloudFormationCustomResourceEvent) -> dict:
173244
)
174245
parameter_pattern_validator("S3_BUCKET_NAME", params.get("S3_BUCKET_NAME"), pattern=r"^[0-9a-zA-Z]+([0-9a-zA-Z-]*[0-9a-zA-Z])*$")
175246
parameter_pattern_validator("SRA_SOLUTION_NAME", params.get("SRA_SOLUTION_NAME"), pattern=r"^.{1,256}$")
176-
parameter_pattern_validator("ENABLE_S3_DATA_EVENTS", params.get("ENABLE_S3_DATA_EVENTS"), pattern=true_false_pattern)
177-
parameter_pattern_validator("ENABLE_LAMBDA_DATA_EVENTS", params.get("ENABLE_LAMBDA_DATA_EVENTS"), pattern=true_false_pattern)
247+
parameter_pattern_validator(
248+
"DELEGATED_ADMIN_ACCOUNT_ID",
249+
params.get("DELEGATED_ADMIN_ACCOUNT_ID"),
250+
pattern=r"^\d{12}$",
251+
)
178252
parameter_pattern_validator("ENABLE_DATA_EVENTS_ONLY", params.get("ENABLE_DATA_EVENTS_ONLY"), pattern=true_false_pattern)
253+
parameter_pattern_validator("ENABLE_LAMBDA_DATA_EVENTS", params.get("ENABLE_LAMBDA_DATA_EVENTS"), pattern=true_false_pattern)
254+
parameter_pattern_validator("ENABLE_S3_DATA_EVENTS", params.get("ENABLE_S3_DATA_EVENTS"), pattern=true_false_pattern)
179255

180256
if params.get("CLOUDWATCH_LOG_GROUP_ARN", "") or params.get("CLOUDWATCH_LOG_GROUP_ROLE_ARN", ""):
181257
parameter_pattern_validator(
@@ -200,6 +276,8 @@ def process_create_update(params: dict) -> None:
200276
params: parameters
201277
"""
202278
enable_aws_service_access(AWS_SERVICE_PRINCIPAL)
279+
list_delegated_administrator(params["DELEGATED_ADMIN_ACCOUNT_ID"], AWS_SERVICE_PRINCIPAL)
280+
set_delegated_admin(params["DELEGATED_ADMIN_ACCOUNT_ID"])
203281
cloudtrail_params = {
204282
"cloudtrail_name": params["CLOUDTRAIL_NAME"],
205283
"cloudwatch_log_group_arn": params["CLOUDWATCH_LOG_GROUP_ARN"],
@@ -210,19 +288,22 @@ def process_create_update(params: dict) -> None:
210288
}
211289

212290
if params["action"] == "Add":
213-
CLOUDTRAIL_CLIENT.create_trail(**get_cloudtrail_parameters(True, cloudtrail_params))
291+
try:
292+
CLOUDTRAIL_CLIENT.create_trail(**get_cloudtrail_parameters(True, cloudtrail_params))
293+
except CLOUDTRAIL_CLIENT.exceptions.TrailAlreadyExistsException:
294+
LOGGER.info(f"{params['CLOUDTRAIL_NAME']} already exists.")
214295
elif params["action"] == "Update":
215296
CLOUDTRAIL_CLIENT.update_trail(**get_cloudtrail_parameters(False, cloudtrail_params))
216297
LOGGER.info(f"Successful {params['action']} of the Organization CloudTrail")
217298

218299
event_selectors = get_data_event_config(
219300
aws_partition=params.get("AWS_PARTITION", "aws"),
220301
enable_data_events_only=(params.get("ENABLE_DATA_EVENTS_ONLY", "false")).lower() in "true",
221-
enable_s3_data_events=(params.get("ENABLE_S3_DATA_EVENTS", "false")).lower() in "true",
222302
enable_lambda_data_events=(params.get("ENABLE_LAMBDA_DATA_EVENTS", "false")).lower() in "true",
303+
enable_s3_data_events=(params.get("ENABLE_S3_DATA_EVENTS", "false")).lower() in "true",
223304
)
224305

225-
if event_selectors and event_selectors["DataResources"]:
306+
if event_selectors:
226307
CLOUDTRAIL_CLIENT.put_event_selectors(TrailName=params["CLOUDTRAIL_NAME"], EventSelectors=[event_selectors])
227308
LOGGER.info("Data Events Enabled")
228309

@@ -252,6 +333,7 @@ def process_event(event: CloudFormationCustomResourceEvent, context: Context) ->
252333
process_create_update(params)
253334
elif params["action"] == "Remove":
254335
try:
336+
deregister_delegated_administrator(params.get("DELEGATED_ADMIN_ACCOUNT_ID", ""), AWS_SERVICE_PRINCIPAL)
255337
CLOUDTRAIL_CLIENT.delete_trail(Name=params["CLOUDTRAIL_NAME"])
256338
LOGGER.info("Deleted the Organizations CloudTrail")
257339
except CLOUDTRAIL_CLIENT.exceptions.TrailNotFoundException:
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
boto3

aws_sra_examples/solutions/cloudtrail/cloudtrail_org/templates/sra-cloudtrail-org-main-ssm.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,7 @@ Resources:
301301
pCloudTrailS3BucketName: !Sub '{{resolve:secretsmanager:arn:${AWS::Partition}:secretsmanager:${AWS::Region}:${pLogArchiveAccountId}:secret:sra/cloudtrail_org_s3_bucket:SecretString:OrganizationCloudTrailS3Bucket:AWSCURRENT}}'
302302
pCreateCloudTrailLogGroup: !Ref pCreateCloudTrailLogGroup
303303
pCreateLambdaLogGroup: !Ref pCreateLambdaLogGroup
304+
pDelegatedAdminAccountId: !Ref pAuditAccountId
304305
pEnableDataEventsOnly: !Ref pEnableDataEventsOnly
305306
pEnableLambdaDataEvents: !Ref pEnableLambdaDataEvents
306307
pEnableS3DataEvents: !Ref pEnableS3DataEvents

aws_sra_examples/solutions/cloudtrail/cloudtrail_org/templates/sra-cloudtrail-org-main.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,7 @@ Resources:
282282
pCloudTrailS3BucketName: !Sub '{{resolve:secretsmanager:arn:${AWS::Partition}:secretsmanager:${AWS::Region}:${pLogArchiveAccountId}:secret:sra/cloudtrail_org_s3_bucket:SecretString:OrganizationCloudTrailS3Bucket:AWSCURRENT}}'
283283
pCreateCloudTrailLogGroup: !Ref pCreateCloudTrailLogGroup
284284
pCreateLambdaLogGroup: !Ref pCreateLambdaLogGroup
285+
pDelegatedAdminAccountId: !Ref pAuditAccountId
285286
pEnableDataEventsOnly: !Ref pEnableDataEventsOnly
286287
pEnableLambdaDataEvents: !Ref pEnableLambdaDataEvents
287288
pEnableS3DataEvents: !Ref pEnableS3DataEvents

aws_sra_examples/solutions/cloudtrail/cloudtrail_org/templates/sra-cloudtrail-org.yaml

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ Metadata:
4141
- pEnableDataEventsOnly
4242
- pEnableLambdaDataEvents
4343
- pEnableS3DataEvents
44+
- pDelegatedAdminAccountId
4445

4546
- Label:
4647
default: General Lambda Function Properties
@@ -67,6 +68,8 @@ Metadata:
6768
default: Create CloudTrail Log Group
6869
pCreateLambdaLogGroup:
6970
default: Create Lambda Log Group
71+
pDelegatedAdminAccountId:
72+
default: Delegated Admin Account ID
7073
pEnableDataEventsOnly:
7174
default: Enable Data Events Only
7275
pEnableLambdaDataEvents:
@@ -139,6 +142,11 @@ Parameters:
139142
Indicates whether a CloudWatch Log Group should be explicitly created for the Lambda function, to allow for setting a Log Retention and/or KMS
140143
Key for encryption.
141144
Type: String
145+
pDelegatedAdminAccountId:
146+
AllowedPattern: '^\d{12}$'
147+
ConstraintDescription: Must be 12 digits
148+
Description: Delegated administrator account ID
149+
Type: String
142150
pEnableDataEventsOnly:
143151
AllowedValues: ['true', 'false']
144152
Default: 'true'
@@ -324,6 +332,23 @@ Resources:
324332
- cloudtrail:UpdateTrail
325333
Resource: !Sub arn:${AWS::Partition}:cloudtrail:${AWS::Region}:${AWS::AccountId}:trail/*
326334

335+
- Sid: AllowCloudTrailDelegatedAdministrator
336+
Effect: Allow
337+
Action:
338+
- cloudtrail:RegisterOrganizationDelegatedAdmin
339+
- cloudtrail:DeregisterOrganizationDelegatedAdmin
340+
Resource: "*"
341+
342+
- Sid: RegisterDeregisterDelegatedAdministrator
343+
Effect: Allow
344+
Action:
345+
- organizations:DeregisterDelegatedAdministrator
346+
- organizations:RegisterDelegatedAdministrator
347+
Condition:
348+
StringLikeIfExists:
349+
organizations:ServicePrincipal: cloudtrail.amazonaws.com
350+
Resource: !Sub arn:${AWS::Partition}:organizations::*:account/o-*/*
351+
327352
- PolicyName: cloudtrail-org-policy-organization
328353
PolicyDocument:
329354
Version: 2012-10-17
@@ -332,10 +357,19 @@ Resources:
332357
Effect: Allow
333358
Action:
334359
- organizations:DescribeOrganization
335-
- organizations:DisableAWSServiceAccess
336-
- organizations:EnableAWSServiceAccess
337360
- organizations:ListAWSServiceAccessForOrganization
338361
- organizations:ListAccounts
362+
- organizations:ListDelegatedAdministrators
363+
Resource: '*'
364+
365+
- Sid: AWSServiceAccess
366+
Effect: Allow
367+
Action:
368+
- organizations:DisableAWSServiceAccess
369+
- organizations:EnableAWSServiceAccess
370+
Condition:
371+
StringLikeIfExists:
372+
organizations:ServicePrincipal: cloudtrail.amazonaws.com
339373
Resource: '*'
340374

341375
- PolicyName: sra-cloudtrail-org-policy-iam
@@ -408,9 +442,21 @@ Resources:
408442
Environment:
409443
Variables:
410444
LOG_LEVEL: !Ref pLambdaLogLevel
445+
DELEGATED_ADMIN_ACCOUNT_ID: !Ref pDelegatedAdminAccountId
411446
Code:
412447
S3Bucket: !Ref pSRAStagingS3BucketName
413448
S3Key: !Sub ${pSRASolutionName}/lambda_code/${pSRASolutionName}.zip
449+
Layers:
450+
- !Ref rCloudTrailOrgLambdaLayer
451+
452+
rCloudTrailOrgLambdaLayer:
453+
Type: AWS::Lambda::LayerVersion
454+
Properties:
455+
Content:
456+
S3Bucket: !Ref pSRAStagingS3BucketName
457+
S3Key: !Sub ${pSRASolutionName}/layer_code/${pSRASolutionName}-layer.zip
458+
Description: Boto3 version layerto enable CloudTrail delegated admin
459+
LayerName: !Sub ${pCloudTrailLambdaFunctionName}-updated-boto3-layer
414460

415461
rLambdaCustomResource:
416462
Type: Custom::LambdaCustomResource
@@ -427,3 +473,4 @@ Resources:
427473
KMS_KEY_ID: !Ref pOrganizationCloudTrailKMSKeyId
428474
S3_BUCKET_NAME: !Ref pCloudTrailS3BucketName
429475
SRA_SOLUTION_NAME: !Ref pSRASolutionName
476+
DELEGATED_ADMIN_ACCOUNT_ID: !Ref pDelegatedAdminAccountId

0 commit comments

Comments
 (0)