AWSTemplateFormatVersion: "2010-09-09" Transform: AWS::Serverless-2016-10-31 Description: "" Resources: CloudWatchLogsDeliveryFullAccessPolicy: Type: "AWS::IAM::ManagedPolicy" Metadata: cfn_nag: rules_to_suppress: - id: W13 reason: "These actions don't require resources but Cloudformation requires an explicit wildcard in place" Properties: Path: "/service-role/" PolicyDocument: Version: "2012-10-17" Statement: Effect: "Allow" Action: - "logs:CreateLogDelivery" - "logs:GetLogDelivery" - "logs:UpdateLogDelivery" - "logs:DeleteLogDelivery" - "logs:ListLogDeliveries" - "logs:PutResourcePolicy" - "logs:DescribeResourcePolicies" - "logs:DescribeLogGroups" Resource: "*" StepFunctionInvokeLambdasPolicy: Type: "AWS::IAM::ManagedPolicy" Properties: Path: "/service-role/" PolicyDocument: Version: "2012-10-17" Statement: Effect: "Allow" Action: "lambda:InvokeFunction" Resource: - !Sub "arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${GetInstanceTypesLambdaFunction}:*" - !Sub "arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${GetInstanceTypesLambdaFunction}" - !Sub "arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${GetUsageLambdaFunction}:*" - !Sub "arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${GetUsageLambdaFunction}" - !Sub "arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${GetInstancesLambdaFunction}:*" - !Sub "arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${GetInstancesLambdaFunction}" - !Sub "arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${GetInstanceUtilizationLambdaFunction}:*" - !Sub "arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${GetInstanceUtilizationLambdaFunction}" - !Sub "arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${StoreRegionalUtilizationLambdaFunction}:*" - !Sub "arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${StoreRegionalUtilizationLambdaFunction}" CloudWatchLogsPolicy: Type: "AWS::IAM::ManagedPolicy" Properties: Path: "/service-role/" PolicyDocument: Version: "2012-10-17" Statement: - Sid: CreateLogGroup Effect: "Allow" Action: "logs:CreateLogGroup" Resource: !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:*" - Sid: CreateLogEvents Effect: "Allow" Action: - "logs:CreateLogStream" - "logs:PutLogEvents" Resource: !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/*:*" AmazonEventBridgeSchedulerExecutionPolicy: Type: "AWS::IAM::ManagedPolicy" Properties: Path: "/service-role/" PolicyDocument: Version: "2012-10-17" Statement: Effect: "Allow" Action: "states:StartExecution" Resource: !Sub "arn:aws:states:${AWS::Region}:${AWS::AccountId}:stateMachine:GetUtilization" GetInstanceUtilizationLambdaFunction: Type: "AWS::Serverless::Function" Metadata: cfn_nag: rules_to_suppress: - id: W58 reason: "False positive: Lambda function has permissions to write logs as the role has attached the managed policy CloudWatchLogsPolicy" - id: W89 reason: "Using this lambda within a VPC just adds delay and cost overhead, as there are no VPC resources involved in the process" - id: W92 reason: "Using ReservedConcurrentExecutions for a lambda function that is invoked on a daily process is over provisioning resources" Properties: Description: "Get Daily average EC2 instance utilization for a specific instance id" FunctionName: "GetInstanceUtilization" Handler: "index.handler" Architectures: - "x86_64" CodeUri: "lambdas/GetInstanceUtilization.zip" MemorySize: 128 Role: !GetAtt GetInstanceUtilizationRole.Arn Runtime: "nodejs18.x" Timeout: 3 GetInstanceTypesLambdaFunction: Type: "AWS::Serverless::Function" Metadata: cfn_nag: rules_to_suppress: - id: W58 reason: "False positive: Lambda function has permissions to write logs as the role has attached the managed policy CloudWatchLogsPolicy" - id: W89 reason: "Using this lambda within a VPC just adds delay and cost overhead, as there are no VPC resources involved in the process" - id: W92 reason: "Using ReservedConcurrentExecutions for a lambda function that is invoked on a daily process is over provisioning resources" Properties: Description: "Get offered instance types offered in a region" FunctionName: "GetInstanceTypes" Handler: "index.handler" Architectures: - "x86_64" CodeUri: "lambdas/GetInstanceTypes.zip" MemorySize: 1024 Role: !GetAtt GetInstanceTypesRole.Arn Runtime: "nodejs18.x" Timeout: 30 StoreRegionalUtilizationLambdaFunction: Type: "AWS::Serverless::Function" Metadata: cfn_nag: rules_to_suppress: - id: W58 reason: "False positive: Lambda function has permissions to write logs as the role has attached the managed policy CloudWatchLogsPolicy" - id: W89 reason: "Using this lambda within a VPC just adds delay and cost overhead, as there are no VPC resources involved in the process" - id: W92 reason: "Using ReservedConcurrentExecutions for a lambda function that is invoked on a daily process is over provisioning resources" Properties: Description: "Store as CloudWatch metric resulting utilization" FunctionName: "StoreRegionalUtilization" Handler: "index.handler" Architectures: - "arm64" CodeUri: "lambdas/StoreRegionalUtilization.zip" MemorySize: 128 Role: !GetAtt StoreRegionalUtilizationRole.Arn Runtime: "nodejs18.x" Timeout: 3 GetUsageLambdaFunction: Type: "AWS::Serverless::Function" Metadata: cfn_nag: rules_to_suppress: - id: W58 reason: "False positive: Lambda function has permissions to write logs as the role has attached the managed policy CloudWatchLogsPolicy" - id: W89 reason: "Using this lambda within a VPC just adds delay and cost overhead, as there are no VPC resources involved in the process" - id: W92 reason: "Using ReservedConcurrentExecutions for a lambda function that is invoked on a daily process is over provisioning resources" Properties: Description: "Identify in which regions there was EC2 usage yesterday" FunctionName: "GetUsage" Handler: "index.handler" Architectures: - "x86_64" CodeUri: "lambdas/GetUsage.zip" MemorySize: 128 Role: !GetAtt GetUsageRole.Arn Runtime: "nodejs18.x" Timeout: 3 GetInstancesLambdaFunction: Type: "AWS::Serverless::Function" Metadata: cfn_nag: rules_to_suppress: - id: W58 reason: "False positive: Lambda function has permissions to write logs as the role has attached the managed policy CloudWatchLogsPolicy" - id: W89 reason: "Using this lambda within a VPC just adds delay and cost overhead, as there are no VPC resources involved in the process" - id: W92 reason: "Using ReservedConcurrentExecutions for a lambda function that is invoked on a daily process is over provisioning resources" Properties: Description: "Get List of currently existing EC2 instances" FunctionName: "GetInstances" Handler: "index.handler" Architectures: - "x86_64" CodeUri: "lambdas/GetInstances.zip" MemorySize: 128 Role: !GetAtt GetInstancesRole.Arn Runtime: "nodejs18.x" Timeout: 10 AmazonEventBridgeSchedulerRole: Type: "AWS::IAM::Role" Properties: Path: "/service-role/" AssumeRolePolicyDocument: "Version": "2012-10-17" "Statement": "Effect": "Allow" "Principal": "Service": "scheduler.amazonaws.com" "Action": "sts:AssumeRole" "Condition": "StringLike": "aws:SourceArn": !Sub "arn:aws:scheduler:${AWS::Region}:${AWS::AccountId}:schedule/default/*" "aws:SourceAccount": !Sub "${AWS::AccountId}" MaxSessionDuration: 3600 ManagedPolicyArns: - !Ref AmazonEventBridgeSchedulerExecutionPolicy GetInstancesRole: Type: "AWS::IAM::Role" Properties: Path: "/service-role/" AssumeRolePolicyDocument: "Version": "2012-10-17" "Statement": "Effect": "Allow" "Principal": "Service": "lambda.amazonaws.com" "Action": "sts:AssumeRole" MaxSessionDuration: 3600 ManagedPolicyArns: - !Ref CloudWatchLogsPolicy GetInstanceTypesRole: Type: "AWS::IAM::Role" Properties: Path: "/service-role/" AssumeRolePolicyDocument: "Version": "2012-10-17" "Statement": "Effect": "Allow" "Principal": "Service": "lambda.amazonaws.com" "Action": "sts:AssumeRole" MaxSessionDuration: 3600 ManagedPolicyArns: - !Ref CloudWatchLogsPolicy GetUsageRole: Type: "AWS::IAM::Role" Properties: Path: "/service-role/" AssumeRolePolicyDocument: "Version": "2012-10-17" "Statement": "Effect": "Allow" "Principal": "Service": "lambda.amazonaws.com" "Action": "sts:AssumeRole" MaxSessionDuration: 3600 ManagedPolicyArns: - !Ref CloudWatchLogsPolicy GetInstanceUtilizationRole: Type: "AWS::IAM::Role" Properties: Path: "/service-role/" AssumeRolePolicyDocument: "Version": "2012-10-17" "Statement": "Effect": "Allow" "Principal": "Service": "lambda.amazonaws.com" "Action": "sts:AssumeRole" MaxSessionDuration: 3600 ManagedPolicyArns: - !Ref CloudWatchLogsPolicy StoreRegionalUtilizationRole: Type: "AWS::IAM::Role" Properties: Path: "/service-role/" AssumeRolePolicyDocument: "Version": "2012-10-17" "Statement": "Effect": "Allow" "Principal": "Service": "lambda.amazonaws.com" "Action": "sts:AssumeRole" MaxSessionDuration: 3600 ManagedPolicyArns: - !Ref CloudWatchLogsPolicy StepFunctionsGetUtilizationRole: Type: "AWS::IAM::Role" Properties: Path: "/service-role/" AssumeRolePolicyDocument: "Version": "2012-10-17" "Statement": "Effect": "Allow" "Principal": "Service": "states.amazonaws.com" "Action": "sts:AssumeRole" MaxSessionDuration: 3600 ManagedPolicyArns: - !Ref StepFunctionInvokeLambdasPolicy - !Ref CloudWatchLogsDeliveryFullAccessPolicy StepFunctionsStateMachine: Type: "AWS::Serverless::StateMachine" Properties: Name: "GetUtilization" DefinitionUri: step-function.json DefinitionSubstitutions: Region: !Sub ${AWS::Region} AccountId: !Sub ${AWS::AccountId} Role: !GetAtt StepFunctionsGetUtilizationRole.Arn Type: "STANDARD" Logging: Destinations: - CloudWatchLogsLogGroup: LogGroupArn: !GetAtt StepFunctionsStateMachinesLogGroup.Arn IncludeExecutionData: true Level: "ALL" StepFunctionsStateMachinesLogGroup: Type: "AWS::Logs::LogGroup" Properties: LogGroupName: "/aws/vendedlogs/states/GetUtilization-Logs" RetentionInDays: 30 KmsKeyId: !GetAtt BundleKey.Arn IAMPolicy: Type: "AWS::IAM::Policy" Metadata: cfn_nag: rules_to_suppress: - id: W12 reason: "Action GetCostAndUsage doesn't require resources but Cloudformation requires an explicit wildcard in place" Properties: PolicyDocument: { "Version": "2012-10-17", "Statement": [ { "Sid": "VisualEditor0", "Effect": "Allow", "Action": "ce:GetCostAndUsage", "Resource": "*" } ] } Roles: - !Ref GetUsageRole PolicyName: "AllowGetCostAndUsage" IAMPolicy2: Type: "AWS::IAM::Policy" Metadata: cfn_nag: rules_to_suppress: - id: W12 reason: "Action DescribeInstanceTypes doesn't require resources but Cloudformation requires an explicit wildcard in place" Properties: PolicyDocument: Version: "2012-10-17" Statement: Sid: "VisualEditor0" Effect: "Allow" Action: "ec2:DescribeInstanceTypes" Resource: "*" Roles: - !Ref GetInstanceTypesRole PolicyName: "DescribeInstanceTypes" IAMPolicy3: Type: "AWS::IAM::Policy" Metadata: cfn_nag: rules_to_suppress: - id: W12 reason: "Action DescribeInstances doesn't require resources but Cloudformation requires an explicit wildcard in place" Properties: PolicyDocument: Version: "2012-10-17" Statement: Sid: "VisualEditor0" Effect: "Allow" Action: "ec2:DescribeInstances" Resource: "*" Roles: - !Ref GetInstancesRole PolicyName: "DescribeInstances" IAMPolicy4: Type: "AWS::IAM::Policy" Metadata: cfn_nag: rules_to_suppress: - id: W12 reason: "Action GetMetricStatistics doesn't require resources but Cloudformation requires an explicit wildcard in place" Properties: PolicyDocument: "Version": "2012-10-17" "Statement": "Sid": "VisualEditor0" "Effect": "Allow" "Action": - "cloudwatch:GetMetricStatistics" - "cloudwatch:GetMetricData" "Resource": "*" Roles: - !Ref GetInstanceUtilizationRole PolicyName: "GetMetrics" IAMPolicy5: Type: "AWS::IAM::Policy" Metadata: cfn_nag: rules_to_suppress: - id: W12 reason: "Action PutMetricData doesn't require resources but Cloudformation requires an explicit wildcard in place" Properties: PolicyDocument: "Version": "2012-10-17" "Statement": "Sid": "VisualEditor0" "Effect": "Allow" "Action": "cloudwatch:PutMetricData" "Resource": "*" Roles: - !Ref StoreRegionalUtilizationRole PolicyName: "PutMetricData" CloudWatchDashboard: Type: "AWS::CloudWatch::Dashboard" Properties: DashboardName: "Sustainability" DashboardBody: !Sub "{\"widgets\":[{\"type\":\"metric\",\"x\":0,\"y\":0,\"width\":11,\"height\":10,\"properties\":{\"metrics\":[[{\"expression\":\"SEARCH('{Sustainability/KPI,region,service,type}', 'Average', 86400)\",\"id\":\"e1\",\"region\":\"${AWS::Region}\"}]],\"view\":\"timeSeries\",\"stacked\":false,\"region\":\"${AWS::Region}\",\"stat\":\"Average\",\"period\":300,\"start\":\"-PT168H\",\"title\":\"EC2 CPU Utilization\",\"end\":\"PT0H\",\"yAxis\":{\"left\":{\"showUnits\":true},\"right\":{\"showUnits\":false}}}}]}" DailyScheduler: Type: AWS::Scheduler::Schedule Properties: Description: "Run Step Function Daily" FlexibleTimeWindow: MaximumWindowInMinutes: 120 Mode: "FLEXIBLE" GroupName: default ScheduleExpression: cron(0 5 ? * * *) State: "ENABLED" Target: Arn: !GetAtt StepFunctionsStateMachine.Arn RoleArn: !GetAtt AmazonEventBridgeSchedulerRole.Arn BundleKey: Type: AWS::KMS::Key Properties: Enabled: True EnableKeyRotation: True KeyPolicy: Version: 2012-10-17 Id: key-default-1 Statement: - Sid: Enable IAM User Permissions Effect: Allow Principal: AWS: !Sub "arn:aws:iam::${AWS::AccountId}:root" Action: 'kms:*' Resource: '*' - Sid: Allow use of the key Effect: Allow Principal: Service: !Sub "logs.${AWS::Region}.amazonaws.com" Action: - 'kms:DescribeKey' - 'kms:Encrypt' - 'kms:Decrypt' - 'kms:ReEncrypt*' - 'kms:GenerateDataKey' - 'kms:GenerateDataKeyWithoutPlaintext' Resource: '*'