AWSTemplateFormatVersion: 2010-09-09 Description: Configura los controles detectivos y resguardo de bitacoras en la cuenta destino Metadata: AWS::CloudFormation::Interface: ParameterGroups: - Label: default: Artefactos de seguridad Parameters: - AccountPrefix - Label: default: Deteccion de incidencias Parameters: - EnableGuardDuty - EnableMacie - EnableSecurityHub - Label: default: Bitacoras y monitoreo Parameters: - ConfigBucketName - CloudTrailBucketName - CloudTrailKey - CloudWatchLogsKey Parameters: EnableGuardDuty: Type: String Description: Habilitar deteccion de amenazas AllowedValues: ['true','false'] Default: 'true' EnableMacie: Type: String Description: Habilitar deteccion de datos sensibles en buckets de S3 AllowedValues: ['true','false'] Default: 'true' EnableSecurityHub: Type: String Description: Habilitar visualizacion centralizada de hallazgos de seguridad AllowedValues: ['true','false'] Default: 'true' AccountPrefix: Type: AWS::SSM::Parameter::Value Description: Parametro con la abreviatura del nombre de la cuenta Default: /cuenta/prefijo ConfigBucketName: Type: AWS::SSM::Parameter::Value Description: Parametro con el nombre del bucket de AWS Config Default: /seguridad/buckets/bucket-config CloudTrailBucketName: Type: AWS::SSM::Parameter::Value Description: Parametro con el nombre del bucket de AWS CloudTrail Default: /seguridad/buckets/bucket-cloudtrail CloudTrailKey: Type: AWS::SSM::Parameter::Value Description: Parametro con arn de la llave para cifrar CloudTrail Default: '/seguridad/llaves/LlaveCloudTrail' CloudWatchLogsKey: Type: AWS::SSM::Parameter::Value Description: Parametro con arn de la llave para cifrar CloudWatch Logs Default: '/seguridad/llaves/LlaveCloudWatch' Conditions: EnableSecurityHubCondition: !Equals [!Ref EnableSecurityHub, 'true'] EnableGuardDutyCondition: !Equals [!Ref EnableGuardDuty, 'true'] EnableMacieCondition: !Equals [!Ref EnableMacie, 'true'] Resources: # ======================= # # AWS IAM Support Role # # ======================= # IAMSupportRole: Type: AWS::IAM::Role Properties: RoleName: !Sub ${AccountPrefix}-suppport-role AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: AWS: !Sub arn:aws:iam::${AWS::AccountId}:root Action: sts:AssumeRole ManagedPolicyArns: ['arn:aws:iam::aws:policy/AWSSupportAccess'] Metadata: cfn_nag: rules_to_suppress: - id: W28 reason: "Se define un nombre para el rol" # ===================== # # IAM Security Policy # # ===================== # IamPasswordPolicy: Type: 'Custom::PasswordPolicy' Properties: HardExpiry: false AllowUsersToChangePassword: true MaxPasswordAge: 90 MinimumPasswordLength: 14 PasswordReusePrevention: 6 RequireLowercaseCharacters: true RequireNumbers: true RequireSymbols: true RequireUppercaseCharacters: true ServiceToken: 'Fn::GetAtt': - IamPasswordPolicyLambda - Arn IamPasswordPolicyLambdaRole: Type: 'AWS::IAM::Role' Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: lambda.amazonaws.com Action: - 'sts:AssumeRole' Path: / ManagedPolicyArns: - 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole' Policies: - PolicyName: iam PolicyDocument: Statement: - Effect: Allow Action: - 'iam:UpdateAccountPasswordPolicy' - 'iam:DeleteAccountPasswordPolicy' Resource: '*' Metadata: cfn_nag: rules_to_suppress: - id: W11 reason: "El rol aplica sobre la politica de seguridad de IAM" IamPasswordPolicyLambda: Type: 'AWS::Lambda::Function' Properties: ReservedConcurrentExecutions: 5 Code: ZipFile: >- 'use strict'; const AWS = require('aws-sdk'); const response = require('cfn-response'); const iam = new AWS.IAM({apiVersion: '2010-05-08'}); exports.handler = (event, context, cb) => { console.log(`Invoke: ${JSON.stringify(event)}`); const done = (err) => { if (err) { console.log(`Error: ${JSON.stringify(err)}`); response.send(event, context, response.FAILED, {}, event.PhysicalResourceId); } else { response.send(event, context, response.SUCCESS, {}, event.PhysicalResourceId ); } }; if (event.RequestType === 'Delete') { iam.deleteAccountPasswordPolicy({}, done); } else if (event.RequestType === 'Create' || event.RequestType === 'Update') { const params = { AllowUsersToChangePassword: event.ResourceProperties.AllowUsersToChangePassword === 'true', HardExpiry: event.ResourceProperties.HardExpiry === 'true', MinimumPasswordLength: parseInt(event.ResourceProperties.MinimumPasswordLength, 10), RequireLowercaseCharacters: event.ResourceProperties.RequireLowercaseCharacters === 'true', RequireNumbers: event.ResourceProperties.RequireNumbers === 'true', RequireSymbols: event.ResourceProperties.RequireSymbols === 'true', RequireUppercaseCharacters: event.ResourceProperties.RequireUppercaseCharacters === 'true', }; if (parseInt(event.ResourceProperties.MaxPasswordAge, 10) > 0) { params.MaxPasswordAge = parseInt(event.ResourceProperties.MaxPasswordAge, 10); } if (parseInt(event.ResourceProperties.PasswordReusePrevention, 10) > 0) { params.PasswordReusePrevention = parseInt(event.ResourceProperties.PasswordReusePrevention, 10); } iam.updateAccountPasswordPolicy(params, done); } else { cb(new Error(`unsupported RequestType: ${event.RequestType}`)); } }; Handler: index.handler MemorySize: 128 Role: 'Fn::GetAtt': - IamPasswordPolicyLambdaRole - Arn Runtime: nodejs16.x Timeout: 60 Metadata: cfn_nag: rules_to_suppress: - id: W89 reason: "No se despliega en VPC para simplicidad" #===============================================================================# # AWS Config - Registro de recursos y reglas de evaluaciĆ³n de conformidad # #===============================================================================# #--------------------- # # AWS Config Recorder # #--------------------- # ConfigRecorder: Type: AWS::Config::ConfigurationRecorder Properties: RecordingGroup: AllSupported: true IncludeGlobalResourceTypes: true Name: !Sub ${AccountPrefix}-awsconfig-recorder RoleARN: !GetAtt ConfigRole.Arn # ---------------------------- # # AWS Config Delivery Channel # # ---------------------------- # DeliveryChannel: Type: 'AWS::Config::DeliveryChannel' Properties: ConfigSnapshotDeliveryProperties: DeliveryFrequency: One_Hour S3BucketName: !Ref ConfigBucketName # -------------------- # # AWS Config IAM role # # -------------------- # ConfigRole: Type: AWS::IAM::Role Properties: RoleName: !Sub ${AccountPrefix}-awsconfig-role ManagedPolicyArns: ['arn:aws:iam::aws:policy/service-role/AWS_ConfigRole'] AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: config.amazonaws.com Action: sts:AssumeRole Policies: - PolicyName: AllowBucketAccess PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: s3:PutObject Resource: !Join ['', ['arn:aws:s3:::', !Ref 'ConfigBucketName', /AWSLogs/, !Ref 'AWS::AccountId', /*]] Condition: StringEquals: s3:x-amz-acl: bucket-owner-full-control - Effect: Allow Action: s3:GetBucketAcl Resource: !Join ['', ['arn:aws:s3:::', !Ref 'ConfigBucketName']] Metadata: cfn_nag: rules_to_suppress: - id: W28 reason: "Se define un nombre para el rol" #===============================================================================# # AWS IAM (Identity and Access Management - Analizador de acceso # #===============================================================================# AnalizadorAccesoIAM: Type: AWS::AccessAnalyzer::Analyzer Properties: AnalyzerName: !Sub ${AccountPrefix}-analizador-IAM Type: ACCOUNT #===============================================================================# # Amazon GuardDuty - Deteccion Inteligente de amenazas # #===============================================================================# GuardDutyDetector: Condition: EnableGuardDutyCondition Type: AWS::GuardDuty::Detector Properties: Enable: True FindingPublishingFrequency: FIFTEEN_MINUTES #===============================================================================# # Amazon Macie - Proteccion de datos. Deteccion y proteccion de datos sensibles # #===============================================================================# MacieSession: Condition: EnableMacieCondition Type: AWS::Macie::Session Properties: FindingPublishingFrequency: ONE_HOUR Status: ENABLED #===========================================================================================================# # AWS Security Hub - Evaluacion y visualizacion de controles de seguridad y hallazgos de otras herramientas # #===========================================================================================================# SecurityHub: Condition: EnableSecurityHubCondition Type: AWS::SecurityHub::Hub DependsOn: DeliveryChannel #===============================================================================# # AWS CloudTrail - Registro de llamadas API a la cuenta AWS # #===============================================================================# #---------------------# # Trail # #---------------------# AccountTrail: Type: AWS::CloudTrail::Trail Properties: TrailName: !Sub ${AccountPrefix}-cloudtrail S3BucketName: !Ref CloudTrailBucketName IsLogging: true IsMultiRegionTrail: true EnableLogFileValidation: true KMSKeyId: !Ref CloudTrailKey IncludeGlobalServiceEvents: true InsightSelectors: - InsightType: ApiCallRateInsight - InsightType: ApiErrorRateInsight CloudWatchLogsLogGroupArn: !GetAtt TrailLogGroup.Arn CloudWatchLogsRoleArn: !GetAtt TrailLogGroupRole.Arn # ------------------------------ # # CloudWatch Log Group for trail # # ------------------------------ # TrailLogGroup: Type: 'AWS::Logs::LogGroup' Properties: LogGroupName: !Sub ${AccountPrefix}-cloudtrail-logs KmsKeyId: !Ref CloudWatchLogsKey RetentionInDays: 90 #--------------------------------------------------# # Rol IAM para permitir a cloudtrail escribir logs # #--------------------------------------------------# TrailLogGroupRole: Type: 'AWS::IAM::Role' Properties: RoleName: !Sub ${AccountPrefix}-cloudwatch-role AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Sid: CloudTrailAssumeRole Effect: Allow Principal: Service: 'cloudtrail.amazonaws.com' Action: 'sts:AssumeRole' Policies: - PolicyName: 'cloudtrail-policy' PolicyDocument: Version: '2012-10-17' Statement: - Sid: AWSCloudTrailCreateLogStream Effect: Allow Action: 'logs:CreateLogStream' Resource: !GetAtt 'TrailLogGroup.Arn' - Sid: AWSCloudTrailPutLogEvents Effect: Allow Action: 'logs:PutLogEvents' Resource: !GetAtt 'TrailLogGroup.Arn' Metadata: cfn_nag: rules_to_suppress: - id: W28 reason: "Se define un nombre para el rol" #===============================================================================# # Parametros SSM # #===============================================================================# #--------------------------------------------# # Nombre del grupo de logs asociado al trail # #--------------------------------------------# CloudWatchLogGroup: Type: AWS::SSM::Parameter Properties: Description: Nombre del grupo de logs de Cloudwatch asociado al trail Name: /seguridad/cloudtrail/cloudwatchlogs Type: String Value: !Ref TrailLogGroup