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/RainModules/cloudfront-nocache.yml b/RainModules/cloudfront-nocache.yml new file mode 100644 index 00000000..ad4d444d --- /dev/null +++ b/RainModules/cloudfront-nocache.yml @@ -0,0 +1,86 @@ +Parameters: + + Name: + Type: String + + DomainName: + Type: String + + Port: + Type: String + Default: 80 + +Resources: + + CachePolicy: + Type: AWS::CloudFront::CachePolicy + Properties: + CachePolicyConfig: + DefaultTTL: 86400 + MaxTTL: 31536000 + MinTTL: 1 + Name: !Ref Name + 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 + + Distribution: + Type: AWS::CloudFront::Distribution + Properties: + Tags: + - Key: Name + Value: !Ref Name + - Key: Description + Value: !Ref Name + 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: !Sub CloudFront-${AWS::StackName} + ViewerProtocolPolicy: allow-all + PathPattern: '/proxy/*' + DefaultCacheBehavior: + AllowedMethods: + - GET + - HEAD + - OPTIONS + - PUT + - PATCH + - POST + - DELETE + CachePolicyId: !Ref CachePolicy + OriginRequestPolicyId: 216adef6-5c7f-47e4-b989-5492eafa07d3 + TargetOriginId: !Sub CloudFront-${AWS::StackName} + ViewerProtocolPolicy: allow-all + Origins: + - DomainName: !Ref DomainName + Id: !Sub CloudFront-${AWS::StackName} + CustomOriginConfig: + HTTPPort: !Ref Port + OriginProtocolPolicy: http-only 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 new file mode 100644 index 00000000..4365d6ea --- /dev/null +++ b/Solutions/GitLab/GitLabServer-pkg.yaml @@ -0,0 +1,434 @@ +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: !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 + + #CreationPolicy: + #ResourceSignal: + #Timeout: PT30M + Properties: + AvailabilityZone: !Select + - 0 + - !GetAZs + BlockDeviceMappings: + - DeviceName: /dev/xvda + Ebs: + VolumeSize: 128 + IamInstanceProfile: !Ref InstanceProfile + ImageId: !Ref LatestAMI + InstanceType: !Ref InstanceType + SecurityGroupIds: + - !GetAtt InstanceSecurityGroup.GroupId + SubnetId: !GetAtt NetworkPublicSubnet1.SubnetId + Tags: + - Key: Name + Value: gitlab-server + UserData: !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 + Properties: + AvailabilityZone: !Select + - 0 + - !GetAZs + Ref: AWS::Region + CidrBlock: 10.0.0.0/18 + MapPublicIpOnLaunch: true + VpcId: !Ref NetworkVPC + Tags: + - Key: Name + Value: gitlab-server-public-subnet-1 + Metadata: + guard: + SuppressedRules: + - SUBNET_AUTO_ASSIGN_PUBLIC_IP_DISABLED + + 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 + Properties: + DestinationCidrBlock: 0.0.0.0/0 + GatewayId: !Ref NetworkInternetGateway + RouteTableId: !Ref NetworkPublicSubnet1RouteTable + Metadata: + guard: + SuppressedRules: + - NO_UNRESTRICTED_ROUTE_TO_IGW + DependsOn: + - NetworkVPCGW + + NetworkPublicSubnet1EIP: + Type: AWS::EC2::EIP + Properties: + Domain: vpc + Tags: + - Key: Name + Value: gitlab-server-public-subnet-1-eip + + NetworkPublicSubnet1NATGateway: + Type: AWS::EC2::NatGateway + Properties: + AllocationId: !GetAtt NetworkPublicSubnet1EIP.AllocationId + SubnetId: !Ref NetworkPublicSubnet1 + Tags: + - Key: Name + Value: gitlab-server-public-subnet-1-ngw + DependsOn: + - NetworkPublicSubnet1DefaultRoute + - NetworkPublicSubnet1RouteTableAssociation + + NetworkPublicSubnet2: + Type: AWS::EC2::Subnet + Properties: + AvailabilityZone: !Select + - 1 + - !GetAZs + Ref: AWS::Region + CidrBlock: 10.0.64.0/18 + MapPublicIpOnLaunch: true + VpcId: !Ref NetworkVPC + Tags: + - Key: Name + Value: gitlab-server-public-subnet-2 + Metadata: + guard: + SuppressedRules: + - SUBNET_AUTO_ASSIGN_PUBLIC_IP_DISABLED + + 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 + Properties: + DestinationCidrBlock: 0.0.0.0/0 + GatewayId: !Ref NetworkInternetGateway + RouteTableId: !Ref NetworkPublicSubnet2RouteTable + Metadata: + guard: + SuppressedRules: + - NO_UNRESTRICTED_ROUTE_TO_IGW + DependsOn: + - NetworkVPCGW + + NetworkPublicSubnet2EIP: + Type: AWS::EC2::EIP + Properties: + Domain: vpc + Tags: + - Key: Name + Value: gitlab-server-public-subnet-eip + + NetworkPublicSubnet2NATGateway: + Type: AWS::EC2::NatGateway + Properties: + AllocationId: !GetAtt NetworkPublicSubnet2EIP.AllocationId + SubnetId: !Ref NetworkPublicSubnet2 + Tags: + - Key: Name + Value: gitlab-server-public-subnet-ngw + DependsOn: + - NetworkPublicSubnet2DefaultRoute + - NetworkPublicSubnet2RouteTableAssociation + + NetworkPrivateSubnet1Subnet: + Type: AWS::EC2::Subnet + Properties: + AvailabilityZone: !Select + - 0 + - !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: !Select + - 1 + - !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 + 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: !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: !Sub CloudFront-${AWS::StackName} + ViewerProtocolPolicy: allow-all + Origins: + - DomainName: !GetAtt Server.PublicDnsName + Id: !Sub CloudFront-${AWS::StackName} + CustomOriginConfig: + HTTPPort: 80 + OriginProtocolPolicy: http-only + DependsOn: + - Server + +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.sh b/Solutions/GitLab/GitLabServer.sh new file mode 100644 index 00000000..fb385b15 --- /dev/null +++ b/Solutions/GitLab/GitLabServer.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +set -eou pipefail + +local_ip=$(ec2-metadata | grep "^local-ipv4: " | cut -d " " -f 2) + +# Install cfn-signal +yum install -y aws-cfn-bootstrap + +# Install postfix +yum install -y postfix +systemctl enable postfix +systemctl start postfix + +# Get the yum repo +curl https://packages.gitlab.com/install/repositories/gitlab/gitlab-ee/script.rpm.sh | sudo bash + +# Install gitlab and run it on the local ip +export EXTERNAL_URL="http://$local_ip" +yum install -y gitlab-ee + +# Tell CloudFormation we're ready to go +# This is a variable for the Sub intrisic function, not a bash variable +cfn-signal -s true --stack ${AWS::StackName} --resource Server --region ${AWS::Region} + + diff --git a/Solutions/GitLab/GitLabServer.yaml b/Solutions/GitLab/GitLabServer.yaml new file mode 100644 index 00000000..261e26b6 --- /dev/null +++ b/Solutions/GitLab/GitLabServer.yaml @@ -0,0 +1,149 @@ +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: !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 + CreationPolicy: + ResourceSignal: + Timeout: PT30M + Properties: + AvailabilityZone: !Select + - 0 + - !GetAZs + BlockDeviceMappings: + - DeviceName: /dev/xvda + Ebs: + VolumeSize: 128 + IamInstanceProfile: !Ref InstanceProfile + ImageId: !Ref LatestAMI + InstanceType: !Ref InstanceType + SecurityGroupIds: + - !GetAtt InstanceSecurityGroup.GroupId + SubnetId: !GetAtt NetworkPublicSubnet1.SubnetId + Tags: + - Key: Name + Value: gitlab-server + UserData: !Base64 + Fn::Sub: !Rain::Embed GitLabServer.sh + + CloudFront: + Type: !Rain::Module ../../RainModules/cloudfront-nocache.yml + Properties: + Name: gitlab-server + DomainName: !GetAtt Server.PublicDnsName + Port: 80 + Overrides: + Distribution: + DependsOn: Server + +Outputs: + URL: + Value: !Sub https://${CloudFrontDistribution.DomainName}/?folder=/home/ec2-user diff --git a/Solutions/GitLab/README.md b/Solutions/GitLab/README.md new file mode 100644 index 00000000..d1a1a99e --- /dev/null +++ b/Solutions/GitLab/README.md @@ -0,0 +1,7 @@ +# A CloudFormation template to install GitLab on an EC2 instance + +This template creates a VPC and an EC2 instance with a self-hosted instance of GitLab. + + + + 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 new file mode 100644 index 00000000..c1a86612 --- /dev/null +++ b/Solutions/GitLabAndVSCode/GitLabAndVSCode.yaml @@ -0,0 +1,194 @@ +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: + 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 + 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: !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: + Type: AWS::EC2::Instance + DependsOn: + - InstanceRolePolicy + - InstanceRole + CreationPolicy: + ResourceSignal: + Timeout: PT30M + Properties: + AvailabilityZone: !Select + - 0 + - !GetAZs + BlockDeviceMappings: + - DeviceName: /dev/xvda + Ebs: + VolumeSize: 128 + IamInstanceProfile: !Ref InstanceProfile + ImageId: !Ref LatestAMI + InstanceType: !Ref InstanceType + SecurityGroupIds: + - !GetAtt InstanceSecurityGroup.GroupId + SubnetId: !GetAtt NetworkPublicSubnet1.SubnetId + Tags: + - Key: Name + Value: gitlab-server + UserData: !Base64 + Fn::Sub: !Rain::Embed GitLabServer.sh + + VSCodeServer: + Type: AWS::EC2::Instance + DependsOn: + - InstanceRolePolicy + - InstanceRole + CreationPolicy: + ResourceSignal: + Timeout: PT5M + Properties: + AvailabilityZone: !Select + - 0 + - !GetAZs + BlockDeviceMappings: + - DeviceName: /dev/xvda + Ebs: + VolumeSize: 128 + IamInstanceProfile: !Ref InstanceProfile + ImageId: !Ref LatestAMI + InstanceType: !Ref InstanceType + SecurityGroupIds: + - !GetAtt InstanceSecurityGroup.GroupId + SubnetId: !GetAtt NetworkPublicSubnet1.SubnetId + Tags: + - Key: Name + Value: vscode-server + UserData: !Base64 + Fn::Sub: !Rain::Embed VSCodeServer.sh + + GitLabCloudFront: + Type: !Rain::Module ../../RainModules/cloudfront-nocache.yml + Properties: + Name: gitlab-server + DomainName: !GetAtt GitLabServer.PublicDnsName + Port: 80 + Overrides: + Distribution: + DependsOn: GitLabServer + + VSCodeCloudFront: + Type: !Rain::Module ../../RainModules/cloudfront-nocache.yml + Properties: + Name: vscode-server + DomainName: !GetAtt VSCodeServer.PublicDnsName + Port: 8080 + Overrides: + Distribution: DependsOn:VSCodeServer + +Outputs: + VSCodeURL: + Value: !Sub https://${VSCodeCloudFrontDistribution.DomainName}/?folder=/home/ec2-user + + GitLabURL: + Value: !Sub https://${GitLabCloudFrontDistribution.DomainName}/?folder=/home/ec2-user diff --git a/Solutions/GitLabAndVSCode/GitLabServer.sh b/Solutions/GitLabAndVSCode/GitLabServer.sh new file mode 100644 index 00000000..fb385b15 --- /dev/null +++ b/Solutions/GitLabAndVSCode/GitLabServer.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +set -eou pipefail + +local_ip=$(ec2-metadata | grep "^local-ipv4: " | cut -d " " -f 2) + +# Install cfn-signal +yum install -y aws-cfn-bootstrap + +# Install postfix +yum install -y postfix +systemctl enable postfix +systemctl start postfix + +# Get the yum repo +curl https://packages.gitlab.com/install/repositories/gitlab/gitlab-ee/script.rpm.sh | sudo bash + +# Install gitlab and run it on the local ip +export EXTERNAL_URL="http://$local_ip" +yum install -y gitlab-ee + +# Tell CloudFormation we're ready to go +# This is a variable for the Sub intrisic function, not a bash variable +cfn-signal -s true --stack ${AWS::StackName} --resource Server --region ${AWS::Region} + + diff --git a/Solutions/GitLabAndVSCode/VSCodeServer.sh b/Solutions/GitLabAndVSCode/VSCodeServer.sh new file mode 100644 index 00000000..1bc98246 --- /dev/null +++ b/Solutions/GitLabAndVSCode/VSCodeServer.sh @@ -0,0 +1,64 @@ +#!/bin/bash + +set -eou pipefail + +local_ip=$(ec2-metadata | grep "^local-ipv4: " | cut -d " " -f 2) + +# Install the latest code-server from coder.com (not from yum) +export HOME=/root +curl -fsSL https://code-server.dev/install.sh | bash + +# Install cfn-signal +yum install -y aws-cfn-bootstrap + +#Install argon2 for hashing the vscode server password +yum install -y argon2 + +# Configure the service +tee /etc/systemd/system/code-server.service <", + "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: + Type: String + Default: gitea-password + 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 + 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: !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: + Type: AWS::EC2::Instance + DependsOn: + - InstanceRolePolicy + - InstanceRole + CreationPolicy: + ResourceSignal: + Timeout: PT20M + Properties: + AvailabilityZone: !Select + - 0 + - !GetAZs + BlockDeviceMappings: + - DeviceName: /dev/xvda + Ebs: + VolumeSize: 128 + IamInstanceProfile: !Ref InstanceProfile + ImageId: !Ref LatestAMI + InstanceType: !Ref InstanceType + SecurityGroupIds: + - !GetAtt InstanceSecurityGroup.GroupId + SubnetId: !GetAtt NetworkPublicSubnet1.SubnetId + Tags: + - Key: Name + Value: gitea-server + UserData: !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.sh b/Solutions/Gitea/Gitea.sh new file mode 100644 index 00000000..60e5d912 --- /dev/null +++ b/Solutions/Gitea/Gitea.sh @@ -0,0 +1,82 @@ +#!/bin/bash + +set -eou pipefail + +local_ip=$(ec2-metadata | grep "^local-ipv4: " | cut -d " " -f 2) + +# Get the password from secrets manager +secret_string=$(aws secretsmanager get-secret-value --secret-id ${SecretName} | jq -r ".SecretString") + +# Install cfn-signal +yum install -y aws-cfn-bootstrap + +# Install go +yum install -y go + +# Install nodejs +yum install -y nodejs + +# Clone the repo and build Gitea +sudo -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: + Type: String + Default: gitea-password + 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 + 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: !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: + Type: AWS::EC2::Instance + DependsOn: + - InstanceRolePolicy + - InstanceRole + CreationPolicy: + ResourceSignal: + Timeout: PT20M + Properties: + AvailabilityZone: !Select + - 0 + - !GetAZs + BlockDeviceMappings: + - DeviceName: /dev/xvda + Ebs: + VolumeSize: 128 + IamInstanceProfile: !Ref InstanceProfile + ImageId: !Ref LatestAMI + InstanceType: !Ref InstanceType + SecurityGroupIds: + - !GetAtt InstanceSecurityGroup.GroupId + SubnetId: !GetAtt NetworkPublicSubnet1.SubnetId + Tags: + - Key: Name + Value: gitea-server + UserData: !Base64 + Fn::Sub: !Rain::Embed Gitea.sh + + CloudFront: + Type: !Rain::Module ../../RainModules/cloudfront-nocache.yml + Properties: + Name: gitea-server + DomainName: !GetAtt Server.PublicDnsName + Port: 8080 + Overrides: + Distribution: + DependsOn: Server + +Outputs: + URL: + Value: !Sub https://${CloudFrontDistribution.DomainName} diff --git a/Solutions/Gitea/README.md b/Solutions/Gitea/README.md new file mode 100644 index 00000000..b2518154 --- /dev/null +++ b/Solutions/Gitea/README.md @@ -0,0 +1,30 @@ +# Gitea Server + +Create an EC2 instance with Gitea installed, and a CloudFront distribution for +encrypted access to the web ui from the browser. The output from the template +provides the CloudFront URL. + +As a prerequisite, you need to create a plaintext secret in Secrets Manager to +store your password for a Gitea user called 'admin1' that will be created by the +user data script. The default name for the secret is 'gitea-password'. + +## Files + +### `Gitea.yaml` + +This is the raw template, which includes [Rain](https://github.com/aws-cloudformation/rain) +directives to import a VPC module and embed the user data script. + +### `Gitea-pkg.yaml` + +The is the rendered template that you can deploy with `aws cloudformation +deploy` or `rain deploy`. To regenerate this template if you make any changes +to `Gitea.yaml`, run `rain pkg -x Gitea.yaml > Gitea-pkg.yaml`. + +### `Gitea.sh` + +This is the user data script that is embedded in the packaged template. It is +meant to be used with Amazon Linux instances. + + + 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.sh b/Solutions/VSCode/VSCodeServer.sh index cad2f223..1bc98246 100644 --- a/Solutions/VSCode/VSCodeServer.sh +++ b/Solutions/VSCode/VSCodeServer.sh @@ -8,7 +8,13 @@ local_ip=$(ec2-metadata | grep "^local-ipv4: " | cut -d " " -f 2) export HOME=/root curl -fsSL https://code-server.dev/install.sh | bash +# Install cfn-signal +yum install -y aws-cfn-bootstrap + +#Install argon2 for hashing the vscode server password yum install -y argon2 + +# Configure the service tee /etc/systemd/system/code-server.service < 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: @@ -116,6 +116,9 @@ Resources: DependsOn: - InstanceRolePolicy - InstanceRole + CreationPolicy: + ResourceSignal: + Timeout: PT5M Properties: AvailabilityZone: !Select - 0 @@ -126,93 +129,26 @@ 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 - - CachePolicy: - Type: AWS::CloudFront::CachePolicy - Properties: - CachePolicyConfig: - DefaultTTL: 86400 - MaxTTL: 31536000 - MinTTL: 1 - Name: vscode-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 - - Distribution: - Type: AWS::CloudFront::Distribution - Properties: Tags: - Key: Name Value: vscode-server - - Key: Description - Value: Distribution for EC2 VSCode 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: !Sub CloudFront-${AWS::StackName} - ViewerProtocolPolicy: allow-all - PathPattern: '/proxy/*' - DefaultCacheBehavior: - AllowedMethods: - - GET - - HEAD - - OPTIONS - - PUT - - PATCH - - POST - - DELETE - CachePolicyId: !Ref CachePolicy - OriginRequestPolicyId: 216adef6-5c7f-47e4-b989-5492eafa07d3 - TargetOriginId: !Sub CloudFront-${AWS::StackName} - ViewerProtocolPolicy: allow-all - Origins: - - DomainName: !GetAtt Server.PublicDnsName - Id: !Sub CloudFront-${AWS::StackName} - CustomOriginConfig: - HTTPPort: 8080 - OriginProtocolPolicy: http-only + UserData: !Base64 + Fn::Sub: !Rain::Embed VSCodeServer.sh + CloudFront: + Type: !Rain::Module ../../RainModules/cloudfront-nocache.yml + Properties: + Name: vscode-server + DomainName: !GetAtt Server.PublicDnsName + Port: 8080 + Overrides: + Distribution: + DependsOn: Server Outputs: - URL: - Value: !Sub https://${Distribution.DomainName}/?folder=/home/ec2-user - + 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