From 1c77bf5ecbdae41f0b073a041a9d8526cef196ee Mon Sep 17 00:00:00 2001 From: Greg Antic Date: Mon, 26 Aug 2024 13:43:55 +0000 Subject: [PATCH 1/2] CloudWatch Dashboard for NATGW VPC flow logs --- .../CloudWatch_Dashboard_NAT_FlowLogs.yaml | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 CloudWatch/CloudWatch_Dashboard_NAT_FlowLogs.yaml diff --git a/CloudWatch/CloudWatch_Dashboard_NAT_FlowLogs.yaml b/CloudWatch/CloudWatch_Dashboard_NAT_FlowLogs.yaml new file mode 100644 index 00000000..fae3c9f0 --- /dev/null +++ b/CloudWatch/CloudWatch_Dashboard_NAT_FlowLogs.yaml @@ -0,0 +1,89 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: 'Creates a CloudWatch Dashboard with four CloudWatch Logs Insights log widgets that query VPC flow logs for NAT Gateway, related to https://repost.aws/knowledge-center/vpc-find-traffic-sources-nat-gateway' + +Resources: + CloudWatchDashboard: + Type: 'AWS::CloudWatch::Dashboard' + Properties: + DashboardName: !Sub '${NatGatewayID}-Traffic-Dashboard' + DashboardBody: + Fn::Sub: | + { + "widgets": [ + { + "type": "log", + "x": 0, + "y": 0, + "width": 12, + "height": 9, + "properties": { + "query": "SOURCE '${LogGroupName}' | fields @timestamp, @message | filter (dstAddr like '${NatGatewayPrivateIP}' and isIpv4InSubnet(srcAddr, '${VpcCidr}')) | stats sum(bytes) as bytesTransferred by srcAddr, dstAddr | sort bytesTransferred desc | limit 10", + "region": "${AWS::Region}", + "stacked": false, + "title": "Top 10 - Instances sending most traffic through NAT gateway ${NatGatewayID}", + "view": "table" + } + }, + { + "type": "log", + "x": 12, + "y": 0, + "width": 12, + "height": 9, + "properties": { + "query": "SOURCE '${LogGroupName}' | fields @timestamp, @message | filter (dstAddr like '${NatGatewayPrivateIP}' and isIpv4InSubnet(srcAddr, '${VpcCidr}')) or (srcAddr like '${NatGatewayPrivateIP}' and isIpv4InSubnet(dstAddr, '${VpcCidr}'))| stats sum(bytes) as bytesTransferred by srcAddr, dstAddr | sort bytesTransferred desc | limit 10", + "region": "${AWS::Region}", + "stacked": false, + "title": "Top 10 - Traffic To and from NAT gateway ${NatGatewayID}", + "view": "table" + } + }, + { + "type": "log", + "x": 0, + "y": 9, + "width": 12, + "height": 9, + "properties": { + "query": "SOURCE '${LogGroupName}' | fields @timestamp, @message | filter (srcAddr like '${NatGatewayPrivateIP}' and not isIpv4InSubnet(dstAddr, '${VpcCidr}')) | stats sum(bytes) as bytesTransferred by srcAddr, dstAddr | sort bytesTransferred desc | limit 10", + "region": "${AWS::Region}", + "stacked": false, + "title": "Top 10 - Most often upload communication destinations through NAT Gateway ${NatGatewayID}", + "view": "table" + } + }, + { + "type": "log", + "x": 12, + "y": 9, + "width": 12, + "height": 9, + "properties": { + "query": "SOURCE '${LogGroupName}' | fields @timestamp, @message | filter (dstAddr like '${NatGatewayPrivateIP}' and not isIpv4InSubnet(srcAddr, '${VpcCidr}')) | stats sum(bytes) as bytesTransferred by srcAddr, dstAddr | sort bytesTransferred desc | limit 10", + "region": "${AWS::Region}", + "stacked": false, + "title": "Top 10 - Most often download communication destinations through NAT Gateway ${NatGatewayID}", + "view": "table" + } + } + ] + } + +Parameters: + NatGatewayPrivateIP: + Type: String + Description: The private IP address of the NAT Gateway + NatGatewayID: + Type: String + Description: The ID of the NAT Gateway + VpcCidr: + Type: String + Description: The CIDR block of the VPC + LogGroupName: + Type: String + Description: The ARN of the log group to query + +Outputs: + DashboardArn: + Description: ARN of the created CloudWatch Dashboard + Value: !Ref CloudWatchDashboard \ No newline at end of file From 86fdef44d7a0b6563f3b7cc82a0a63941765a879 Mon Sep 17 00:00:00 2001 From: Eric Beard Date: Wed, 28 Aug 2024 14:38:03 -0700 Subject: [PATCH 2/2] Auto-generated files and formatting --- .../CustomResources/getfromjson/bandit.json | 5 + .../getfromjson/{bandit.yaml => bandit.yml} | 4 +- .../StackSets/common-resources-pkg.json | 415 ++++++++++ .../common-resources-stackset-pkg.json | 54 ++ .../StackSets/common-resources-stackset.json | 56 ++ .../StackSets/common-resources-stackset.yaml | 7 +- .../StackSets/common-resources.json | 28 + .../StackSets/common-resources.yaml | 8 +- .../StackSets/log-setup-management-pkg.json | 291 +++++++ .../StackSets/log-setup-management.json | 293 +++++++ .../StackSets/log-setup-management.yaml | 82 +- .../StackSets/log-setup-target-accounts.json | 135 ++++ .../StackSets/log-setup-target-accounts.yaml | 21 +- .../CloudFront.json | 2 +- .../CloudFront.yaml | 2 +- Solutions/GitLab/GitLabServer-pkg.json | 736 +++++++++++++++++ Solutions/GitLab/GitLabServer-pkg.yaml | 8 +- Solutions/GitLab/GitLabServer.json | 263 ++++++ Solutions/GitLab/GitLabServer.yaml | 27 +- Solutions/GitLab/{README.yaml => README.md} | 0 .../GitLabAndVSCode/GitLabAndVSCode.json | 357 +++++++++ .../GitLabAndVSCode/GitLabAndVSCode.yaml | 51 +- Solutions/Gitea/Gitea-pkg.json | 746 ++++++++++++++++++ Solutions/Gitea/Gitea-pkg.yaml | 1 - Solutions/Gitea/Gitea.json | 268 +++++++ Solutions/Gitea/Gitea.yaml | 25 +- Solutions/VSCode/VSCodeServer-pkg.json | 746 ++++++++++++++++++ Solutions/VSCode/VSCodeServer-pkg.yaml | 1 - Solutions/VSCode/VSCodeServer.json | 268 +++++++ Solutions/VSCode/VSCodeServer.yaml | 25 +- scripts/lint-single.sh | 2 +- 31 files changed, 4782 insertions(+), 145 deletions(-) create mode 100644 CloudFormation/CustomResources/getfromjson/bandit.json rename CloudFormation/CustomResources/getfromjson/{bandit.yaml => bandit.yml} (76%) create mode 100644 CloudFormation/StackSets/common-resources-pkg.json create mode 100644 CloudFormation/StackSets/common-resources-stackset-pkg.json create mode 100644 CloudFormation/StackSets/common-resources-stackset.json create mode 100644 CloudFormation/StackSets/common-resources.json create mode 100644 CloudFormation/StackSets/log-setup-management-pkg.json create mode 100644 CloudFormation/StackSets/log-setup-management.json create mode 100644 CloudFormation/StackSets/log-setup-target-accounts.json create mode 100644 Solutions/GitLab/GitLabServer-pkg.json create mode 100644 Solutions/GitLab/GitLabServer.json rename Solutions/GitLab/{README.yaml => README.md} (100%) create mode 100644 Solutions/GitLabAndVSCode/GitLabAndVSCode.json create mode 100644 Solutions/Gitea/Gitea-pkg.json create mode 100644 Solutions/Gitea/Gitea.json create mode 100644 Solutions/VSCode/VSCodeServer-pkg.json create mode 100644 Solutions/VSCode/VSCodeServer.json diff --git a/CloudFormation/CustomResources/getfromjson/bandit.json b/CloudFormation/CustomResources/getfromjson/bandit.json new file mode 100644 index 00000000..62358802 --- /dev/null +++ b/CloudFormation/CustomResources/getfromjson/bandit.json @@ -0,0 +1,5 @@ +{ + "exclude_dirs": [ + "tests" + ] +} diff --git a/CloudFormation/CustomResources/getfromjson/bandit.yaml b/CloudFormation/CustomResources/getfromjson/bandit.yml similarity index 76% rename from CloudFormation/CustomResources/getfromjson/bandit.yaml rename to CloudFormation/CustomResources/getfromjson/bandit.yml index 02aa3f1f..06ebd39a 100644 --- a/CloudFormation/CustomResources/getfromjson/bandit.yaml +++ b/CloudFormation/CustomResources/getfromjson/bandit.yml @@ -1,3 +1,5 @@ # For more information, see https://bandit.readthedocs.io/en/latest/config.html -exclude_dirs: ['tests'] + +exclude_dirs: + - tests diff --git a/CloudFormation/StackSets/common-resources-pkg.json b/CloudFormation/StackSets/common-resources-pkg.json new file mode 100644 index 00000000..0f249c07 --- /dev/null +++ b/CloudFormation/StackSets/common-resources-pkg.json @@ -0,0 +1,415 @@ +{ + "Description": "This template has resources that will be installed into all managed accounts\nin the OU. For the purposes of the sample it's not important what exactly we\nare creating here. To demonstrate the consolidated logging, errors can be\nintroduced into this template, such as choosing a bucket name that already\nexists. This template uses a Rain module, which can be packaged with `rain\npkg -x common-resources.yaml`.\n", + "Parameters": { + "AppName": { + "Description": "This name will be used as part of resource names", + "Type": "String", + "Default": "stacksets-sample" + } + }, + "Resources": { + "TestQ": { + "Type": "AWS::SQS::Queue", + "Properties": { + "QueueName": "test-events17" + } + }, + "StorageLogBucket": { + "Type": "AWS::S3::Bucket", + "Metadata": { + "Comment": "This bucket records access logs for the main bucket", + "checkov": { + "skip": [ + { + "comment": "This is the log bucket", + "id": "CKV_AWS_18" + } + ] + }, + "guard": { + "SuppressedRules": [ + "S3_BUCKET_LOGGING_ENABLED", + "S3_BUCKET_REPLICATION_ENABLED" + ] + } + }, + "Properties": { + "BucketEncryption": { + "ServerSideEncryptionConfiguration": [ + { + "ServerSideEncryptionByDefault": { + "SSEAlgorithm": "AES256" + } + } + ] + }, + "BucketName": { + "Fn::Sub": "${AppName}-logs-${AWS::Region}-${AWS::AccountId}" + }, + "ObjectLockConfiguration": { + "ObjectLockEnabled": "Enabled", + "Rule": { + "DefaultRetention": { + "Mode": "COMPLIANCE", + "Years": 1 + } + } + }, + "ObjectLockEnabled": true, + "PublicAccessBlockConfiguration": { + "BlockPublicAcls": true, + "BlockPublicPolicy": true, + "IgnorePublicAcls": true, + "RestrictPublicBuckets": true + }, + "VersioningConfiguration": { + "Status": "Enabled" + } + } + }, + "StorageBucket": { + "Type": "AWS::S3::Bucket", + "Metadata": { + "guard": { + "SuppressedRules": [ + "S3_BUCKET_DEFAULT_LOCK_ENABLED" + ] + } + }, + "Properties": { + "BucketEncryption": { + "ServerSideEncryptionConfiguration": [ + { + "ServerSideEncryptionByDefault": { + "SSEAlgorithm": "AES256" + } + } + ] + }, + "BucketName": { + "Fn::Sub": "${AppName}-${AWS::Region}-${AWS::AccountId}" + }, + "LoggingConfiguration": { + "DestinationBucketName": { + "Ref": "StorageLogBucket" + } + }, + "ObjectLockEnabled": false, + "PublicAccessBlockConfiguration": { + "BlockPublicAcls": true, + "BlockPublicPolicy": true, + "IgnorePublicAcls": true, + "RestrictPublicBuckets": true + }, + "ReplicationConfiguration": { + "Role": { + "Fn::GetAtt": [ + "StorageReplicationRole", + "Arn" + ] + }, + "Rules": [ + { + "Destination": { + "Bucket": { + "Fn::GetAtt": [ + "StorageReplicaBucket", + "Arn" + ] + } + }, + "Status": "Enabled" + } + ] + }, + "VersioningConfiguration": { + "Status": "Enabled" + } + } + }, + "StorageReplicaBucket": { + "Type": "AWS::S3::Bucket", + "Metadata": { + "Comment": "This bucket is used as a target for replicas from the main bucket", + "checkov": { + "skip": [ + { + "comment": "This is the replica bucket", + "id": "CKV_AWS_18" + } + ] + }, + "guard": { + "SuppressedRules": [ + "S3_BUCKET_DEFAULT_LOCK_ENABLED", + "S3_BUCKET_REPLICATION_ENABLED", + "S3_BUCKET_LOGGING_ENABLED" + ] + } + }, + "Properties": { + "BucketEncryption": { + "ServerSideEncryptionConfiguration": [ + { + "ServerSideEncryptionByDefault": { + "SSEAlgorithm": "AES256" + } + } + ] + }, + "BucketName": { + "Fn::Sub": "${AppName}-replicas-${AWS::Region}-${AWS::AccountId}" + }, + "ObjectLockEnabled": false, + "PublicAccessBlockConfiguration": { + "BlockPublicAcls": true, + "BlockPublicPolicy": true, + "IgnorePublicAcls": true, + "RestrictPublicBuckets": true + }, + "VersioningConfiguration": { + "Status": "Enabled" + } + } + }, + "StorageReplicationPolicy": { + "Type": "AWS::IAM::RolePolicy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetReplicationConfiguration", + "s3:ListBucket" + ], + "Effect": "Allow", + "Resource": { + "Fn::Sub": "arn:${AWS::Partition}:s3:::${AppName}-${AWS::Region}-${AWS::AccountId}" + } + }, + { + "Action": [ + "s3:GetObjectVersionForReplication", + "s3:GetObjectVersionAcl", + "s3:GetObjectVersionTagging" + ], + "Effect": "Allow", + "Resource": { + "Fn::Sub": "arn:${AWS::Partition}:s3:::${AppName}-${AWS::Region}-${AWS::AccountId}/*" + } + }, + { + "Action": [ + "s3:ReplicateObject", + "s3:ReplicateDelete", + "s3:ReplicationTags" + ], + "Effect": "Allow", + "Resource": { + "Fn::Sub": "arn:${AWS::Partition}:s3:::${AppName}-replicas-${AWS::Region}-${AWS::AccountId}/*" + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "bucket-replication-policy", + "RoleName": { + "Ref": "StorageReplicationRole" + } + } + }, + "StorageReplicationRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "s3.amazonaws.com" + ] + } + } + ], + "Version": "2012-10-17" + }, + "Path": "/" + } + }, + "StorageLogBucketPolicyPolicy": { + "Type": "AWS::S3::BucketPolicy", + "Properties": { + "Bucket": { + "Fn::Sub": "${AppName}-logs-${AWS::Region}-${AWS::AccountId}" + }, + "PolicyDocument": { + "Statement": [ + { + "Action": "s3:*", + "Condition": { + "Bool": { + "aws:SecureTransport": false + } + }, + "Effect": "Deny", + "Principal": { + "AWS": "*" + }, + "Resource": [ + { + "Fn::Sub": "arn:${AWS::Partition}:s3:::${AppName}-logs-${AWS::Region}-${AWS::AccountId}" + }, + { + "Fn::Sub": "arn:${AWS::Partition}:s3:::${AppName}-logs-${AWS::Region}-${AWS::AccountId}/*" + } + ] + }, + { + "Action": "s3:PutObject", + "Condition": { + "ArnLike": { + "aws:SourceArn": { + "Fn::Sub": "arn:${AWS::Partition}:s3:::${AppName}-logs-${AWS::Region}-${AWS::AccountId}" + } + }, + "StringEquals": { + "aws:SourceAccount": { + "Ref": "AWS::AccountId" + } + } + }, + "Effect": "Allow", + "Principal": { + "Service": "logging.s3.amazonaws.com" + }, + "Resource": [ + { + "Fn::Sub": "arn:${AWS::Partition}:s3:::${AppName}-logs-${AWS::Region}-${AWS::AccountId}/*" + } + ] + } + ], + "Version": "2012-10-17" + } + } + }, + "StorageBucketPolicyPolicy": { + "Type": "AWS::S3::BucketPolicy", + "Properties": { + "Bucket": { + "Fn::Sub": "${AppName}-${AWS::Region}-${AWS::AccountId}" + }, + "PolicyDocument": { + "Statement": [ + { + "Action": "s3:*", + "Condition": { + "Bool": { + "aws:SecureTransport": false + } + }, + "Effect": "Deny", + "Principal": { + "AWS": "*" + }, + "Resource": [ + { + "Fn::Sub": "arn:${AWS::Partition}:s3:::${AppName}-${AWS::Region}-${AWS::AccountId}" + }, + { + "Fn::Sub": "arn:${AWS::Partition}:s3:::${AppName}-${AWS::Region}-${AWS::AccountId}/*" + } + ] + }, + { + "Action": "s3:PutObject", + "Condition": { + "ArnLike": { + "aws:SourceArn": { + "Fn::Sub": "arn:${AWS::Partition}:s3:::${AppName}-${AWS::Region}-${AWS::AccountId}" + } + }, + "StringEquals": { + "aws:SourceAccount": { + "Ref": "AWS::AccountId" + } + } + }, + "Effect": "Allow", + "Principal": { + "Service": "logging.s3.amazonaws.com" + }, + "Resource": [ + { + "Fn::Sub": "arn:${AWS::Partition}:s3:::${AppName}-${AWS::Region}-${AWS::AccountId}/*" + } + ] + } + ], + "Version": "2012-10-17" + } + } + }, + "StorageReplicaBucketPolicyPolicy": { + "Type": "AWS::S3::BucketPolicy", + "Properties": { + "Bucket": { + "Fn::Sub": "${AppName}-replicas-${AWS::Region}-${AWS::AccountId}" + }, + "PolicyDocument": { + "Statement": [ + { + "Action": "s3:*", + "Condition": { + "Bool": { + "aws:SecureTransport": false + } + }, + "Effect": "Deny", + "Principal": { + "AWS": "*" + }, + "Resource": [ + { + "Fn::Sub": "arn:${AWS::Partition}:s3:::${AppName}-replicas-${AWS::Region}-${AWS::AccountId}" + }, + { + "Fn::Sub": "arn:${AWS::Partition}:s3:::${AppName}-replicas-${AWS::Region}-${AWS::AccountId}/*" + } + ] + }, + { + "Action": "s3:PutObject", + "Condition": { + "ArnLike": { + "aws:SourceArn": { + "Fn::Sub": "arn:${AWS::Partition}:s3:::${AppName}-replicas-${AWS::Region}-${AWS::AccountId}" + } + }, + "StringEquals": { + "aws:SourceAccount": { + "Ref": "AWS::AccountId" + } + } + }, + "Effect": "Allow", + "Principal": { + "Service": "logging.s3.amazonaws.com" + }, + "Resource": [ + { + "Fn::Sub": "arn:${AWS::Partition}:s3:::${AppName}-replicas-${AWS::Region}-${AWS::AccountId}/*" + } + ] + } + ], + "Version": "2012-10-17" + } + } + } + } +} diff --git a/CloudFormation/StackSets/common-resources-stackset-pkg.json b/CloudFormation/StackSets/common-resources-stackset-pkg.json new file mode 100644 index 00000000..8df5cf6f --- /dev/null +++ b/CloudFormation/StackSets/common-resources-stackset-pkg.json @@ -0,0 +1,54 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + "Description": "This template contains a stack set that deploys common-resource.yaml to target accounts", + "Parameters": { + "OUID": { + "Type": "String", + "Default": "ou-qxtx-vv0thlla" + } + }, + "Resources": { + "StackSet": { + "Type": "AWS::CloudFormation::StackSet", + "Properties": { + "TemplateBody": "Description: |\n This template has resources that will be installed into all managed accounts\n in the OU. For the purposes of the sample it's not important what exactly we\n are creating here. To demonstrate the consolidated logging, errors can be\n introduced into this template, such as choosing a bucket name that already\n exists. This template uses a Rain module, which can be packaged with `rain\n pkg -x common-resources.yaml`.\n\nParameters:\n AppName:\n Type: String\n Description: This name will be used as part of resource names\n Default: stacksets-sample\n\nResources:\n TestQ:\n Type: AWS::SQS::Queue\n Properties:\n QueueName: test-events17\n\n StorageLogBucket:\n Type: AWS::S3::Bucket\n Properties:\n BucketEncryption:\n ServerSideEncryptionConfiguration:\n - ServerSideEncryptionByDefault:\n SSEAlgorithm: AES256\n BucketName: !Sub ${AppName}-logs-${AWS::Region}-${AWS::AccountId}\n ObjectLockConfiguration:\n ObjectLockEnabled: Enabled\n Rule:\n DefaultRetention:\n Mode: COMPLIANCE\n Years: 1\n ObjectLockEnabled: true\n PublicAccessBlockConfiguration:\n BlockPublicAcls: true\n BlockPublicPolicy: true\n IgnorePublicAcls: true\n RestrictPublicBuckets: true\n VersioningConfiguration:\n Status: Enabled\n Metadata:\n Comment: This bucket records access logs for the main bucket\n checkov:\n skip:\n - comment: This is the log bucket\n id: CKV_AWS_18\n guard:\n SuppressedRules:\n - S3_BUCKET_LOGGING_ENABLED\n - S3_BUCKET_REPLICATION_ENABLED\n\n StorageBucket:\n Type: AWS::S3::Bucket\n Properties:\n BucketEncryption:\n ServerSideEncryptionConfiguration:\n - ServerSideEncryptionByDefault:\n SSEAlgorithm: AES256\n BucketName: !Sub ${AppName}-${AWS::Region}-${AWS::AccountId}\n LoggingConfiguration:\n DestinationBucketName: !Ref StorageLogBucket\n ObjectLockEnabled: false\n PublicAccessBlockConfiguration:\n BlockPublicAcls: true\n BlockPublicPolicy: true\n IgnorePublicAcls: true\n RestrictPublicBuckets: true\n ReplicationConfiguration:\n Role: !GetAtt StorageReplicationRole.Arn\n Rules:\n - Destination:\n Bucket: !GetAtt StorageReplicaBucket.Arn\n Status: Enabled\n VersioningConfiguration:\n Status: Enabled\n Metadata:\n guard:\n SuppressedRules:\n - S3_BUCKET_DEFAULT_LOCK_ENABLED\n\n StorageReplicaBucket:\n Type: AWS::S3::Bucket\n Properties:\n BucketEncryption:\n ServerSideEncryptionConfiguration:\n - ServerSideEncryptionByDefault:\n SSEAlgorithm: AES256\n BucketName: !Sub ${AppName}-replicas-${AWS::Region}-${AWS::AccountId}\n ObjectLockEnabled: false\n PublicAccessBlockConfiguration:\n BlockPublicAcls: true\n BlockPublicPolicy: true\n IgnorePublicAcls: true\n RestrictPublicBuckets: true\n VersioningConfiguration:\n Status: Enabled\n Metadata:\n Comment: This bucket is used as a target for replicas from the main bucket\n checkov:\n skip:\n - comment: This is the replica bucket\n id: CKV_AWS_18\n guard:\n SuppressedRules:\n - S3_BUCKET_DEFAULT_LOCK_ENABLED\n - S3_BUCKET_REPLICATION_ENABLED\n - S3_BUCKET_LOGGING_ENABLED\n\n StorageReplicationPolicy:\n Type: AWS::IAM::RolePolicy\n Properties:\n PolicyDocument:\n Statement:\n - Action:\n - s3:GetReplicationConfiguration\n - s3:ListBucket\n Effect: Allow\n Resource: !Sub arn:${AWS::Partition}:s3:::${AppName}-${AWS::Region}-${AWS::AccountId}\n - Action:\n - s3:GetObjectVersionForReplication\n - s3:GetObjectVersionAcl\n - s3:GetObjectVersionTagging\n Effect: Allow\n Resource: !Sub arn:${AWS::Partition}:s3:::${AppName}-${AWS::Region}-${AWS::AccountId}/*\n - Action:\n - s3:ReplicateObject\n - s3:ReplicateDelete\n - s3:ReplicationTags\n Effect: Allow\n Resource: !Sub arn:${AWS::Partition}:s3:::${AppName}-replicas-${AWS::Region}-${AWS::AccountId}/*\n Version: \"2012-10-17\"\n PolicyName: bucket-replication-policy\n RoleName: !Ref StorageReplicationRole\n\n StorageReplicationRole:\n Type: AWS::IAM::Role\n Properties:\n AssumeRolePolicyDocument:\n Statement:\n - Action:\n - sts:AssumeRole\n Effect: Allow\n Principal:\n Service:\n - s3.amazonaws.com\n Version: \"2012-10-17\"\n Path: /\n\n StorageLogBucketPolicyPolicy:\n Type: AWS::S3::BucketPolicy\n Properties:\n Bucket: !Sub ${AppName}-logs-${AWS::Region}-${AWS::AccountId}\n PolicyDocument:\n Statement:\n - Action: s3:*\n Condition:\n Bool:\n aws:SecureTransport: false\n Effect: Deny\n Principal:\n AWS: '*'\n Resource:\n - !Sub arn:${AWS::Partition}:s3:::${AppName}-logs-${AWS::Region}-${AWS::AccountId}\n - !Sub arn:${AWS::Partition}:s3:::${AppName}-logs-${AWS::Region}-${AWS::AccountId}/*\n - Action: s3:PutObject\n Condition:\n ArnLike:\n aws:SourceArn: !Sub arn:${AWS::Partition}:s3:::${AppName}-logs-${AWS::Region}-${AWS::AccountId}\n StringEquals:\n aws:SourceAccount: !Ref AWS::AccountId\n Effect: Allow\n Principal:\n Service: logging.s3.amazonaws.com\n Resource:\n - !Sub arn:${AWS::Partition}:s3:::${AppName}-logs-${AWS::Region}-${AWS::AccountId}/*\n Version: \"2012-10-17\"\n\n StorageBucketPolicyPolicy:\n Type: AWS::S3::BucketPolicy\n Properties:\n Bucket: !Sub ${AppName}-${AWS::Region}-${AWS::AccountId}\n PolicyDocument:\n Statement:\n - Action: s3:*\n Condition:\n Bool:\n aws:SecureTransport: false\n Effect: Deny\n Principal:\n AWS: '*'\n Resource:\n - !Sub arn:${AWS::Partition}:s3:::${AppName}-${AWS::Region}-${AWS::AccountId}\n - !Sub arn:${AWS::Partition}:s3:::${AppName}-${AWS::Region}-${AWS::AccountId}/*\n - Action: s3:PutObject\n Condition:\n ArnLike:\n aws:SourceArn: !Sub arn:${AWS::Partition}:s3:::${AppName}-${AWS::Region}-${AWS::AccountId}\n StringEquals:\n aws:SourceAccount: !Ref AWS::AccountId\n Effect: Allow\n Principal:\n Service: logging.s3.amazonaws.com\n Resource:\n - !Sub arn:${AWS::Partition}:s3:::${AppName}-${AWS::Region}-${AWS::AccountId}/*\n Version: \"2012-10-17\"\n\n StorageReplicaBucketPolicyPolicy:\n Type: AWS::S3::BucketPolicy\n Properties:\n Bucket: !Sub ${AppName}-replicas-${AWS::Region}-${AWS::AccountId}\n PolicyDocument:\n Statement:\n - Action: s3:*\n Condition:\n Bool:\n aws:SecureTransport: false\n Effect: Deny\n Principal:\n AWS: '*'\n Resource:\n - !Sub arn:${AWS::Partition}:s3:::${AppName}-replicas-${AWS::Region}-${AWS::AccountId}\n - !Sub arn:${AWS::Partition}:s3:::${AppName}-replicas-${AWS::Region}-${AWS::AccountId}/*\n - Action: s3:PutObject\n Condition:\n ArnLike:\n aws:SourceArn: !Sub arn:${AWS::Partition}:s3:::${AppName}-replicas-${AWS::Region}-${AWS::AccountId}\n StringEquals:\n aws:SourceAccount: !Ref AWS::AccountId\n Effect: Allow\n Principal:\n Service: logging.s3.amazonaws.com\n Resource:\n - !Sub arn:${AWS::Partition}:s3:::${AppName}-replicas-${AWS::Region}-${AWS::AccountId}/*\n Version: \"2012-10-17\"\n\n\n\n# BadBucket:\n# Type: AWS::S3::Bucket\n# Metadata:\n# guard:\n# SuppressedRules:\n# - S3_BUCKET_SERVER_SIDE_ENCRYPTION_ENABLED\n# - S3_BUCKET_DEFAULT_LOCK_ENABLED\n# - S3_BUCKET_VERSIONING_ENABLED\n# - S3_BUCKET_REPLICATION_ENABLED\n# - S3_BUCKET_PUBLIC_WRITE_PROHIBITED\n# - S3_BUCKET_PUBLIC_READ_PROHIBITED\n# - S3_BUCKET_LOGGING_ENABLED\n# Comment:\n# This bucket is purposefully using an existing name, to cause a deployment failure\n# Properties:\n# BucketName: rain-artifacts-646934191481-us-east-1", + "Capabilities": [ + "CAPABILITY_IAM" + ], + "StackInstancesGroup": [ + { + "DeploymentTargets": { + "OrganizationalUnitIds": [ + { + "Ref": "OUID" + } + ] + }, + "Regions": [ + "us-east-1", + "us-west-2" + ] + } + ], + "Parameters": [ + { + "ParameterKey": "AppName", + "ParameterValue": "stackset-logging-sample" + } + ], + "PermissionModel": "SERVICE_MANAGED", + "Description": "This stack set is part of a sample that demonstrates how to set up cross account logging", + "OperationPreferences": { + "FailureToleranceCount": 0, + "MaxConcurrentCount": 2, + "RegionConcurrencyType": "PARALLEL" + }, + "AutoDeployment": { + "Enabled": true, + "RetainStacksOnAccountRemoval": true + }, + "StackSetName": "common-resources" + } + } + } +} diff --git a/CloudFormation/StackSets/common-resources-stackset.json b/CloudFormation/StackSets/common-resources-stackset.json new file mode 100644 index 00000000..d61dd4d7 --- /dev/null +++ b/CloudFormation/StackSets/common-resources-stackset.json @@ -0,0 +1,56 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + "Description": "This template contains a stack set that deploys common-resource.yaml to target accounts", + "Parameters": { + "OUID": { + "Type": "String", + "Default": "ou-qxtx-vv0thlla" + } + }, + "Resources": { + "StackSet": { + "Type": "AWS::CloudFormation::StackSet", + "Properties": { + "TemplateBody": { + "Rain::Embed": "common-resources-pkg.yaml" + }, + "Capabilities": [ + "CAPABILITY_IAM" + ], + "StackInstancesGroup": [ + { + "DeploymentTargets": { + "OrganizationalUnitIds": [ + { + "Ref": "OUID" + } + ] + }, + "Regions": [ + "us-east-1", + "us-west-2" + ] + } + ], + "Parameters": [ + { + "ParameterKey": "AppName", + "ParameterValue": "stackset-logging-sample" + } + ], + "PermissionModel": "SERVICE_MANAGED", + "Description": "This stack set is part of a sample that demonstrates how to set up cross account logging", + "OperationPreferences": { + "FailureToleranceCount": 0, + "MaxConcurrentCount": 2, + "RegionConcurrencyType": "PARALLEL" + }, + "AutoDeployment": { + "Enabled": true, + "RetainStacksOnAccountRemoval": true + }, + "StackSetName": "common-resources" + } + } + } +} diff --git a/CloudFormation/StackSets/common-resources-stackset.yaml b/CloudFormation/StackSets/common-resources-stackset.yaml index f4562561..f1738869 100644 --- a/CloudFormation/StackSets/common-resources-stackset.yaml +++ b/CloudFormation/StackSets/common-resources-stackset.yaml @@ -1,9 +1,9 @@ -AWSTemplateFormatVersion: 2010-09-09 +AWSTemplateFormatVersion: "2010-09-09" Description: This template contains a stack set that deploys common-resource.yaml to target accounts Parameters: - OUID: + OUID: Type: String Default: ou-qxtx-vv0thlla @@ -17,7 +17,7 @@ Resources: StackInstancesGroup: - DeploymentTargets: OrganizationalUnitIds: - - !Ref OUID + - !Ref OUID Regions: - us-east-1 - us-west-2 @@ -34,4 +34,3 @@ Resources: Enabled: true RetainStacksOnAccountRemoval: true StackSetName: common-resources - diff --git a/CloudFormation/StackSets/common-resources.json b/CloudFormation/StackSets/common-resources.json new file mode 100644 index 00000000..7111ff64 --- /dev/null +++ b/CloudFormation/StackSets/common-resources.json @@ -0,0 +1,28 @@ +{ + "Description": "This template has resources that will be installed into all managed accounts\nin the OU. For the purposes of the sample it's not important what exactly we\nare creating here. To demonstrate the consolidated logging, errors can be\nintroduced into this template, such as choosing a bucket name that already\nexists. This template uses a Rain module, which can be packaged with `rain\npkg -x common-resources.yaml`.\n", + "Parameters": { + "AppName": { + "Description": "This name will be used as part of resource names", + "Type": "String", + "Default": "stacksets-sample" + } + }, + "Resources": { + "Storage": { + "Type": { + "Rain::Module": "../../RainModules/bucket.yml" + }, + "Properties": { + "AppName": { + "Ref": "AppName" + } + } + }, + "TestQ": { + "Type": "AWS::SQS::Queue", + "Properties": { + "QueueName": "test-events17" + } + } + } +} diff --git a/CloudFormation/StackSets/common-resources.yaml b/CloudFormation/StackSets/common-resources.yaml index 04d36a2d..fc0e6f24 100644 --- a/CloudFormation/StackSets/common-resources.yaml +++ b/CloudFormation/StackSets/common-resources.yaml @@ -13,17 +13,18 @@ Parameters: Default: stacksets-sample Resources: - Storage: - Type: !Rain::Module "../../RainModules/bucket.yml" + Type: !Rain::Module ../../RainModules/bucket.yml Properties: - AppName: !Ref AppName + AppName: !Ref AppName TestQ: Type: AWS::SQS::Queue Properties: QueueName: test-events17 + + # BadBucket: # Type: AWS::S3::Bucket # Metadata: @@ -40,4 +41,3 @@ Resources: # This bucket is purposefully using an existing name, to cause a deployment failure # Properties: # BucketName: rain-artifacts-646934191481-us-east-1 - diff --git a/CloudFormation/StackSets/log-setup-management-pkg.json b/CloudFormation/StackSets/log-setup-management-pkg.json new file mode 100644 index 00000000..3ff221d5 --- /dev/null +++ b/CloudFormation/StackSets/log-setup-management-pkg.json @@ -0,0 +1,291 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + "Description": "A central event bus rule and log group to collect CloudFormation logs from all target accounts", + "Parameters": { + "OUID": { + "Description": "The Id of the Organization Unit to deploy the stack set to.", + "Type": "String", + "Default": "ou-qxtx-vv0thlla" + }, + "OrgID": { + "Description": "The Id of the Organization to verify the cross account API call. All accounts in this org will be granted permissions to put events onto the default event bus in this account. Note that this is not the OUID, it's the org itself and should start with o-", + "Type": "String", + "Default": "o-jhfo4fcm1s" + }, + "CentralEventBusName": { + "Type": "String", + "Default": "central-cloudformation" + }, + "CentralEventLogName": { + "Type": "String", + "Default": "central-cloudformation-logs" + } + }, + "Transform": "AWS::LanguageExtensions", + "Resources": { + "CentralEventBus": { + "Type": "AWS::Events::EventBus", + "Properties": { + "Description": "A custom event bus in the central account to be used as a destination for events from a rule in target accounts", + "Name": { + "Ref": "CentralEventBusName" + }, + "DeadLetterConfig": { + "Arn": { + "Fn::GetAtt": [ + "DeadLetterQueue", + "Arn" + ] + } + } + } + }, + "CentralEventBusPolicy": { + "Type": "AWS::Events::EventBusPolicy", + "Metadata": { + "Comment": "Note that the condition requires the Organization ID, not the Organizational Unit ID. If you want to refine the access down to an OU, you could use aws:PrincipalOrgPaths in the condition instead." + }, + "Properties": { + "EventBusName": { + "Ref": "CentralEventBus" + }, + "StatementId": "CentralEventBusPolicyStatement", + "Statement": { + "Effect": "Allow", + "Principal": "*", + "Action": "events:PutEvents", + "Resource": { + "Fn::Sub": "arn:aws:events:${AWS::Region}:${AWS::AccountId}:event-bus/${CentralEventBusName}" + }, + "Condition": { + "StringEquals": { + "aws:PrincipalOrgID": { + "Ref": "OrgID" + } + } + } + } + } + }, + "CentralEventLog": { + "Type": "AWS::Logs::LogGroup", + "DependsOn": "CentralEventBus", + "Properties": { + "LogGroupClass": "STANDARD", + "LogGroupName": { + "Ref": "CentralEventLogName" + }, + "KmsKeyId": { + "Fn::GetAtt": [ + "CentralEventLogKey", + "Arn" + ] + } + } + }, + "CentralEventLogKey": { + "Type": "AWS::KMS::Key", + "Properties": { + "Description": "KMS key for log group", + "KeyPolicy": { + "Version": "2012-10-17", + "Id": "key-policy", + "Statement": [ + { + "Action": [ + "kms:Create*", + "kms:Describe*", + "kms:Enable*", + "kms:List*", + "kms:Put*", + "kms:Update*", + "kms:Revoke*", + "kms:Disable*", + "kms:Get*", + "kms:Delete*", + "kms:ScheduleKeyDeletion", + "kms:CancelKeyDeletion", + "kms:GenerateDataKey", + "kms:TagResource", + "kms:UntagResource" + ], + "Effect": "Allow", + "Principal": { + "AWS": [ + { + "Fn::Sub": "arn:aws:iam::${AWS::AccountId}:role/Admin" + } + ] + }, + "Resource": { + "Fn::Sub": "arn:aws:kms:${AWS::Region}:${AWS::AccountId}:key/*" + } + }, + { + "Sid": "Allow CloudWatch Logs to use the key", + "Effect": "Allow", + "Principal": { + "Service": "logs.amazonaws.com" + }, + "Action": [ + "kms:Encrypt*", + "kms:Decrypt*", + "kms:ReEncrypt*", + "kms:GenerateDataKey*", + "kms:Describe*" + ], + "Resource": { + "Fn::Sub": "arn:aws:kms:${AWS::Region}:${AWS::AccountId}:key/*" + } + } + ] + } + } + }, + "CentralEventLogQuery": { + "Type": "AWS::Logs::QueryDefinition", + "Properties": { + "Name": "CentralCloudFormationEventLogs", + "QueryString": "fields time, account, region, `detail.resource-type`, `detail.logical-resource-id`, `detail.status-details.status` | sort @timestamp desc", + "LogGroupNames": [ + { + "Ref": "CentralEventLogName" + } + ] + } + }, + "CentralEventLogQueryReason": { + "Type": "AWS::Logs::QueryDefinition", + "Properties": { + "Name": "CentralCloudFormationFailures", + "QueryString": "fields time, account, region, `detail.resource-type`, `detail.logical-resource-id`, `detail.status-details.status` as status, `detail.status-details.status-reason` as reason | sort @timestamp desc | filter status like \"FAILED\" | filter reason not like \"canceled\" | filter resource not like \"AWS::CloudFormation::Stack\" ", + "LogGroupNames": [ + { + "Ref": "CentralEventLogName" + } + ] + } + }, + "CentralEventLogPolicy": { + "Type": "AWS::Logs::ResourcePolicy", + "Metadata": { + "Comment": "The PolicyDocument in this resource *must* be JSON, unlike the standard IAM resources that allow YAML. Also note that you have to put the policy here and not in a role referenced by AWS::Events::Rule.RoleArn, which is meant for cross-account scenarios." + }, + "Properties": { + "PolicyName": "CentralEventLogResourcePolicy", + "PolicyDocument": { + "Fn::Sub": "{\n \"Statement\": [\n {\n \"Effect\": \"Allow\",\n \"Principal\": {\n \"Service\": [\n \"delivery.logs.amazonaws.com\",\n \"events.amazonaws.com\"\n ]\n },\n \"Action\": [\n \"logs:PutLogEvents\",\n \"logs:CreateLogStream\"\n ],\n \"Resource\": \"${CentralEventLog.Arn}\"\n }\n ]\n}\n" + } + } + }, + "CentralEventRule": { + "Type": "AWS::Events::Rule", + "DependsOn": [ + "CentralEventLog" + ], + "Metadata": { + "Comment": "We use an empty prefix here to capture all events forwarded from target accounts", + "cfn-lint": { + "config": { + "ignore_checks": [ + "W3005" + ] + } + } + }, + "Properties": { + "Name": "CloudFormationLogs", + "EventBusName": { + "Ref": "CentralEventBusName" + }, + "State": "ENABLED", + "EventPattern": { + "source": [ + { + "prefix": "" + } + ] + }, + "Targets": [ + { + "Arn": { + "Fn::GetAtt": [ + "CentralEventLog", + "Arn" + ] + }, + "Id": "CloudFormationLogsToCentralGroup", + "DeadLetterConfig": { + "Arn": { + "Fn::GetAtt": [ + "DeadLetterQueue", + "Arn" + ] + } + } + } + ] + } + }, + "DeadLetterQueue": { + "Type": "AWS::SQS::Queue", + "Properties": { + "QueueName": { + "Fn::Sub": "${CentralEventBusName}-DLQ" + } + } + }, + "TargetAccountLogging": { + "Type": "AWS::CloudFormation::StackSet", + "DependsOn": [ + "CentralEventRule", + "CentralEventLog", + "CentralEventLogPolicy" + ], + "Properties": { + "TemplateBody": "AWSTemplateFormatVersion: '2010-09-09'\n\nDescription: EventBridge Rule to send CloudFormation events to a central EventBus\n\nParameters:\n\n CentralEventBusArn:\n Type: String\n\nResources:\n\n CloudFormationEventRule:\n Type: AWS::Events::Rule\n Metadata:\n Comment: Send all cloudformation events to the central event bus\n Properties:\n Name: CloudFormationEventRule\n EventBusName: !Sub arn:aws:events:${AWS::Region}:${AWS::AccountId}:event-bus/default\n EventPattern:\n source:\n - aws.cloudformation\n State: ENABLED\n Targets:\n - Arn: !Ref CentralEventBusArn \n RoleArn: !GetAtt EventBridgeRole.Arn\n Id: CentralEventBus\n DeadLetterConfig:\n Arn: !GetAtt DeadLetterQueue.Arn\n\n DeadLetterQueue:\n Type: AWS::SQS::Queue\n Properties:\n QueueName: CloudFormation-Logs-DLQ\n\n DeadLetterQueuePolicy:\n Type: AWS::SQS::QueuePolicy\n Properties:\n PolicyDocument:\n Version: \"2012-10-17\"\n Id: AllowEventBridgeToWriteLogs\n Statement:\n - Sid: AllowEventBridgeToWriteLogs\n Effect: Allow\n Principal:\n Service: events.amazonaws.com\n Action: sqs:SendMessage\n Resource: !GetAtt DeadLetterQueue.Arn\n Condition:\n ArnLike:\n aws:SourceArn: !Sub \"arn:aws:events:${AWS::Region}:${AWS::AccountId}:rule/CloudFormationEventRule\"\n Queues:\n - !Ref DeadLetterQueue\n\n EventBridgeRole:\n Type: AWS::IAM::Role\n Properties:\n AssumeRolePolicyDocument:\n Version: '2012-10-17'\n Statement:\n - Effect: Allow\n Principal:\n Service: events.amazonaws.com\n Action: 'sts:AssumeRole'\n\n EventBridgeRolePolicy:\n Type: AWS::IAM::RolePolicy\n Metadata: \n Comment: Allow CloudFormation events to be written to the default event bus in the target account\n Properties:\n PolicyName: EventBridgeRolePolicy\n PolicyDocument:\n Version: '2012-10-17'\n Statement:\n - Effect: Allow\n Action: 'events:PutEvents'\n Resource: !Ref CentralEventBusArn \n RoleName: !Ref EventBridgeRole", + "Capabilities": [ + "CAPABILITY_IAM" + ], + "StackInstancesGroup": [ + { + "DeploymentTargets": { + "OrganizationalUnitIds": [ + { + "Ref": "OUID" + } + ] + }, + "Regions": [ + "us-east-1", + "us-west-2" + ] + } + ], + "Parameters": [ + { + "ParameterKey": "CentralEventBusArn", + "ParameterValue": { + "Fn::GetAtt": [ + "CentralEventBus", + "Arn" + ] + } + } + ], + "PermissionModel": "SERVICE_MANAGED", + "Description": "This stack set is part of a sample that demonstrates how to set up cross account logging. It configures logging resources in target accounts.", + "OperationPreferences": { + "FailureToleranceCount": 0, + "MaxConcurrentCount": 2, + "RegionConcurrencyType": "PARALLEL" + }, + "AutoDeployment": { + "Enabled": true, + "RetainStacksOnAccountRemoval": true + }, + "StackSetName": "log-setup" + } + } + } +} diff --git a/CloudFormation/StackSets/log-setup-management.json b/CloudFormation/StackSets/log-setup-management.json new file mode 100644 index 00000000..0911106a --- /dev/null +++ b/CloudFormation/StackSets/log-setup-management.json @@ -0,0 +1,293 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + "Description": "A central event bus rule and log group to collect CloudFormation logs from all target accounts", + "Parameters": { + "OUID": { + "Description": "The Id of the Organization Unit to deploy the stack set to.", + "Type": "String", + "Default": "ou-qxtx-vv0thlla" + }, + "OrgID": { + "Description": "The Id of the Organization to verify the cross account API call. All accounts in this org will be granted permissions to put events onto the default event bus in this account. Note that this is not the OUID, it's the org itself and should start with o-", + "Type": "String", + "Default": "o-jhfo4fcm1s" + }, + "CentralEventBusName": { + "Type": "String", + "Default": "central-cloudformation" + }, + "CentralEventLogName": { + "Type": "String", + "Default": "central-cloudformation-logs" + } + }, + "Transform": "AWS::LanguageExtensions", + "Resources": { + "CentralEventBus": { + "Type": "AWS::Events::EventBus", + "Properties": { + "Description": "A custom event bus in the central account to be used as a destination for events from a rule in target accounts", + "Name": { + "Ref": "CentralEventBusName" + }, + "DeadLetterConfig": { + "Arn": { + "Fn::GetAtt": [ + "DeadLetterQueue", + "Arn" + ] + } + } + } + }, + "CentralEventBusPolicy": { + "Type": "AWS::Events::EventBusPolicy", + "Metadata": { + "Comment": "Note that the condition requires the Organization ID, not the Organizational Unit ID. If you want to refine the access down to an OU, you could use aws:PrincipalOrgPaths in the condition instead." + }, + "Properties": { + "EventBusName": { + "Ref": "CentralEventBus" + }, + "StatementId": "CentralEventBusPolicyStatement", + "Statement": { + "Effect": "Allow", + "Principal": "*", + "Action": "events:PutEvents", + "Resource": { + "Fn::Sub": "arn:aws:events:${AWS::Region}:${AWS::AccountId}:event-bus/${CentralEventBusName}" + }, + "Condition": { + "StringEquals": { + "aws:PrincipalOrgID": { + "Ref": "OrgID" + } + } + } + } + } + }, + "CentralEventLog": { + "Type": "AWS::Logs::LogGroup", + "DependsOn": "CentralEventBus", + "Properties": { + "LogGroupClass": "STANDARD", + "LogGroupName": { + "Ref": "CentralEventLogName" + }, + "KmsKeyId": { + "Fn::GetAtt": [ + "CentralEventLogKey", + "Arn" + ] + } + } + }, + "CentralEventLogKey": { + "Type": "AWS::KMS::Key", + "Properties": { + "Description": "KMS key for log group", + "KeyPolicy": { + "Version": "2012-10-17", + "Id": "key-policy", + "Statement": [ + { + "Action": [ + "kms:Create*", + "kms:Describe*", + "kms:Enable*", + "kms:List*", + "kms:Put*", + "kms:Update*", + "kms:Revoke*", + "kms:Disable*", + "kms:Get*", + "kms:Delete*", + "kms:ScheduleKeyDeletion", + "kms:CancelKeyDeletion", + "kms:GenerateDataKey", + "kms:TagResource", + "kms:UntagResource" + ], + "Effect": "Allow", + "Principal": { + "AWS": [ + { + "Fn::Sub": "arn:aws:iam::${AWS::AccountId}:role/Admin" + } + ] + }, + "Resource": { + "Fn::Sub": "arn:aws:kms:${AWS::Region}:${AWS::AccountId}:key/*" + } + }, + { + "Sid": "Allow CloudWatch Logs to use the key", + "Effect": "Allow", + "Principal": { + "Service": "logs.amazonaws.com" + }, + "Action": [ + "kms:Encrypt*", + "kms:Decrypt*", + "kms:ReEncrypt*", + "kms:GenerateDataKey*", + "kms:Describe*" + ], + "Resource": { + "Fn::Sub": "arn:aws:kms:${AWS::Region}:${AWS::AccountId}:key/*" + } + } + ] + } + } + }, + "CentralEventLogQuery": { + "Type": "AWS::Logs::QueryDefinition", + "Properties": { + "Name": "CentralCloudFormationEventLogs", + "QueryString": "fields time, account, region, `detail.resource-type`, `detail.logical-resource-id`, `detail.status-details.status` | sort @timestamp desc", + "LogGroupNames": [ + { + "Ref": "CentralEventLogName" + } + ] + } + }, + "CentralEventLogQueryReason": { + "Type": "AWS::Logs::QueryDefinition", + "Properties": { + "Name": "CentralCloudFormationFailures", + "QueryString": "fields time, account, region, `detail.resource-type`, `detail.logical-resource-id`, `detail.status-details.status` as status, `detail.status-details.status-reason` as reason | sort @timestamp desc | filter status like \"FAILED\" | filter reason not like \"canceled\" | filter resource not like \"AWS::CloudFormation::Stack\" ", + "LogGroupNames": [ + { + "Ref": "CentralEventLogName" + } + ] + } + }, + "CentralEventLogPolicy": { + "Type": "AWS::Logs::ResourcePolicy", + "Metadata": { + "Comment": "The PolicyDocument in this resource *must* be JSON, unlike the standard IAM resources that allow YAML. Also note that you have to put the policy here and not in a role referenced by AWS::Events::Rule.RoleArn, which is meant for cross-account scenarios." + }, + "Properties": { + "PolicyName": "CentralEventLogResourcePolicy", + "PolicyDocument": { + "Fn::Sub": "{\n \"Statement\": [\n {\n \"Effect\": \"Allow\",\n \"Principal\": {\n \"Service\": [\n \"delivery.logs.amazonaws.com\",\n \"events.amazonaws.com\"\n ]\n },\n \"Action\": [\n \"logs:PutLogEvents\",\n \"logs:CreateLogStream\"\n ],\n \"Resource\": \"${CentralEventLog.Arn}\"\n }\n ]\n}\n" + } + } + }, + "CentralEventRule": { + "Type": "AWS::Events::Rule", + "DependsOn": [ + "CentralEventLog" + ], + "Metadata": { + "Comment": "We use an empty prefix here to capture all events forwarded from target accounts", + "cfn-lint": { + "config": { + "ignore_checks": [ + "W3005" + ] + } + } + }, + "Properties": { + "Name": "CloudFormationLogs", + "EventBusName": { + "Ref": "CentralEventBusName" + }, + "State": "ENABLED", + "EventPattern": { + "source": [ + { + "prefix": "" + } + ] + }, + "Targets": [ + { + "Arn": { + "Fn::GetAtt": [ + "CentralEventLog", + "Arn" + ] + }, + "Id": "CloudFormationLogsToCentralGroup", + "DeadLetterConfig": { + "Arn": { + "Fn::GetAtt": [ + "DeadLetterQueue", + "Arn" + ] + } + } + } + ] + } + }, + "DeadLetterQueue": { + "Type": "AWS::SQS::Queue", + "Properties": { + "QueueName": { + "Fn::Sub": "${CentralEventBusName}-DLQ" + } + } + }, + "TargetAccountLogging": { + "Type": "AWS::CloudFormation::StackSet", + "DependsOn": [ + "CentralEventRule", + "CentralEventLog", + "CentralEventLogPolicy" + ], + "Properties": { + "TemplateBody": { + "Rain::Embed": "log-setup-target-accounts.yaml" + }, + "Capabilities": [ + "CAPABILITY_IAM" + ], + "StackInstancesGroup": [ + { + "DeploymentTargets": { + "OrganizationalUnitIds": [ + { + "Ref": "OUID" + } + ] + }, + "Regions": [ + "us-east-1", + "us-west-2" + ] + } + ], + "Parameters": [ + { + "ParameterKey": "CentralEventBusArn", + "ParameterValue": { + "Fn::GetAtt": [ + "CentralEventBus", + "Arn" + ] + } + } + ], + "PermissionModel": "SERVICE_MANAGED", + "Description": "This stack set is part of a sample that demonstrates how to set up cross account logging. It configures logging resources in target accounts.", + "OperationPreferences": { + "FailureToleranceCount": 0, + "MaxConcurrentCount": 2, + "RegionConcurrencyType": "PARALLEL" + }, + "AutoDeployment": { + "Enabled": true, + "RetainStacksOnAccountRemoval": true + }, + "StackSetName": "log-setup" + } + } + } +} diff --git a/CloudFormation/StackSets/log-setup-management.yaml b/CloudFormation/StackSets/log-setup-management.yaml index 09ec3aeb..8fa0e9f3 100644 --- a/CloudFormation/StackSets/log-setup-management.yaml +++ b/CloudFormation/StackSets/log-setup-management.yaml @@ -1,11 +1,10 @@ -AWSTemplateFormatVersion: '2010-09-09' +AWSTemplateFormatVersion: "2010-09-09" Transform: AWS::LanguageExtensions Description: A central event bus rule and log group to collect CloudFormation logs from all target accounts Parameters: - OUID: Type: String Description: The Id of the Organization Unit to deploy the stack set to. @@ -25,7 +24,6 @@ Parameters: Default: central-cloudformation-logs Resources: - CentralEventBus: Type: AWS::Events::EventBus Properties: @@ -43,20 +41,19 @@ Resources: StatementId: CentralEventBusPolicyStatement Statement: Effect: Allow - Principal: "*" - Action: 'events:PutEvents' + Principal: '*' + Action: events:PutEvents Resource: !Sub arn:aws:events:${AWS::Region}:${AWS::AccountId}:event-bus/${CentralEventBusName} - Condition: + Condition: StringEquals: - "aws:PrincipalOrgID": !Ref OrgID - + aws:PrincipalOrgID: !Ref OrgID CentralEventLog: DependsOn: CentralEventBus Type: AWS::Logs::LogGroup Properties: LogGroupClass: STANDARD - LogGroupName: !Ref CentralEventLogName + LogGroupName: !Ref CentralEventLogName KmsKeyId: !GetAtt CentralEventLogKey.Arn CentralEventLogKey: @@ -64,7 +61,7 @@ Resources: Properties: Description: KMS key for log group KeyPolicy: - Version: '2012-10-17' + Version: "2012-10-17" Id: key-policy Statement: - Action: @@ -87,34 +84,34 @@ Resources: Principal: AWS: - !Sub arn:aws:iam::${AWS::AccountId}:role/Admin - Resource: !Sub arn:aws:kms:${AWS::Region}:${AWS::AccountId}:key/* + Resource: !Sub arn:aws:kms:${AWS::Region}:${AWS::AccountId}:key/* - Sid: Allow CloudWatch Logs to use the key Effect: Allow Principal: Service: logs.amazonaws.com Action: - - 'kms:Encrypt*' - - 'kms:Decrypt*' - - 'kms:ReEncrypt*' - - 'kms:GenerateDataKey*' - - 'kms:Describe*' - Resource: !Sub arn:aws:kms:${AWS::Region}:${AWS::AccountId}:key/* + - kms:Encrypt* + - kms:Decrypt* + - kms:ReEncrypt* + - kms:GenerateDataKey* + - kms:Describe* + Resource: !Sub arn:aws:kms:${AWS::Region}:${AWS::AccountId}:key/* CentralEventLogQuery: Type: AWS::Logs::QueryDefinition Properties: Name: CentralCloudFormationEventLogs - QueryString: "fields time, account, region, `detail.resource-type`, `detail.logical-resource-id`, `detail.status-details.status` | sort @timestamp desc" + QueryString: fields time, account, region, `detail.resource-type`, `detail.logical-resource-id`, `detail.status-details.status` | sort @timestamp desc LogGroupNames: - - !Ref CentralEventLogName + - !Ref CentralEventLogName CentralEventLogQueryReason: Type: AWS::Logs::QueryDefinition Properties: Name: CentralCloudFormationFailures - QueryString: "fields time, account, region, `detail.resource-type`, `detail.logical-resource-id`, `detail.status-details.status` as status, `detail.status-details.status-reason` as reason | sort @timestamp desc | filter status like \"FAILED\" | filter reason not like \"canceled\" | filter resource not like \"AWS::CloudFormation::Stack\" " + QueryString: 'fields time, account, region, `detail.resource-type`, `detail.logical-resource-id`, `detail.status-details.status` as status, `detail.status-details.status-reason` as reason | sort @timestamp desc | filter status like "FAILED" | filter reason not like "canceled" | filter resource not like "AWS::CloudFormation::Stack" ' LogGroupNames: - - !Ref CentralEventLogName + - !Ref CentralEventLogName CentralEventLogPolicy: Type: AWS::Logs::ResourcePolicy @@ -122,31 +119,29 @@ Resources: Comment: The PolicyDocument in this resource *must* be JSON, unlike the standard IAM resources that allow YAML. Also note that you have to put the policy here and not in a role referenced by AWS::Events::Rule.RoleArn, which is meant for cross-account scenarios. Properties: PolicyName: CentralEventLogResourcePolicy - PolicyDocument: - !Sub | + PolicyDocument: !Sub | + { + "Statement": [ { - "Statement": [ - { - "Effect": "Allow", - "Principal": { - "Service": [ - "delivery.logs.amazonaws.com", - "events.amazonaws.com" - ] - }, - "Action": [ - "logs:PutLogEvents", - "logs:CreateLogStream" - ], - "Resource": "${CentralEventLog.Arn}" - } - ] + "Effect": "Allow", + "Principal": { + "Service": [ + "delivery.logs.amazonaws.com", + "events.amazonaws.com" + ] + }, + "Action": [ + "logs:PutLogEvents", + "logs:CreateLogStream" + ], + "Resource": "${CentralEventLog.Arn}" } + ] + } - CentralEventRule: Type: AWS::Events::Rule - DependsOn: + DependsOn: - CentralEventLog Metadata: Comment: We use an empty prefix here to capture all events forwarded from target accounts @@ -185,7 +180,7 @@ Resources: StackInstancesGroup: - DeploymentTargets: OrganizationalUnitIds: - - !Ref OUID + - !Ref OUID Regions: - us-east-1 - us-west-2 @@ -202,6 +197,3 @@ Resources: Enabled: true RetainStacksOnAccountRemoval: true StackSetName: log-setup - - - diff --git a/CloudFormation/StackSets/log-setup-target-accounts.json b/CloudFormation/StackSets/log-setup-target-accounts.json new file mode 100644 index 00000000..a3882781 --- /dev/null +++ b/CloudFormation/StackSets/log-setup-target-accounts.json @@ -0,0 +1,135 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + "Description": "EventBridge Rule to send CloudFormation events to a central EventBus", + "Parameters": { + "CentralEventBusArn": { + "Type": "String" + } + }, + "Resources": { + "CloudFormationEventRule": { + "Type": "AWS::Events::Rule", + "Metadata": { + "Comment": "Send all cloudformation events to the central event bus" + }, + "Properties": { + "Name": "CloudFormationEventRule", + "EventBusName": { + "Fn::Sub": "arn:aws:events:${AWS::Region}:${AWS::AccountId}:event-bus/default" + }, + "EventPattern": { + "source": [ + "aws.cloudformation" + ] + }, + "State": "ENABLED", + "Targets": [ + { + "Arn": { + "Ref": "CentralEventBusArn" + }, + "RoleArn": { + "Fn::GetAtt": [ + "EventBridgeRole", + "Arn" + ] + }, + "Id": "CentralEventBus", + "DeadLetterConfig": { + "Arn": { + "Fn::GetAtt": [ + "DeadLetterQueue", + "Arn" + ] + } + } + } + ] + } + }, + "DeadLetterQueue": { + "Type": "AWS::SQS::Queue", + "Properties": { + "QueueName": "CloudFormation-Logs-DLQ" + } + }, + "DeadLetterQueuePolicy": { + "Type": "AWS::SQS::QueuePolicy", + "Properties": { + "PolicyDocument": { + "Version": "2012-10-17", + "Id": "AllowEventBridgeToWriteLogs", + "Statement": [ + { + "Sid": "AllowEventBridgeToWriteLogs", + "Effect": "Allow", + "Principal": { + "Service": "events.amazonaws.com" + }, + "Action": "sqs:SendMessage", + "Resource": { + "Fn::GetAtt": [ + "DeadLetterQueue", + "Arn" + ] + }, + "Condition": { + "ArnLike": { + "aws:SourceArn": { + "Fn::Sub": "arn:aws:events:${AWS::Region}:${AWS::AccountId}:rule/CloudFormationEventRule" + } + } + } + } + ] + }, + "Queues": [ + { + "Ref": "DeadLetterQueue" + } + ] + } + }, + "EventBridgeRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Service": "events.amazonaws.com" + }, + "Action": "sts:AssumeRole" + } + ] + } + } + }, + "EventBridgeRolePolicy": { + "Type": "AWS::IAM::RolePolicy", + "Metadata": { + "Comment": "Allow CloudFormation events to be written to the default event bus in the target account" + }, + "Properties": { + "PolicyName": "EventBridgeRolePolicy", + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": "events:PutEvents", + "Resource": { + "Ref": "CentralEventBusArn" + } + } + ] + }, + "RoleName": { + "Ref": "EventBridgeRole" + } + } + } + } +} diff --git a/CloudFormation/StackSets/log-setup-target-accounts.yaml b/CloudFormation/StackSets/log-setup-target-accounts.yaml index c821153c..8d080520 100644 --- a/CloudFormation/StackSets/log-setup-target-accounts.yaml +++ b/CloudFormation/StackSets/log-setup-target-accounts.yaml @@ -1,14 +1,12 @@ -AWSTemplateFormatVersion: '2010-09-09' +AWSTemplateFormatVersion: "2010-09-09" Description: EventBridge Rule to send CloudFormation events to a central EventBus Parameters: - CentralEventBusArn: Type: String Resources: - CloudFormationEventRule: Type: AWS::Events::Rule Metadata: @@ -21,7 +19,7 @@ Resources: - aws.cloudformation State: ENABLED Targets: - - Arn: !Ref CentralEventBusArn + - Arn: !Ref CentralEventBusArn RoleArn: !GetAtt EventBridgeRole.Arn Id: CentralEventBus DeadLetterConfig: @@ -47,7 +45,7 @@ Resources: Resource: !GetAtt DeadLetterQueue.Arn Condition: ArnLike: - aws:SourceArn: !Sub "arn:aws:events:${AWS::Region}:${AWS::AccountId}:rule/CloudFormationEventRule" + aws:SourceArn: !Sub arn:aws:events:${AWS::Region}:${AWS::AccountId}:rule/CloudFormationEventRule Queues: - !Ref DeadLetterQueue @@ -55,24 +53,23 @@ Resources: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: - Version: '2012-10-17' + Version: "2012-10-17" Statement: - Effect: Allow Principal: Service: events.amazonaws.com - Action: 'sts:AssumeRole' + Action: sts:AssumeRole EventBridgeRolePolicy: Type: AWS::IAM::RolePolicy - Metadata: + Metadata: Comment: Allow CloudFormation events to be written to the default event bus in the target account Properties: PolicyName: EventBridgeRolePolicy PolicyDocument: - Version: '2012-10-17' + Version: "2012-10-17" Statement: - Effect: Allow - Action: 'events:PutEvents' - Resource: !Ref CentralEventBusArn + Action: events:PutEvents + Resource: !Ref CentralEventBusArn RoleName: !Ref EventBridgeRole - diff --git a/Solutions/CloudFrontCustomOriginLambda@Edge/CloudFront.json b/Solutions/CloudFrontCustomOriginLambda@Edge/CloudFront.json index dca7ca24..f2c894c1 100644 --- a/Solutions/CloudFrontCustomOriginLambda@Edge/CloudFront.json +++ b/Solutions/CloudFrontCustomOriginLambda@Edge/CloudFront.json @@ -61,7 +61,7 @@ "EC2ImageId": { "Description": "EC2 AMI Id", "Type": "AWS::EC2::Image::Id", - "Default": "ami-xxx" + "Default": "ami-0d85a662720db9789" }, "EC2InstanceType": { "Description": "Amazon EC2 instance type.", diff --git a/Solutions/CloudFrontCustomOriginLambda@Edge/CloudFront.yaml b/Solutions/CloudFrontCustomOriginLambda@Edge/CloudFront.yaml index 828ba177..4bb3dfeb 100644 --- a/Solutions/CloudFrontCustomOriginLambda@Edge/CloudFront.yaml +++ b/Solutions/CloudFrontCustomOriginLambda@Edge/CloudFront.yaml @@ -56,7 +56,7 @@ Parameters: EC2ImageId: Description: EC2 AMI Id Type: AWS::EC2::Image::Id - Default: ami-0d85a662720db9789 + Default: ami-0d85a662720db9789 EC2InstanceType: Description: Amazon EC2 instance type. diff --git a/Solutions/GitLab/GitLabServer-pkg.json b/Solutions/GitLab/GitLabServer-pkg.json new file mode 100644 index 00000000..f2cb9658 --- /dev/null +++ b/Solutions/GitLab/GitLabServer-pkg.json @@ -0,0 +1,736 @@ +{ + "Parameters": { + "LatestAMI": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-6.1-x86_64" + }, + "InstanceType": { + "Type": "String", + "Default": "m5.large" + } + }, + "Mappings": { + "Prefixes": { + "ap-northeast-1": { + "PrefixList": "pl-58a04531" + }, + "ap-northeast-2": { + "PrefixList": "pl-22a6434b" + }, + "ap-south-1": { + "PrefixList": "pl-9aa247f3" + }, + "ap-southeast-1": { + "PrefixList": "pl-31a34658" + }, + "ap-southeast-2": { + "PrefixList": "pl-b8a742d1" + }, + "ca-central-1": { + "PrefixList": "pl-38a64351" + }, + "eu-central-1": { + "PrefixList": "pl-a3a144ca" + }, + "eu-north-1": { + "PrefixList": "pl-fab65393" + }, + "eu-west-1": { + "PrefixList": "pl-4fa04526" + }, + "eu-west-2": { + "PrefixList": "pl-93a247fa" + }, + "eu-west-3": { + "PrefixList": "pl-75b1541c" + }, + "sa-east-1": { + "PrefixList": "pl-5da64334" + }, + "us-east-1": { + "PrefixList": "pl-3b927c52" + }, + "us-east-2": { + "PrefixList": "pl-b6a144df" + }, + "us-west-1": { + "PrefixList": "pl-4ea04527" + }, + "us-west-2": { + "PrefixList": "pl-82a045eb" + } + } + }, + "Resources": { + "InstanceSecurityGroup": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "gitlab-server-isg", + "SecurityGroupIngress": [ + { + "Description": "Allow HTTP from com.amazonaws.global.cloudfront.origin-facing", + "IpProtocol": "tcp", + "FromPort": 80, + "ToPort": 80, + "SourcePrefixListId": { + "Fn::FindInMap": [ + "Prefixes", + { + "Ref": "AWS::Region" + }, + "PrefixList" + ] + } + } + ], + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "Tags": [ + { + "Key": "Name", + "Value": "gitlab-server-isg" + } + ], + "VpcId": { + "Ref": "NetworkVPC" + } + } + }, + "InstanceRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ec2.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "Tags": [ + { + "Key": "Name", + "Value": "gitlab-server-instance" + } + ] + } + }, + "InstanceRolePolicy": { + "Type": "AWS::IAM::RolePolicy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "ec2messages:*", + "ssm:UpdateInstanceInformation", + "ssmmessages:*", + "secretsmanager:GetSecretValue" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "InstanceRolePolicy", + "RoleName": { + "Ref": "InstanceRole" + } + } + }, + "InstanceProfile": { + "Type": "AWS::IAM::InstanceProfile", + "Properties": { + "Roles": [ + { + "Ref": "InstanceRole" + } + ] + } + }, + "Server": { + "Type": "AWS::EC2::Instance", + "DependsOn": [ + "InstanceRolePolicy", + "InstanceRole" + ], + "Properties": { + "AvailabilityZone": { + "Fn::Select": [ + 0, + { + "Fn::GetAZs": null + } + ] + }, + "BlockDeviceMappings": [ + { + "DeviceName": "/dev/xvda", + "Ebs": { + "VolumeSize": 128 + } + } + ], + "IamInstanceProfile": { + "Ref": "InstanceProfile" + }, + "ImageId": { + "Ref": "LatestAMI" + }, + "InstanceType": { + "Ref": "InstanceType" + }, + "SecurityGroupIds": [ + { + "Fn::GetAtt": [ + "InstanceSecurityGroup", + "GroupId" + ] + } + ], + "SubnetId": { + "Fn::GetAtt": [ + "NetworkPublicSubnet1", + "SubnetId" + ] + }, + "Tags": [ + { + "Key": "Name", + "Value": "gitlab-server" + } + ], + "UserData": { + "Fn::Base64": { + "Fn::Sub": "#!/bin/bash\n\nset -eou pipefail\n\nlocal_ip=$(ec2-metadata | grep \"^local-ipv4: \" | cut -d \" \" -f 2)\n\n# Install cfn-signal\nyum install -y aws-cfn-bootstrap\n\n# Install postfix\nyum install -y postfix\nsystemctl enable postfix\nsystemctl start postfix\n\n# Get the yum repo\ncurl https://packages.gitlab.com/install/repositories/gitlab/gitlab-ee/script.rpm.sh | sudo bash\n\n# Install gitlab and run it on the local ip\nexport EXTERNAL_URL=\"http://$local_ip\" \nyum install -y gitlab-ee\n\n# Tell CloudFormation we're ready to go\n# This is a variable for the Sub intrisic function, not a bash variable\ncfn-signal -s true --stack ${AWS::StackName} --resource Server --region ${AWS::Region}" + } + } + } + }, + "NetworkVPC": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.0.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "gitlab-server" + } + ] + } + }, + "NetworkPublicSubnet1": { + "Type": "AWS::EC2::Subnet", + "Metadata": { + "guard": { + "SuppressedRules": [ + "SUBNET_AUTO_ASSIGN_PUBLIC_IP_DISABLED" + ] + } + }, + "Properties": { + "AvailabilityZone": { + "Fn::Select": [ + 0, + { + "Fn::GetAZs": { + "Ref": "AWS::Region" + } + } + ] + }, + "CidrBlock": "10.0.0.0/18", + "MapPublicIpOnLaunch": true, + "VpcId": { + "Ref": "NetworkVPC" + }, + "Tags": [ + { + "Key": "Name", + "Value": "gitlab-server-public-subnet-1" + } + ] + } + }, + "NetworkPublicSubnet1RouteTable": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "NetworkVPC" + }, + "Tags": [ + { + "Key": "Name", + "Value": "gitlab-server-public-subnet-1-rt" + } + ] + } + }, + "NetworkPublicSubnet1RouteTableAssociation": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "NetworkPublicSubnet1RouteTable" + }, + "SubnetId": { + "Ref": "NetworkPublicSubnet1" + } + } + }, + "NetworkPublicSubnet1DefaultRoute": { + "Type": "AWS::EC2::Route", + "DependsOn": [ + "NetworkVPCGW" + ], + "Metadata": { + "guard": { + "SuppressedRules": [ + "NO_UNRESTRICTED_ROUTE_TO_IGW" + ] + } + }, + "Properties": { + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "NetworkInternetGateway" + }, + "RouteTableId": { + "Ref": "NetworkPublicSubnet1RouteTable" + } + } + }, + "NetworkPublicSubnet1EIP": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "gitlab-server-public-subnet-1-eip" + } + ] + } + }, + "NetworkPublicSubnet1NATGateway": { + "Type": "AWS::EC2::NatGateway", + "DependsOn": [ + "NetworkPublicSubnet1DefaultRoute", + "NetworkPublicSubnet1RouteTableAssociation" + ], + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "NetworkPublicSubnet1EIP", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "NetworkPublicSubnet1" + }, + "Tags": [ + { + "Key": "Name", + "Value": "gitlab-server-public-subnet-1-ngw" + } + ] + } + }, + "NetworkPublicSubnet2": { + "Type": "AWS::EC2::Subnet", + "Metadata": { + "guard": { + "SuppressedRules": [ + "SUBNET_AUTO_ASSIGN_PUBLIC_IP_DISABLED" + ] + } + }, + "Properties": { + "AvailabilityZone": { + "Fn::Select": [ + 1, + { + "Fn::GetAZs": { + "Ref": "AWS::Region" + } + } + ] + }, + "CidrBlock": "10.0.64.0/18", + "MapPublicIpOnLaunch": true, + "VpcId": { + "Ref": "NetworkVPC" + }, + "Tags": [ + { + "Key": "Name", + "Value": "gitlab-server-public-subnet-2" + } + ] + } + }, + "NetworkPublicSubnet2RouteTable": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "NetworkVPC" + }, + "Tags": [ + { + "Key": "Name", + "Value": "gitlab-server-public-subnet-2-rt" + } + ] + } + }, + "NetworkPublicSubnet2RouteTableAssociation": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "NetworkPublicSubnet2RouteTable" + }, + "SubnetId": { + "Ref": "NetworkPublicSubnet2" + } + } + }, + "NetworkPublicSubnet2DefaultRoute": { + "Type": "AWS::EC2::Route", + "DependsOn": [ + "NetworkVPCGW" + ], + "Metadata": { + "guard": { + "SuppressedRules": [ + "NO_UNRESTRICTED_ROUTE_TO_IGW" + ] + } + }, + "Properties": { + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "NetworkInternetGateway" + }, + "RouteTableId": { + "Ref": "NetworkPublicSubnet2RouteTable" + } + } + }, + "NetworkPublicSubnet2EIP": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "gitlab-server-public-subnet-eip" + } + ] + } + }, + "NetworkPublicSubnet2NATGateway": { + "Type": "AWS::EC2::NatGateway", + "DependsOn": [ + "NetworkPublicSubnet2DefaultRoute", + "NetworkPublicSubnet2RouteTableAssociation" + ], + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "NetworkPublicSubnet2EIP", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "NetworkPublicSubnet2" + }, + "Tags": [ + { + "Key": "Name", + "Value": "gitlab-server-public-subnet-ngw" + } + ] + } + }, + "NetworkPrivateSubnet1Subnet": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "AvailabilityZone": { + "Fn::Select": [ + 0, + { + "Fn::GetAZs": { + "Ref": "AWS::Region" + } + } + ] + }, + "CidrBlock": "10.0.128.0/18", + "MapPublicIpOnLaunch": false, + "VpcId": { + "Ref": "NetworkVPC" + }, + "Tags": [ + { + "Key": "Name", + "Value": "gitlab-server-private-subnet-1" + } + ] + } + }, + "NetworkPrivateSubnet1RouteTable": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "NetworkVPC" + }, + "Tags": [ + { + "Key": "Name", + "Value": "gitlab-server-private-subnet-1-rt" + } + ] + } + }, + "NetworkPrivateSubnet1RouteTableAssociation": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "NetworkPrivateSubnet1RouteTable" + }, + "SubnetId": { + "Ref": "NetworkPrivateSubnet1Subnet" + } + } + }, + "NetworkPrivateSubnet1DefaultRoute": { + "Type": "AWS::EC2::Route", + "Properties": { + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "NetworkPublicSubnet1NATGateway" + }, + "RouteTableId": { + "Ref": "NetworkPrivateSubnet1RouteTable" + } + } + }, + "NetworkPrivateSubnet2Subnet": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "AvailabilityZone": { + "Fn::Select": [ + 1, + { + "Fn::GetAZs": { + "Ref": "AWS::Region" + } + } + ] + }, + "CidrBlock": "10.0.192.0/18", + "MapPublicIpOnLaunch": false, + "VpcId": { + "Ref": "NetworkVPC" + }, + "Tags": [ + { + "Key": "Name", + "Value": "gitlab-server-private-subnet-2" + } + ] + } + }, + "NetworkPrivateSubnet2RouteTable": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "NetworkVPC" + }, + "Tags": [ + { + "Key": "Name", + "Value": "gitlab-server-private-subnet-2-rt" + } + ] + } + }, + "NetworkPrivateSubnet2RouteTableAssociation": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "NetworkPrivateSubnet2RouteTable" + }, + "SubnetId": { + "Ref": "NetworkPrivateSubnet2Subnet" + } + } + }, + "NetworkPrivateSubnet2DefaultRoute": { + "Type": "AWS::EC2::Route", + "Properties": { + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "NetworkPublicSubnet2NATGateway" + }, + "RouteTableId": { + "Ref": "NetworkPrivateSubnet2RouteTable" + } + } + }, + "NetworkInternetGateway": { + "Type": "AWS::EC2::InternetGateway", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "gitlab-server" + } + ] + } + }, + "NetworkVPCGW": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "InternetGatewayId": { + "Ref": "NetworkInternetGateway" + }, + "VpcId": { + "Ref": "NetworkVPC" + } + } + }, + "CloudFrontCachePolicy": { + "Type": "AWS::CloudFront::CachePolicy", + "Properties": { + "CachePolicyConfig": { + "DefaultTTL": 86400, + "MaxTTL": 31536000, + "MinTTL": 1, + "Name": "gitlab-server", + "ParametersInCacheKeyAndForwardedToOrigin": { + "CookiesConfig": { + "CookieBehavior": "all" + }, + "EnableAcceptEncodingGzip": false, + "HeadersConfig": { + "HeaderBehavior": "whitelist", + "Headers": [ + "Accept-Charset", + "Authorization", + "Origin", + "Accept", + "Referer", + "Host", + "Accept-Language", + "Accept-Encoding", + "Accept-Datetime" + ] + }, + "QueryStringsConfig": { + "QueryStringBehavior": "all" + } + } + } + } + }, + "CloudFrontDistribution": { + "Type": "AWS::CloudFront::Distribution", + "DependsOn": [ + "Server" + ], + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "gitlab-server" + }, + { + "Key": "Description", + "Value": "gitlab-server" + } + ], + "DistributionConfig": { + "Enabled": true, + "HttpVersion": "http2", + "CacheBehaviors": [ + { + "AllowedMethods": [ + "GET", + "HEAD", + "OPTIONS", + "PUT", + "PATCH", + "POST", + "DELETE" + ], + "CachePolicyId": "4135ea2d-6df8-44a3-9df3-4b5a84be39ad", + "Compress": false, + "OriginRequestPolicyId": "216adef6-5c7f-47e4-b989-5492eafa07d3", + "TargetOriginId": { + "Fn::Sub": "CloudFront-${AWS::StackName}" + }, + "ViewerProtocolPolicy": "allow-all", + "PathPattern": "/proxy/*" + } + ], + "DefaultCacheBehavior": { + "AllowedMethods": [ + "GET", + "HEAD", + "OPTIONS", + "PUT", + "PATCH", + "POST", + "DELETE" + ], + "CachePolicyId": { + "Ref": "CloudFrontCachePolicy" + }, + "OriginRequestPolicyId": "216adef6-5c7f-47e4-b989-5492eafa07d3", + "TargetOriginId": { + "Fn::Sub": "CloudFront-${AWS::StackName}" + }, + "ViewerProtocolPolicy": "allow-all" + }, + "Origins": [ + { + "DomainName": { + "Fn::GetAtt": [ + "Server", + "PublicDnsName" + ] + }, + "Id": { + "Fn::Sub": "CloudFront-${AWS::StackName}" + }, + "CustomOriginConfig": { + "HTTPPort": 80, + "OriginProtocolPolicy": "http-only" + } + } + ] + } + } + } + }, + "Outputs": { + "URL": { + "Value": { + "Fn::Sub": "https://${CloudFrontDistribution.DomainName}/?folder=/home/ec2-user" + } + } + } +} diff --git a/Solutions/GitLab/GitLabServer-pkg.yaml b/Solutions/GitLab/GitLabServer-pkg.yaml index c99b7c8b..4365d6ea 100644 --- a/Solutions/GitLab/GitLabServer-pkg.yaml +++ b/Solutions/GitLab/GitLabServer-pkg.yaml @@ -106,9 +106,10 @@ Resources: DependsOn: - InstanceRolePolicy - InstanceRole - #CreationPolicy: - #ResourceSignal: - #Timeout: PT30M + + #CreationPolicy: + #ResourceSignal: + #Timeout: PT30M Properties: AvailabilityZone: !Select - 0 @@ -431,4 +432,3 @@ Resources: Outputs: URL: Value: !Sub https://${CloudFrontDistribution.DomainName}/?folder=/home/ec2-user - diff --git a/Solutions/GitLab/GitLabServer.json b/Solutions/GitLab/GitLabServer.json new file mode 100644 index 00000000..e2f6ccaa --- /dev/null +++ b/Solutions/GitLab/GitLabServer.json @@ -0,0 +1,263 @@ +{ + "Parameters": { + "LatestAMI": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-6.1-x86_64" + }, + "InstanceType": { + "Type": "String", + "Default": "m5.large" + } + }, + "Mappings": { + "Prefixes": { + "ap-northeast-1": { + "PrefixList": "pl-58a04531" + }, + "ap-northeast-2": { + "PrefixList": "pl-22a6434b" + }, + "ap-south-1": { + "PrefixList": "pl-9aa247f3" + }, + "ap-southeast-1": { + "PrefixList": "pl-31a34658" + }, + "ap-southeast-2": { + "PrefixList": "pl-b8a742d1" + }, + "ca-central-1": { + "PrefixList": "pl-38a64351" + }, + "eu-central-1": { + "PrefixList": "pl-a3a144ca" + }, + "eu-north-1": { + "PrefixList": "pl-fab65393" + }, + "eu-west-1": { + "PrefixList": "pl-4fa04526" + }, + "eu-west-2": { + "PrefixList": "pl-93a247fa" + }, + "eu-west-3": { + "PrefixList": "pl-75b1541c" + }, + "sa-east-1": { + "PrefixList": "pl-5da64334" + }, + "us-east-1": { + "PrefixList": "pl-3b927c52" + }, + "us-east-2": { + "PrefixList": "pl-b6a144df" + }, + "us-west-1": { + "PrefixList": "pl-4ea04527" + }, + "us-west-2": { + "PrefixList": "pl-82a045eb" + } + } + }, + "Resources": { + "Network": { + "Type": { + "Rain::Module": "../../RainModules/vpc.yml" + }, + "Properties": { + "Name": "gitlab-server" + } + }, + "InstanceSecurityGroup": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "gitlab-server-isg", + "SecurityGroupIngress": [ + { + "Description": "Allow HTTP from com.amazonaws.global.cloudfront.origin-facing", + "IpProtocol": "tcp", + "FromPort": 80, + "ToPort": 80, + "SourcePrefixListId": { + "Fn::FindInMap": [ + "Prefixes", + { + "Ref": "AWS::Region" + }, + "PrefixList" + ] + } + } + ], + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "Tags": [ + { + "Key": "Name", + "Value": "gitlab-server-isg" + } + ], + "VpcId": { + "Ref": "NetworkVPC" + } + } + }, + "InstanceRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ec2.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "Tags": [ + { + "Key": "Name", + "Value": "gitlab-server-instance" + } + ] + } + }, + "InstanceRolePolicy": { + "Type": "AWS::IAM::RolePolicy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "ec2messages:*", + "ssm:UpdateInstanceInformation", + "ssmmessages:*", + "secretsmanager:GetSecretValue" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "InstanceRolePolicy", + "RoleName": { + "Ref": "InstanceRole" + } + } + }, + "InstanceProfile": { + "Type": "AWS::IAM::InstanceProfile", + "Properties": { + "Roles": [ + { + "Ref": "InstanceRole" + } + ] + } + }, + "Server": { + "CreationPolicy": { + "ResourceSignal": { + "Timeout": "PT30M" + } + }, + "Type": "AWS::EC2::Instance", + "DependsOn": [ + "InstanceRolePolicy", + "InstanceRole" + ], + "Properties": { + "AvailabilityZone": { + "Fn::Select": [ + 0, + { + "Fn::GetAZs": null + } + ] + }, + "BlockDeviceMappings": [ + { + "DeviceName": "/dev/xvda", + "Ebs": { + "VolumeSize": 128 + } + } + ], + "IamInstanceProfile": { + "Ref": "InstanceProfile" + }, + "ImageId": { + "Ref": "LatestAMI" + }, + "InstanceType": { + "Ref": "InstanceType" + }, + "SecurityGroupIds": [ + { + "Fn::GetAtt": [ + "InstanceSecurityGroup", + "GroupId" + ] + } + ], + "SubnetId": { + "Fn::GetAtt": [ + "NetworkPublicSubnet1", + "SubnetId" + ] + }, + "Tags": [ + { + "Key": "Name", + "Value": "gitlab-server" + } + ], + "UserData": { + "Fn::Base64": { + "Fn::Sub": { + "Rain::Embed": "GitLabServer.sh" + } + } + } + } + }, + "CloudFront": { + "Type": { + "Rain::Module": "../../RainModules/cloudfront-nocache.yml" + }, + "Properties": { + "Name": "gitlab-server", + "DomainName": { + "Fn::GetAtt": [ + "Server", + "PublicDnsName" + ] + }, + "Port": 80 + }, + "Overrides": { + "Distribution": { + "DependsOn": "Server" + } + } + } + }, + "Outputs": { + "URL": { + "Value": { + "Fn::Sub": "https://${CloudFrontDistribution.DomainName}/?folder=/home/ec2-user" + } + } + } +} diff --git a/Solutions/GitLab/GitLabServer.yaml b/Solutions/GitLab/GitLabServer.yaml index 44d23f1d..261e26b6 100644 --- a/Solutions/GitLab/GitLabServer.yaml +++ b/Solutions/GitLab/GitLabServer.yaml @@ -1,15 +1,13 @@ Parameters: - LatestAMI: Type: AWS::SSM::Parameter::Value Default: /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-6.1-x86_64 InstanceType: Type: String - Default: m5.large + Default: m5.large Mappings: - Prefixes: ap-northeast-1: PrefixList: pl-58a04531 @@ -45,9 +43,8 @@ Mappings: PrefixList: pl-82a045eb Resources: - Network: - Type: !Rain::Module "../../RainModules/vpc.yml" + Type: !Rain::Module ../../RainModules/vpc.yml Properties: Name: gitlab-server @@ -60,14 +57,17 @@ Resources: IpProtocol: tcp FromPort: 80 ToPort: 80 - SourcePrefixListId: !FindInMap [Prefixes, !Ref 'AWS::Region', PrefixList] + SourcePrefixListId: !FindInMap + - Prefixes + - !Ref AWS::Region + - PrefixList SecurityGroupEgress: - CidrIp: 0.0.0.0/0 Description: Allow all outbound traffic by default IpProtocol: "-1" Tags: - Key: Name - Value: gitlab-server-isg + Value: gitlab-server-isg VpcId: !Ref NetworkVPC InstanceRole: @@ -124,19 +124,18 @@ Resources: VolumeSize: 128 IamInstanceProfile: !Ref InstanceProfile ImageId: !Ref LatestAMI - InstanceType: !Ref InstanceType + InstanceType: !Ref InstanceType SecurityGroupIds: - !GetAtt InstanceSecurityGroup.GroupId SubnetId: !GetAtt NetworkPublicSubnet1.SubnetId Tags: - Key: Name - Value: gitlab-server - UserData: - Fn::Base64: - Fn::Sub: !Rain::Embed GitLabServer.sh + Value: gitlab-server + UserData: !Base64 + Fn::Sub: !Rain::Embed GitLabServer.sh CloudFront: - Type: !Rain::Module "../../RainModules/cloudfront-nocache.yml" + Type: !Rain::Module ../../RainModules/cloudfront-nocache.yml Properties: Name: gitlab-server DomainName: !GetAtt Server.PublicDnsName @@ -146,7 +145,5 @@ Resources: DependsOn: Server Outputs: - URL: Value: !Sub https://${CloudFrontDistribution.DomainName}/?folder=/home/ec2-user - diff --git a/Solutions/GitLab/README.yaml b/Solutions/GitLab/README.md similarity index 100% rename from Solutions/GitLab/README.yaml rename to Solutions/GitLab/README.md diff --git a/Solutions/GitLabAndVSCode/GitLabAndVSCode.json b/Solutions/GitLabAndVSCode/GitLabAndVSCode.json new file mode 100644 index 00000000..3774e278 --- /dev/null +++ b/Solutions/GitLabAndVSCode/GitLabAndVSCode.json @@ -0,0 +1,357 @@ +{ + "Parameters": { + "LatestAMI": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-6.1-x86_64" + }, + "InstanceType": { + "Type": "String", + "Default": "m5.large" + }, + "SecretName": { + "Description": "The name of the secrets manager secret that stores the password to be used for the VSCode Server. The password must be a simple plaintext string with no JSON.", + "Type": "String", + "Default": "vscode-password" + } + }, + "Mappings": { + "Prefixes": { + "ap-northeast-1": { + "PrefixList": "pl-58a04531" + }, + "ap-northeast-2": { + "PrefixList": "pl-22a6434b" + }, + "ap-south-1": { + "PrefixList": "pl-9aa247f3" + }, + "ap-southeast-1": { + "PrefixList": "pl-31a34658" + }, + "ap-southeast-2": { + "PrefixList": "pl-b8a742d1" + }, + "ca-central-1": { + "PrefixList": "pl-38a64351" + }, + "eu-central-1": { + "PrefixList": "pl-a3a144ca" + }, + "eu-north-1": { + "PrefixList": "pl-fab65393" + }, + "eu-west-1": { + "PrefixList": "pl-4fa04526" + }, + "eu-west-2": { + "PrefixList": "pl-93a247fa" + }, + "eu-west-3": { + "PrefixList": "pl-75b1541c" + }, + "sa-east-1": { + "PrefixList": "pl-5da64334" + }, + "us-east-1": { + "PrefixList": "pl-3b927c52" + }, + "us-east-2": { + "PrefixList": "pl-b6a144df" + }, + "us-west-1": { + "PrefixList": "pl-4ea04527" + }, + "us-west-2": { + "PrefixList": "pl-82a045eb" + } + } + }, + "Resources": { + "Network": { + "Type": { + "Rain::Module": "../../RainModules/vpc.yml" + }, + "Properties": { + "Name": "gitlab-server" + } + }, + "InstanceSecurityGroup": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "gitlab-server-isg", + "SecurityGroupIngress": [ + { + "Description": "Allow HTTP from com.amazonaws.global.cloudfront.origin-facing", + "IpProtocol": "tcp", + "FromPort": 80, + "ToPort": 80, + "SourcePrefixListId": { + "Fn::FindInMap": [ + "Prefixes", + { + "Ref": "AWS::Region" + }, + "PrefixList" + ] + } + } + ], + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "Tags": [ + { + "Key": "Name", + "Value": "gitlab-server-isg" + } + ], + "VpcId": { + "Ref": "NetworkVPC" + } + } + }, + "InstanceRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ec2.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "Tags": [ + { + "Key": "Name", + "Value": "gitlab-server-instance" + } + ] + } + }, + "InstanceRolePolicy": { + "Type": "AWS::IAM::RolePolicy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "ec2messages:*", + "ssm:UpdateInstanceInformation", + "ssmmessages:*", + "secretsmanager:GetSecretValue" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "InstanceRolePolicy", + "RoleName": { + "Ref": "InstanceRole" + } + } + }, + "InstanceProfile": { + "Type": "AWS::IAM::InstanceProfile", + "Properties": { + "Roles": [ + { + "Ref": "InstanceRole" + } + ] + } + }, + "GitLabServer": { + "CreationPolicy": { + "ResourceSignal": { + "Timeout": "PT30M" + } + }, + "Type": "AWS::EC2::Instance", + "DependsOn": [ + "InstanceRolePolicy", + "InstanceRole" + ], + "Properties": { + "AvailabilityZone": { + "Fn::Select": [ + 0, + { + "Fn::GetAZs": null + } + ] + }, + "BlockDeviceMappings": [ + { + "DeviceName": "/dev/xvda", + "Ebs": { + "VolumeSize": 128 + } + } + ], + "IamInstanceProfile": { + "Ref": "InstanceProfile" + }, + "ImageId": { + "Ref": "LatestAMI" + }, + "InstanceType": { + "Ref": "InstanceType" + }, + "SecurityGroupIds": [ + { + "Fn::GetAtt": [ + "InstanceSecurityGroup", + "GroupId" + ] + } + ], + "SubnetId": { + "Fn::GetAtt": [ + "NetworkPublicSubnet1", + "SubnetId" + ] + }, + "Tags": [ + { + "Key": "Name", + "Value": "gitlab-server" + } + ], + "UserData": { + "Fn::Base64": { + "Fn::Sub": { + "Rain::Embed": "GitLabServer.sh" + } + } + } + } + }, + "VSCodeServer": { + "CreationPolicy": { + "ResourceSignal": { + "Timeout": "PT5M" + } + }, + "Type": "AWS::EC2::Instance", + "DependsOn": [ + "InstanceRolePolicy", + "InstanceRole" + ], + "Properties": { + "AvailabilityZone": { + "Fn::Select": [ + 0, + { + "Fn::GetAZs": null + } + ] + }, + "BlockDeviceMappings": [ + { + "DeviceName": "/dev/xvda", + "Ebs": { + "VolumeSize": 128 + } + } + ], + "IamInstanceProfile": { + "Ref": "InstanceProfile" + }, + "ImageId": { + "Ref": "LatestAMI" + }, + "InstanceType": { + "Ref": "InstanceType" + }, + "SecurityGroupIds": [ + { + "Fn::GetAtt": [ + "InstanceSecurityGroup", + "GroupId" + ] + } + ], + "SubnetId": { + "Fn::GetAtt": [ + "NetworkPublicSubnet1", + "SubnetId" + ] + }, + "Tags": [ + { + "Key": "Name", + "Value": "vscode-server" + } + ], + "UserData": { + "Fn::Base64": { + "Fn::Sub": { + "Rain::Embed": "VSCodeServer.sh" + } + } + } + } + }, + "GitLabCloudFront": { + "Type": { + "Rain::Module": "../../RainModules/cloudfront-nocache.yml" + }, + "Properties": { + "Name": "gitlab-server", + "DomainName": { + "Fn::GetAtt": [ + "GitLabServer", + "PublicDnsName" + ] + }, + "Port": 80 + }, + "Overrides": { + "Distribution": { + "DependsOn": "GitLabServer" + } + } + }, + "VSCodeCloudFront": { + "Type": { + "Rain::Module": "../../RainModules/cloudfront-nocache.yml" + }, + "Properties": { + "Name": "vscode-server", + "DomainName": { + "Fn::GetAtt": [ + "VSCodeServer", + "PublicDnsName" + ] + }, + "Port": 8080 + }, + "Overrides": { + "Distribution": "DependsOn:VSCodeServer" + } + } + }, + "Outputs": { + "VSCodeURL": { + "Value": { + "Fn::Sub": "https://${VSCodeCloudFrontDistribution.DomainName}/?folder=/home/ec2-user" + } + }, + "GitLabURL": { + "Value": { + "Fn::Sub": "https://${GitLabCloudFrontDistribution.DomainName}/?folder=/home/ec2-user" + } + } + } +} diff --git a/Solutions/GitLabAndVSCode/GitLabAndVSCode.yaml b/Solutions/GitLabAndVSCode/GitLabAndVSCode.yaml index 0f6ab40e..c1a86612 100644 --- a/Solutions/GitLabAndVSCode/GitLabAndVSCode.yaml +++ b/Solutions/GitLabAndVSCode/GitLabAndVSCode.yaml @@ -1,21 +1,18 @@ Parameters: - LatestAMI: Type: AWS::SSM::Parameter::Value Default: /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-6.1-x86_64 InstanceType: Type: String - Default: m5.large + Default: m5.large SecretName: Type: String Default: vscode-password Description: The name of the secrets manager secret that stores the password to be used for the VSCode Server. The password must be a simple plaintext string with no JSON. - Mappings: - Prefixes: ap-northeast-1: PrefixList: pl-58a04531 @@ -51,9 +48,8 @@ Mappings: PrefixList: pl-82a045eb Resources: - Network: - Type: !Rain::Module "../../RainModules/vpc.yml" + Type: !Rain::Module ../../RainModules/vpc.yml Properties: Name: gitlab-server @@ -66,14 +62,17 @@ Resources: IpProtocol: tcp FromPort: 80 ToPort: 80 - SourcePrefixListId: !FindInMap [Prefixes, !Ref 'AWS::Region', PrefixList] + SourcePrefixListId: !FindInMap + - Prefixes + - !Ref AWS::Region + - PrefixList SecurityGroupEgress: - CidrIp: 0.0.0.0/0 Description: Allow all outbound traffic by default IpProtocol: "-1" Tags: - Key: Name - Value: gitlab-server-isg + Value: gitlab-server-isg VpcId: !Ref NetworkVPC InstanceRole: @@ -130,16 +129,15 @@ Resources: VolumeSize: 128 IamInstanceProfile: !Ref InstanceProfile ImageId: !Ref LatestAMI - InstanceType: !Ref InstanceType + InstanceType: !Ref InstanceType SecurityGroupIds: - !GetAtt InstanceSecurityGroup.GroupId SubnetId: !GetAtt NetworkPublicSubnet1.SubnetId Tags: - Key: Name - Value: gitlab-server - UserData: - Fn::Base64: - Fn::Sub: !Rain::Embed GitLabServer.sh + Value: gitlab-server + UserData: !Base64 + Fn::Sub: !Rain::Embed GitLabServer.sh VSCodeServer: Type: AWS::EC2::Instance @@ -159,39 +157,38 @@ Resources: VolumeSize: 128 IamInstanceProfile: !Ref InstanceProfile ImageId: !Ref LatestAMI - InstanceType: !Ref InstanceType + InstanceType: !Ref InstanceType SecurityGroupIds: - !GetAtt InstanceSecurityGroup.GroupId SubnetId: !GetAtt NetworkPublicSubnet1.SubnetId Tags: - Key: Name - Value: vscode-server - UserData: - Fn::Base64: - Fn::Sub: !Rain::Embed VSCodeServer.sh + Value: vscode-server + UserData: !Base64 + Fn::Sub: !Rain::Embed VSCodeServer.sh GitLabCloudFront: - Type: !Rain::Module "../../RainModules/cloudfront-nocache.yml" + Type: !Rain::Module ../../RainModules/cloudfront-nocache.yml Properties: Name: gitlab-server - DomainName: !GetAtt Server.PublicDnsName + DomainName: !GetAtt GitLabServer.PublicDnsName Port: 80 Overrides: Distribution: DependsOn: GitLabServer VSCodeCloudFront: - Type: !Rain::Module "../../RainModules/cloudfront-nocache.yml" + Type: !Rain::Module ../../RainModules/cloudfront-nocache.yml Properties: Name: vscode-server - DomainName: !GetAtt Server.PublicDnsName + DomainName: !GetAtt VSCodeServer.PublicDnsName Port: 8080 Overrides: - Distribution: - DependsOn:VSCodeServer + Distribution: DependsOn:VSCodeServer Outputs: + VSCodeURL: + Value: !Sub https://${VSCodeCloudFrontDistribution.DomainName}/?folder=/home/ec2-user - URL: - Value: !Sub https://${CloudFrontDistribution.DomainName}/?folder=/home/ec2-user - + GitLabURL: + Value: !Sub https://${GitLabCloudFrontDistribution.DomainName}/?folder=/home/ec2-user diff --git a/Solutions/Gitea/Gitea-pkg.json b/Solutions/Gitea/Gitea-pkg.json new file mode 100644 index 00000000..05cfab4d --- /dev/null +++ b/Solutions/Gitea/Gitea-pkg.json @@ -0,0 +1,746 @@ +{ + "Parameters": { + "LatestAMI": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-6.1-x86_64" + }, + "InstanceType": { + "Type": "String", + "Default": "m7i.xlarge" + }, + "SecretName": { + "Description": "The name of the secrets manager secret to be used as the password for the Gitea admin1 user. The password must be a plaintext string, not JSON.", + "Type": "String", + "Default": "gitea-password" + } + }, + "Mappings": { + "Prefixes": { + "ap-northeast-1": { + "PrefixList": "pl-58a04531" + }, + "ap-northeast-2": { + "PrefixList": "pl-22a6434b" + }, + "ap-south-1": { + "PrefixList": "pl-9aa247f3" + }, + "ap-southeast-1": { + "PrefixList": "pl-31a34658" + }, + "ap-southeast-2": { + "PrefixList": "pl-b8a742d1" + }, + "ca-central-1": { + "PrefixList": "pl-38a64351" + }, + "eu-central-1": { + "PrefixList": "pl-a3a144ca" + }, + "eu-north-1": { + "PrefixList": "pl-fab65393" + }, + "eu-west-1": { + "PrefixList": "pl-4fa04526" + }, + "eu-west-2": { + "PrefixList": "pl-93a247fa" + }, + "eu-west-3": { + "PrefixList": "pl-75b1541c" + }, + "sa-east-1": { + "PrefixList": "pl-5da64334" + }, + "us-east-1": { + "PrefixList": "pl-3b927c52" + }, + "us-east-2": { + "PrefixList": "pl-b6a144df" + }, + "us-west-1": { + "PrefixList": "pl-4ea04527" + }, + "us-west-2": { + "PrefixList": "pl-82a045eb" + } + } + }, + "Resources": { + "InstanceSecurityGroup": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "gitea-server-isg", + "SecurityGroupIngress": [ + { + "Description": "Allow HTTP from com.amazonaws.global.cloudfront.origin-facing", + "IpProtocol": "tcp", + "FromPort": 8080, + "ToPort": 8080, + "SourcePrefixListId": { + "Fn::FindInMap": [ + "Prefixes", + { + "Ref": "AWS::Region" + }, + "PrefixList" + ] + } + } + ], + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "Tags": [ + { + "Key": "Name", + "Value": "gitea-server-isg" + } + ], + "VpcId": { + "Ref": "NetworkVPC" + } + } + }, + "InstanceRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ec2.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "Tags": [ + { + "Key": "Name", + "Value": "gitea-server-instance" + } + ] + } + }, + "InstanceRolePolicy": { + "Type": "AWS::IAM::RolePolicy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "ec2messages:*", + "ssm:UpdateInstanceInformation", + "ssmmessages:*", + "secretsmanager:GetSecretValue" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "InstanceRolePolicy", + "RoleName": { + "Ref": "InstanceRole" + } + } + }, + "InstanceProfile": { + "Type": "AWS::IAM::InstanceProfile", + "Properties": { + "Roles": [ + { + "Ref": "InstanceRole" + } + ] + } + }, + "Server": { + "CreationPolicy": { + "ResourceSignal": { + "Timeout": "PT20M" + } + }, + "Type": "AWS::EC2::Instance", + "DependsOn": [ + "InstanceRolePolicy", + "InstanceRole" + ], + "Properties": { + "AvailabilityZone": { + "Fn::Select": [ + 0, + { + "Fn::GetAZs": null + } + ] + }, + "BlockDeviceMappings": [ + { + "DeviceName": "/dev/xvda", + "Ebs": { + "VolumeSize": 128 + } + } + ], + "IamInstanceProfile": { + "Ref": "InstanceProfile" + }, + "ImageId": { + "Ref": "LatestAMI" + }, + "InstanceType": { + "Ref": "InstanceType" + }, + "SecurityGroupIds": [ + { + "Fn::GetAtt": [ + "InstanceSecurityGroup", + "GroupId" + ] + } + ], + "SubnetId": { + "Fn::GetAtt": [ + "NetworkPublicSubnet1", + "SubnetId" + ] + }, + "Tags": [ + { + "Key": "Name", + "Value": "gitea-server" + } + ], + "UserData": { + "Fn::Base64": { + "Fn::Sub": "#!/bin/bash\n\nset -eou pipefail\n\nlocal_ip=$(ec2-metadata | grep \"^local-ipv4: \" | cut -d \" \" -f 2)\n\n# Get the password from secrets manager\nsecret_string=$(aws secretsmanager get-secret-value --secret-id ${SecretName} | jq -r \".SecretString\")\n\n# Install cfn-signal\nyum install -y aws-cfn-bootstrap\n\n# Install go\nyum install -y go\n\n# Install nodejs\nyum install -y nodejs\n\n# Clone the repo and build Gitea\nsudo -u ec2-user -i <", + "Default": "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-6.1-x86_64" + }, + "InstanceType": { + "Type": "String", + "Default": "m7i.xlarge" + }, + "SecretName": { + "Description": "The name of the secrets manager secret to be used as the password for the Gitea admin1 user. The password must be a plaintext string, not JSON.", + "Type": "String", + "Default": "gitea-password" + } + }, + "Mappings": { + "Prefixes": { + "ap-northeast-1": { + "PrefixList": "pl-58a04531" + }, + "ap-northeast-2": { + "PrefixList": "pl-22a6434b" + }, + "ap-south-1": { + "PrefixList": "pl-9aa247f3" + }, + "ap-southeast-1": { + "PrefixList": "pl-31a34658" + }, + "ap-southeast-2": { + "PrefixList": "pl-b8a742d1" + }, + "ca-central-1": { + "PrefixList": "pl-38a64351" + }, + "eu-central-1": { + "PrefixList": "pl-a3a144ca" + }, + "eu-north-1": { + "PrefixList": "pl-fab65393" + }, + "eu-west-1": { + "PrefixList": "pl-4fa04526" + }, + "eu-west-2": { + "PrefixList": "pl-93a247fa" + }, + "eu-west-3": { + "PrefixList": "pl-75b1541c" + }, + "sa-east-1": { + "PrefixList": "pl-5da64334" + }, + "us-east-1": { + "PrefixList": "pl-3b927c52" + }, + "us-east-2": { + "PrefixList": "pl-b6a144df" + }, + "us-west-1": { + "PrefixList": "pl-4ea04527" + }, + "us-west-2": { + "PrefixList": "pl-82a045eb" + } + } + }, + "Resources": { + "Network": { + "Type": { + "Rain::Module": "../../RainModules/vpc.yml" + }, + "Properties": { + "Name": "gitea-server" + } + }, + "InstanceSecurityGroup": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "gitea-server-isg", + "SecurityGroupIngress": [ + { + "Description": "Allow HTTP from com.amazonaws.global.cloudfront.origin-facing", + "IpProtocol": "tcp", + "FromPort": 8080, + "ToPort": 8080, + "SourcePrefixListId": { + "Fn::FindInMap": [ + "Prefixes", + { + "Ref": "AWS::Region" + }, + "PrefixList" + ] + } + } + ], + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "Tags": [ + { + "Key": "Name", + "Value": "gitea-server-isg" + } + ], + "VpcId": { + "Ref": "NetworkVPC" + } + } + }, + "InstanceRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ec2.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "Tags": [ + { + "Key": "Name", + "Value": "gitea-server-instance" + } + ] + } + }, + "InstanceRolePolicy": { + "Type": "AWS::IAM::RolePolicy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "ec2messages:*", + "ssm:UpdateInstanceInformation", + "ssmmessages:*", + "secretsmanager:GetSecretValue" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "InstanceRolePolicy", + "RoleName": { + "Ref": "InstanceRole" + } + } + }, + "InstanceProfile": { + "Type": "AWS::IAM::InstanceProfile", + "Properties": { + "Roles": [ + { + "Ref": "InstanceRole" + } + ] + } + }, + "Server": { + "CreationPolicy": { + "ResourceSignal": { + "Timeout": "PT20M" + } + }, + "Type": "AWS::EC2::Instance", + "DependsOn": [ + "InstanceRolePolicy", + "InstanceRole" + ], + "Properties": { + "AvailabilityZone": { + "Fn::Select": [ + 0, + { + "Fn::GetAZs": null + } + ] + }, + "BlockDeviceMappings": [ + { + "DeviceName": "/dev/xvda", + "Ebs": { + "VolumeSize": 128 + } + } + ], + "IamInstanceProfile": { + "Ref": "InstanceProfile" + }, + "ImageId": { + "Ref": "LatestAMI" + }, + "InstanceType": { + "Ref": "InstanceType" + }, + "SecurityGroupIds": [ + { + "Fn::GetAtt": [ + "InstanceSecurityGroup", + "GroupId" + ] + } + ], + "SubnetId": { + "Fn::GetAtt": [ + "NetworkPublicSubnet1", + "SubnetId" + ] + }, + "Tags": [ + { + "Key": "Name", + "Value": "gitea-server" + } + ], + "UserData": { + "Fn::Base64": { + "Fn::Sub": { + "Rain::Embed": "Gitea.sh" + } + } + } + } + }, + "CloudFront": { + "Type": { + "Rain::Module": "../../RainModules/cloudfront-nocache.yml" + }, + "Properties": { + "Name": "gitea-server", + "DomainName": { + "Fn::GetAtt": [ + "Server", + "PublicDnsName" + ] + }, + "Port": 8080 + }, + "Overrides": { + "Distribution": { + "DependsOn": "Server" + } + } + } + }, + "Outputs": { + "URL": { + "Value": { + "Fn::Sub": "https://${CloudFrontDistribution.DomainName}" + } + } + } +} diff --git a/Solutions/Gitea/Gitea.yaml b/Solutions/Gitea/Gitea.yaml index 3c931fd5..a2c1b2a4 100644 --- a/Solutions/Gitea/Gitea.yaml +++ b/Solutions/Gitea/Gitea.yaml @@ -1,5 +1,4 @@ Parameters: - LatestAMI: Type: AWS::SSM::Parameter::Value Default: /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-6.1-x86_64 @@ -14,7 +13,6 @@ Parameters: Description: The name of the secrets manager secret to be used as the password for the Gitea admin1 user. The password must be a plaintext string, not JSON. Mappings: - Prefixes: ap-northeast-1: PrefixList: pl-58a04531 @@ -50,9 +48,8 @@ Mappings: PrefixList: pl-82a045eb Resources: - Network: - Type: !Rain::Module "../../RainModules/vpc.yml" + Type: !Rain::Module ../../RainModules/vpc.yml Properties: Name: gitea-server @@ -65,14 +62,17 @@ Resources: IpProtocol: tcp FromPort: 8080 ToPort: 8080 - SourcePrefixListId: !FindInMap [Prefixes, !Ref 'AWS::Region', PrefixList] + SourcePrefixListId: !FindInMap + - Prefixes + - !Ref AWS::Region + - PrefixList SecurityGroupEgress: - CidrIp: 0.0.0.0/0 Description: Allow all outbound traffic by default IpProtocol: "-1" Tags: - Key: Name - Value: gitea-server-isg + Value: gitea-server-isg VpcId: !Ref NetworkVPC InstanceRole: @@ -129,19 +129,18 @@ Resources: VolumeSize: 128 IamInstanceProfile: !Ref InstanceProfile ImageId: !Ref LatestAMI - InstanceType: !Ref InstanceType + InstanceType: !Ref InstanceType SecurityGroupIds: - !GetAtt InstanceSecurityGroup.GroupId SubnetId: !GetAtt NetworkPublicSubnet1.SubnetId Tags: - Key: Name - Value: gitea-server - UserData: - Fn::Base64: - Fn::Sub: !Rain::Embed Gitea.sh + Value: gitea-server + UserData: !Base64 + Fn::Sub: !Rain::Embed Gitea.sh CloudFront: - Type: !Rain::Module "../../RainModules/cloudfront-nocache.yml" + Type: !Rain::Module ../../RainModules/cloudfront-nocache.yml Properties: Name: gitea-server DomainName: !GetAtt Server.PublicDnsName @@ -151,7 +150,5 @@ Resources: DependsOn: Server Outputs: - URL: Value: !Sub https://${CloudFrontDistribution.DomainName} - diff --git a/Solutions/VSCode/VSCodeServer-pkg.json b/Solutions/VSCode/VSCodeServer-pkg.json new file mode 100644 index 00000000..720cdbfe --- /dev/null +++ b/Solutions/VSCode/VSCodeServer-pkg.json @@ -0,0 +1,746 @@ +{ + "Parameters": { + "LatestAMI": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-6.1-x86_64" + }, + "InstanceType": { + "Type": "String", + "Default": "t3.medium" + }, + "SecretName": { + "Description": "The name of the secrets manager secret that stores the password to be used for the VSCode Server. The password must be a simple plaintext string with no JSON.", + "Type": "String", + "Default": "vscode-password" + } + }, + "Mappings": { + "Prefixes": { + "ap-northeast-1": { + "PrefixList": "pl-58a04531" + }, + "ap-northeast-2": { + "PrefixList": "pl-22a6434b" + }, + "ap-south-1": { + "PrefixList": "pl-9aa247f3" + }, + "ap-southeast-1": { + "PrefixList": "pl-31a34658" + }, + "ap-southeast-2": { + "PrefixList": "pl-b8a742d1" + }, + "ca-central-1": { + "PrefixList": "pl-38a64351" + }, + "eu-central-1": { + "PrefixList": "pl-a3a144ca" + }, + "eu-north-1": { + "PrefixList": "pl-fab65393" + }, + "eu-west-1": { + "PrefixList": "pl-4fa04526" + }, + "eu-west-2": { + "PrefixList": "pl-93a247fa" + }, + "eu-west-3": { + "PrefixList": "pl-75b1541c" + }, + "sa-east-1": { + "PrefixList": "pl-5da64334" + }, + "us-east-1": { + "PrefixList": "pl-3b927c52" + }, + "us-east-2": { + "PrefixList": "pl-b6a144df" + }, + "us-west-1": { + "PrefixList": "pl-4ea04527" + }, + "us-west-2": { + "PrefixList": "pl-82a045eb" + } + } + }, + "Resources": { + "InstanceSecurityGroup": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "vscode-server-isg", + "SecurityGroupIngress": [ + { + "Description": "Allow HTTP from com.amazonaws.global.cloudfront.origin-facing", + "IpProtocol": "tcp", + "FromPort": 8080, + "ToPort": 8080, + "SourcePrefixListId": { + "Fn::FindInMap": [ + "Prefixes", + { + "Ref": "AWS::Region" + }, + "PrefixList" + ] + } + } + ], + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "Tags": [ + { + "Key": "Name", + "Value": "vscode-server-isg" + } + ], + "VpcId": { + "Ref": "NetworkVPC" + } + } + }, + "InstanceRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ec2.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "Tags": [ + { + "Key": "Name", + "Value": "vscode-server-instance" + } + ] + } + }, + "InstanceRolePolicy": { + "Type": "AWS::IAM::RolePolicy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "ec2messages:*", + "ssm:UpdateInstanceInformation", + "ssmmessages:*", + "secretsmanager:GetSecretValue" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "InstanceRolePolicy", + "RoleName": { + "Ref": "InstanceRole" + } + } + }, + "InstanceProfile": { + "Type": "AWS::IAM::InstanceProfile", + "Properties": { + "Roles": [ + { + "Ref": "InstanceRole" + } + ] + } + }, + "Server": { + "CreationPolicy": { + "ResourceSignal": { + "Timeout": "PT5M" + } + }, + "Type": "AWS::EC2::Instance", + "DependsOn": [ + "InstanceRolePolicy", + "InstanceRole" + ], + "Properties": { + "AvailabilityZone": { + "Fn::Select": [ + 0, + { + "Fn::GetAZs": null + } + ] + }, + "BlockDeviceMappings": [ + { + "DeviceName": "/dev/xvda", + "Ebs": { + "VolumeSize": 128 + } + } + ], + "IamInstanceProfile": { + "Ref": "InstanceProfile" + }, + "ImageId": { + "Ref": "LatestAMI" + }, + "InstanceType": { + "Ref": "InstanceType" + }, + "SecurityGroupIds": [ + { + "Fn::GetAtt": [ + "InstanceSecurityGroup", + "GroupId" + ] + } + ], + "SubnetId": { + "Fn::GetAtt": [ + "NetworkPublicSubnet1", + "SubnetId" + ] + }, + "Tags": [ + { + "Key": "Name", + "Value": "vscode-server" + } + ], + "UserData": { + "Fn::Base64": { + "Fn::Sub": "#!/bin/bash\n\nset -eou pipefail\n\nlocal_ip=$(ec2-metadata | grep \"^local-ipv4: \" | cut -d \" \" -f 2)\n\n# Install the latest code-server from coder.com (not from yum)\nexport HOME=/root \ncurl -fsSL https://code-server.dev/install.sh | bash\n\n# Install cfn-signal\nyum install -y aws-cfn-bootstrap\n\n#Install argon2 for hashing the vscode server password\nyum install -y argon2\n\n# Configure the service\ntee /etc/systemd/system/code-server.service <", + "Default": "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-6.1-x86_64" + }, + "InstanceType": { + "Type": "String", + "Default": "t3.medium" + }, + "SecretName": { + "Description": "The name of the secrets manager secret that stores the password to be used for the VSCode Server. The password must be a simple plaintext string with no JSON.", + "Type": "String", + "Default": "vscode-password" + } + }, + "Mappings": { + "Prefixes": { + "ap-northeast-1": { + "PrefixList": "pl-58a04531" + }, + "ap-northeast-2": { + "PrefixList": "pl-22a6434b" + }, + "ap-south-1": { + "PrefixList": "pl-9aa247f3" + }, + "ap-southeast-1": { + "PrefixList": "pl-31a34658" + }, + "ap-southeast-2": { + "PrefixList": "pl-b8a742d1" + }, + "ca-central-1": { + "PrefixList": "pl-38a64351" + }, + "eu-central-1": { + "PrefixList": "pl-a3a144ca" + }, + "eu-north-1": { + "PrefixList": "pl-fab65393" + }, + "eu-west-1": { + "PrefixList": "pl-4fa04526" + }, + "eu-west-2": { + "PrefixList": "pl-93a247fa" + }, + "eu-west-3": { + "PrefixList": "pl-75b1541c" + }, + "sa-east-1": { + "PrefixList": "pl-5da64334" + }, + "us-east-1": { + "PrefixList": "pl-3b927c52" + }, + "us-east-2": { + "PrefixList": "pl-b6a144df" + }, + "us-west-1": { + "PrefixList": "pl-4ea04527" + }, + "us-west-2": { + "PrefixList": "pl-82a045eb" + } + } + }, + "Resources": { + "Network": { + "Type": { + "Rain::Module": "../../RainModules/vpc.yml" + }, + "Properties": { + "Name": "vscode-server" + } + }, + "InstanceSecurityGroup": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "vscode-server-isg", + "SecurityGroupIngress": [ + { + "Description": "Allow HTTP from com.amazonaws.global.cloudfront.origin-facing", + "IpProtocol": "tcp", + "FromPort": 8080, + "ToPort": 8080, + "SourcePrefixListId": { + "Fn::FindInMap": [ + "Prefixes", + { + "Ref": "AWS::Region" + }, + "PrefixList" + ] + } + } + ], + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "Tags": [ + { + "Key": "Name", + "Value": "vscode-server-isg" + } + ], + "VpcId": { + "Ref": "NetworkVPC" + } + } + }, + "InstanceRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ec2.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "Tags": [ + { + "Key": "Name", + "Value": "vscode-server-instance" + } + ] + } + }, + "InstanceRolePolicy": { + "Type": "AWS::IAM::RolePolicy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "ec2messages:*", + "ssm:UpdateInstanceInformation", + "ssmmessages:*", + "secretsmanager:GetSecretValue" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "InstanceRolePolicy", + "RoleName": { + "Ref": "InstanceRole" + } + } + }, + "InstanceProfile": { + "Type": "AWS::IAM::InstanceProfile", + "Properties": { + "Roles": [ + { + "Ref": "InstanceRole" + } + ] + } + }, + "Server": { + "CreationPolicy": { + "ResourceSignal": { + "Timeout": "PT5M" + } + }, + "Type": "AWS::EC2::Instance", + "DependsOn": [ + "InstanceRolePolicy", + "InstanceRole" + ], + "Properties": { + "AvailabilityZone": { + "Fn::Select": [ + 0, + { + "Fn::GetAZs": null + } + ] + }, + "BlockDeviceMappings": [ + { + "DeviceName": "/dev/xvda", + "Ebs": { + "VolumeSize": 128 + } + } + ], + "IamInstanceProfile": { + "Ref": "InstanceProfile" + }, + "ImageId": { + "Ref": "LatestAMI" + }, + "InstanceType": { + "Ref": "InstanceType" + }, + "SecurityGroupIds": [ + { + "Fn::GetAtt": [ + "InstanceSecurityGroup", + "GroupId" + ] + } + ], + "SubnetId": { + "Fn::GetAtt": [ + "NetworkPublicSubnet1", + "SubnetId" + ] + }, + "Tags": [ + { + "Key": "Name", + "Value": "vscode-server" + } + ], + "UserData": { + "Fn::Base64": { + "Fn::Sub": { + "Rain::Embed": "VSCodeServer.sh" + } + } + } + } + }, + "CloudFront": { + "Type": { + "Rain::Module": "../../RainModules/cloudfront-nocache.yml" + }, + "Properties": { + "Name": "vscode-server", + "DomainName": { + "Fn::GetAtt": [ + "Server", + "PublicDnsName" + ] + }, + "Port": 8080 + }, + "Overrides": { + "Distribution": { + "DependsOn": "Server" + } + } + } + }, + "Outputs": { + "URL": { + "Value": { + "Fn::Sub": "https://${CloudFrontDistribution.DomainName}/?folder=/home/ec2-user" + } + } + } +} diff --git a/Solutions/VSCode/VSCodeServer.yaml b/Solutions/VSCode/VSCodeServer.yaml index cb69b212..0ba7df51 100644 --- a/Solutions/VSCode/VSCodeServer.yaml +++ b/Solutions/VSCode/VSCodeServer.yaml @@ -1,5 +1,4 @@ Parameters: - LatestAMI: Type: AWS::SSM::Parameter::Value Default: /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-6.1-x86_64 @@ -14,7 +13,6 @@ Parameters: Description: The name of the secrets manager secret that stores the password to be used for the VSCode Server. The password must be a simple plaintext string with no JSON. Mappings: - Prefixes: ap-northeast-1: PrefixList: pl-58a04531 @@ -50,9 +48,8 @@ Mappings: PrefixList: pl-82a045eb Resources: - Network: - Type: !Rain::Module "../../RainModules/vpc.yml" + Type: !Rain::Module ../../RainModules/vpc.yml Properties: Name: vscode-server @@ -65,14 +62,17 @@ Resources: IpProtocol: tcp FromPort: 8080 ToPort: 8080 - SourcePrefixListId: !FindInMap [Prefixes, !Ref 'AWS::Region', PrefixList] + SourcePrefixListId: !FindInMap + - Prefixes + - !Ref AWS::Region + - PrefixList SecurityGroupEgress: - CidrIp: 0.0.0.0/0 Description: Allow all outbound traffic by default IpProtocol: "-1" Tags: - Key: Name - Value: vscode-server-isg + Value: vscode-server-isg VpcId: !Ref NetworkVPC InstanceRole: @@ -129,19 +129,18 @@ Resources: VolumeSize: 128 IamInstanceProfile: !Ref InstanceProfile ImageId: !Ref LatestAMI - InstanceType: !Ref InstanceType + InstanceType: !Ref InstanceType SecurityGroupIds: - !GetAtt InstanceSecurityGroup.GroupId SubnetId: !GetAtt NetworkPublicSubnet1.SubnetId Tags: - Key: Name - Value: vscode-server - UserData: - Fn::Base64: - Fn::Sub: !Rain::Embed VSCodeServer.sh + Value: vscode-server + UserData: !Base64 + Fn::Sub: !Rain::Embed VSCodeServer.sh CloudFront: - Type: !Rain::Module "../../RainModules/cloudfront-nocache.yml" + Type: !Rain::Module ../../RainModules/cloudfront-nocache.yml Properties: Name: vscode-server DomainName: !GetAtt Server.PublicDnsName @@ -151,7 +150,5 @@ Resources: DependsOn: Server Outputs: - URL: Value: !Sub https://${CloudFrontDistribution.DomainName}/?folder=/home/ec2-user - diff --git a/scripts/lint-single.sh b/scripts/lint-single.sh index 3f7ec889..ed95c056 100755 --- a/scripts/lint-single.sh +++ b/scripts/lint-single.sh @@ -18,6 +18,6 @@ then cfn-lint --config-file ${CONFIG_FILE} $1 else echo "$1 has a Rain directive, packaging first, which may break line numbers" - rain pkg $1 | cfn-lint --config-file ${CONFIG_FILE} + rain pkg -x $1 | cfn-lint --config-file ${CONFIG_FILE} fi