نظارت بر انطباق سیاست CloudFormation: استفاده از CloudTrail و آتنا

مقدمه:
این پست جزئیات اجرای را نشان می دهد که نظارت دارد چه کسی یا چه چیزی منابع AWS ایجاد شده توسط CloudFormation را تغییر داده است. با استفاده از AWS Config ، CloudTrail ، Athena و Lambda می توان تغییرات را ردیابی کرد ، می توان گزارش ها را مورد تجزیه و تحلیل قرار داد و گزارش های مربوط به انطباق را می توان خودکار کرد. داده های جمع آوری شده در آمازون S3 ذخیره می شوند و آن را برای حسابرسی ها و تأیید انطباق در دسترس قرار می دهند.
درباره پروژه:
این پست بر اساس مقاله قبلی من در مورد نظارت بر Drift CloudFormation Stack با استفاده از قوانین پیکربندی AWS ساخته شده است. در این نسخه پیشرفته ، قابلیت های نظارت توسط:
پیگیری فعالیت های کاربر بر روی منابع ابری تأثیر می گذارد.
ورود به سیستم جزئیات تغییر آمازون S3 از طریق CloudTrail.
گزارش های پردازش و پرس و جو با استفاده از AWS آتنا.
ترمیم خودکار از طریق مدیر سیستم AWS و لامبدا.
اجزای اصلی:
قانون پیکربندی AWS: مانیتور برای رانش با استفاده از CloudFormation_stack_Drift_Detection_Check.
مدیر سیستم اتوماسیون مدیر سیستم: یک عملکرد لامبدا را برای بررسی های مربوط به انطباق فراخوانی می کند.
اقدام اصلاح: سند اتوماسیون InvokeLambdafromConfig را اجرا می کند.
سطل آمازون S3: فروشگاه ها از نتایج CloudTrail و Athena Query.
میز آتنا: داده های ورود به سیستم خام را سازماندهی و نمایش داده می کند.
مسیر CloudTrail: پرونده های فعالیت AWS API را ضبط می کند.
عملکرد لامبدا: نامهای منابع CloudFormation را برای تغییرات اخیر آتنا استخراج می کند.
زیرساخت Scema:
پیکربندی در infrastructure/monitoring_stack_cloudtrail.yaml
الگوی ابری:
AWSTemplateFormatVersion: '2010-09-09'
Description: CloudTrail setup for monitoring CFN stack modifications
Parameters:
AthenaDatabaseName:
Type: String
Description: Athena database name for running queries
Default: 'cloudtrail_logs'
StackNameToMonitor:
Type: String
Description: CloudFormation stack name to monitor
Default: 'base-infrastructure'
MaximumExecutionFrequency:
Type: String
Description: The maximum frequency with which drift in CloudFormation stacks need to be evaluated
Default: 'One_Hour'
Resources:
#################################
# CloudTrail and Athena
#################################
CloudTrailLogsBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub "aws-cloudtrail-logs-${AWS::AccountId}"
VersioningConfiguration:
Status: Enabled
LifecycleConfiguration:
Rules:
- Id: ExpireLogs
Status: Enabled
ExpirationInDays: 365
CloudTrailLogsBucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref CloudTrailLogsBucket
PolicyDocument:
Version: "2012-10-17"
Statement:
- Sid: "AWSCloudTrailAclCheck"
Effect: Allow
Principal:
Service: cloudtrail.amazonaws.com
Action: s3:GetBucketAcl
Resource: !Sub "arn:${AWS::Partition}:s3:::aws-cloudtrail-logs-${AWS::AccountId}"
Condition:
StringEquals:
AWS:SourceArn: !Sub "arn:${AWS::Partition}:cloudtrail:${AWS::Region}:${AWS::AccountId}:trail/monitoring-cfn-policy-compliance"
- Sid: "AWSCloudTrailWrite"
Effect: Allow
Principal:
Service: cloudtrail.amazonaws.com
Action: s3:PutObject
Resource: !Sub "arn:${AWS::Partition}:s3:::aws-cloudtrail-logs-${AWS::AccountId}/AWSLogs/${AWS::AccountId}/*"
Condition:
StringEquals:
AWS:SourceArn: !Sub "arn:${AWS::Partition}:cloudtrail:${AWS::Region}:${AWS::AccountId}:trail/monitoring-cfn-policy-compliance"
s3:x-amz-acl: "bucket-owner-full-control"
- Sid: "AthenaQueryResultPutObject"
Effect: Allow
Principal:
Service: athena.amazonaws.com
Action: s3:PutObject
Resource: !Sub "arn:${AWS::Partition}:s3:::aws-cloudtrail-logs-${AWS::AccountId}/athena-results/*"
Condition:
StringEquals:
aws:SourceArn: !Sub "arn:${AWS::Partition}:athena:${AWS::Region}:${AWS::AccountId}:workgroup/primary"
CloudTrail:
Type: AWS::CloudTrail::Trail
Properties:
TrailName: monitoring-cfn-policy-compliance
S3BucketName: !Ref CloudTrailLogsBucket
IncludeGlobalServiceEvents: true
IsMultiRegionTrail: true
EnableLogFileValidation: false
IsOrganizationTrail: false
IsLogging: true
AthenaDatabase:
Type: AWS::Glue::Database
Properties:
CatalogId: !Ref AWS::AccountId
DatabaseInput:
Name: !Ref AthenaDatabaseName
AthenaTable:
Type: AWS::Glue::Table
Properties:
CatalogId: !Ref AWS::AccountId
DatabaseName: !Ref AthenaDatabase
TableInput:
Name: !Sub "aws_cloudtrail_logs_${AWS::AccountId}"
TableType: EXTERNAL_TABLE
Parameters:
classification: cloudtrail
StorageDescriptor:
Columns:
- Name: eventVersion
Type: string
- Name: userIdentity
Type: struct,sessionIssuer:struct,ec2RoleDelivery:string,webIdFederationData:struct>>>
- 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: resources
Type: array>
- Name: eventType
Type: string
- Name: apiVersion
Type: string
- Name: readOnly
Type: string
- Name: recipientAccountId
Type: string
- Name: serviceEventDetails
Type: string
- Name: sharedEventID
Type: string
- Name: vpcEndpointId
Type: string
- Name: tlsDetails
Type: struct
Location: !Sub "s3://aws-cloudtrail-logs-${AWS::AccountId}/AWSLogs/${AWS::AccountId}/CloudTrail/"
InputFormat: com.amazon.emr.cloudtrail.CloudTrailInputFormat
OutputFormat: org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat
SerdeInfo:
SerializationLibrary: org.apache.hive.hcatalog.data.JsonSerDe
#################################
# Lambda function
#################################
LambdaExecutionRole:
Type: AWS::IAM::Role
Properties:
RoleName: LambdaAthenaQueryExecutionRole
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
- athena.amazonaws.com
Action:
- sts:AssumeRole
Policies:
- PolicyName: CloudFormationDescribe
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- cloudformation:DescribeStackResources
Resource: "arn:aws:cloudformation:*"
- PolicyName: AthenaQueryPolicy
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- athena:StartQueryExecution
- athena:GetQueryExecution
- athena:GetQueryResults
- athena:GetWorkGroup
- athena:GetDataCatalog
- athena:GetTableMetadata
- glue:GetDatabase
- glue:GetTable
- glue:GetPartitions
Resource: "*"
- Effect: Allow
Action:
- s3:PutObject
- s3:GetObject
- s3:ListBucket
- s3:GetBucketLocation
- s3:PutObjectAcl
Resource:
- !Sub "arn:${AWS::Partition}:s3:::${CloudTrailLogsBucket}"
- !Sub "arn:${AWS::Partition}:s3:::${CloudTrailLogsBucket}/*"
- Effect: Allow
Action:
- lambda:AddPermission
Resource: "*"
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource: "*"
CheckCloudTrailLogsLambda:
Type: AWS::Lambda::Function
Properties:
FunctionName: CheckCloudTrailLogsLambda
Runtime: nodejs22.x
Handler: index.handler
Role: !GetAtt LambdaExecutionRole.Arn
Timeout: 120
MemorySize: 256
Environment:
Variables:
STACKS_TO_MONITOR: !Ref StackNameToMonitor
ATHENA_DATABASE: !Ref AthenaDatabase
ATHENA_TABLE: !Ref AthenaTable
S3_OUTPUT_BUCKET: !Ref CloudTrailLogsBucket
Code:
ZipFile: |
const { AthenaClient, StartQueryExecutionCommand } = require("@aws-sdk/client-athena");
const { CloudFormationClient, DescribeStackResourcesCommand } = require("@aws-sdk/client-cloudformation");
const athena = new AthenaClient({});
const cloudformation = new CloudFormationClient({});
exports.handler = async (event) => {
console.log("Event received:", JSON.stringify(event, null, 2));
const stacks = process.env.STACKS_TO_MONITOR.split(",");
const tableName = process.env.ATHENA_TABLE;
const databaseName = process.env.ATHENA_DATABASE;
const s3Bucket = process.env.S3_OUTPUT_BUCKET;
let resourceNames = [];
// Extract resource names from the CloudFormation stacks
for (const stack of stacks) {
const stackResources = await cloudformation.send(
new DescribeStackResourcesCommand({ StackName: stack })
);
stackResources.StackResources.forEach(resource => {
if (resource.PhysicalResourceId) {
resourceNames.push(resource.PhysicalResourceId);
}
});
}
// Construct Athena query
let whereClause = resourceNames.map(name => `resource.arn LIKE '%${name}%'`).join(" OR ");
let queryString = `
SELECT
userIdentity.userName AS username,
eventName AS action,
eventTime AS timestamp,
resource.arn AS resource_arn,
sourceIPAddress AS request_source,
userAgent AS user_agent
FROM ${tableName}
CROSS JOIN UNNEST(resources) AS t(resource)
WHERE (${whereClause})
AND eventName IS NOT NULL
AND userIdentity.userName IS NOT NULL
AND from_iso8601_timestamp(eventTime) >= current_timestamp - INTERVAL '1' HOUR
ORDER BY from_iso8601_timestamp(eventTime) DESC;
`;
// Run the Athena query
const params = {
QueryString: queryString,
QueryExecutionContext: { Database: databaseName },
ResultConfiguration: { OutputLocation: `s3://${s3Bucket}/athena-results/` }
};
try {
const command = new StartQueryExecutionCommand(params);
const queryExecution = await athena.send(command);
console.log("Query started:", queryExecution.QueryExecutionId);
return { status: "Query started successfully", queryExecutionId: queryExecution.QueryExecutionId };
} catch (error) {
console.error("Error running query:", error);
throw error;
}
};
LambdaPermissionForConfig:
Type: AWS::Lambda::Permission
Properties:
FunctionName: !Ref CheckCloudTrailLogsLambda
Action: lambda:InvokeFunction
Principal: config.amazonaws.com
#################################
# Config Rule
#################################
IamRoleForConfig2:
Type: AWS::IAM::Role
Properties:
RoleName: CfnDriftDetectionForCloudTrail
Description: IAM role for AWS Config to access CloudFormation drift detection
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: config.amazonaws.com
Action:
- sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/ReadOnlyAccess
Policies:
- PolicyName: CloudFormationDriftDetectionpolicy
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- cloudformation:DetectStackResourceDrift
- cloudformation:DetectStackDrift
- cloudformation:DescribeStacks
- cloudformation:DescribeStackResources
- cloudformation:BatchDescribeTypeConfigurations
- cloudformation:DescribeStackResourceDrifts
- cloudformation:DescribeStackDriftDetectionStatus
Resource: !Sub "arn:${AWS::Partition}:cloudformation:${AWS::Region}:${AWS::AccountId}:*"
ConfigRuleCheckCloudTralLogs:
DependsOn:
- LambdaPermissionForConfig
Type: AWS::Config::ConfigRule
Properties:
ConfigRuleName: ConfigRuleCheckCloudTrailLogs
Description: AWS Config rule to detect drift in CFN stacks and check CloudTrail logs
Scope:
TagKey: stack-name
TagValue: !Ref StackNameToMonitor
Source:
Owner: AWS
SourceIdentifier: CLOUDFORMATION_STACK_DRIFT_DETECTION_CHECK
MaximumExecutionFrequency: !Ref MaximumExecutionFrequency
InputParameters:
cloudformationRoleArn: !GetAtt IamRoleForConfig2.Arn
IamRoleForRemediation:
Type: AWS::IAM::Role
Properties:
RoleName: AwsConfigRemediationActionInvokeLambda
Description: IAM role for AWS Config remediation action to invoke Lambda function
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- config.amazonaws.com
- ssm.amazonaws.com
Action:
- sts:AssumeRole
Policies:
- PolicyName: InvokeLambdaPolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- lambda:InvokeFunction
Resource: !GetAtt CheckCloudTrailLogsLambda.Arn
SsmDocumentInvokeLambda:
Type: AWS::SSM::Document
Properties:
DocumentType: Automation
Name: InvokeLambdaFromConfig
Content:
schemaVersion: "0.3"
description: "SSM Automation document to invoke a Lambda function"
parameters:
AutomationAssumeRole:
type: String
description: (Optional) The ARN of the role that allows Automation to perform the actions
default: !GetAtt IamRoleForRemediation.Arn
mainSteps:
- name: InvokeLambda
action: aws:invokeLambdaFunction
inputs:
FunctionName: !Ref CheckCloudTrailLogsLambda
Payload: '{}'
InvocationType: Event
LogType: None
maxAttempts: 2
timeoutSeconds: 30
onFailure: Abort
isCritical: true
assumeRole: !GetAtt IamRoleForRemediation.Arn
RemediationActionInvokeLambda:
Type: AWS::Config::RemediationConfiguration
Properties:
ConfigRuleName: !Ref ConfigRuleCheckCloudTralLogs
TargetType: SSM_DOCUMENT
TargetId: !Ref SsmDocumentInvokeLambda
Automatic: true
MaximumAutomaticAttempts: 2
RetryAttemptSeconds: 30
Parameters:
AutomationAssumeRole:
StaticValue:
Values:
- !GetAtt IamRoleForRemediation.Arn
پیش نیازها:
اطمینان حاصل کنید که پیش نیازهای زیر وجود دارد:
- یک حساب AWS با مجوزهای کافی برای ایجاد و مدیریت منابع.
- AWS CLI روی دستگاه محلی نصب شده است.
- زیرساخت های CloudFormation از پست قبلی من (در صورت وجود) مستقر شده است.
استقرار:
- پشته CloudFormation را مستقر کنید.
aws cloudformation create-stack \
--stack-name monitoring-policy-compliance \
--template-body file://infrastructure/monitoring_stack_cloudtrail.yaml \
--capabilities CAPABILITY_NAMED_IAM \
--disable-rollback
2. منابع مستقر شده با پشته. مقدار منبع را از پشته زیرساخت پایه تغییر داده و قانون تشخیص رانش را برای تأیید عملکرد ارزیابی کنید.
aws ssm put-parameter --name "ConnectionToken" --value "secret_token_value_2" --type "String" --overwrite
aws configservice start-config-rules-evaluation --config-rule-names ConfigRuleCheckCloudTrailLogs
3. بعد از اجرای تشخیص رانش ، نتایج پرس و جو آتنا را که در S3 تحت /و نتایج آتنا در پرونده .csv ذخیره شده است ، بررسی کنید.
aws s3 ls s3:///athena-results/ --recursive
aws s3 cp s3:///.csv ./
در اینجا نمونه ای از سیاهههای مربوط به این پرونده آورده شده است:
)
منابع 4.Cleanup. پس از آزمایش ، ورود به سیستم CloudTrail Trail را متوقف کنید ، تمام داده ها را از سطل S3 حذف کنید و پشته CloudFormation را حذف کنید.
aws cloudtrail stop-logging --name monitor-cfn-policy-compliance
aws s3 rm s3:// --recursive
aws cloudformation delete-stack --stack-name monitoring-policy-compliance
نتیجه گیری:
اجرای این راه حل ، دید در تغییرات تأثیرگذار بر منابع مدیریت ابر را فراهم می کند. این امر باعث افزایش امنیت ، پیگیری انطباق و آمادگی حسابرسی می شود. امکان ورود و پرس و جو از اقدامات کاربر ، پاسخ به درخواست های مربوط به پیروی از مشتریان ، تنظیم کننده ها یا تیم های امنیتی را ساده می کند.
اگر این پست را مفید و جالب دیدید ، لطفاً روی دکمه واکنش زیر کلیک کنید تا پشتیبانی خود را نشان دهید. در صورت تمایل به استفاده و به اشتراک گذاشتن این پست. 🙂