AWSTemplateFormatVersion: 2010-09-09 Description: R on Ubuntu ( https://github.com/aws-samples/sample-r-server ) (uksb-2286sdebbf) (tag:Ubuntu) Transform: "AWS::LanguageExtensions" Metadata: License: Description: | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier: MIT-0 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. AWS::CloudFormation::Interface: ParameterGroups: - Label: default: R Applications Parameters: - installRStudioServer - installRStudioDesktop - installShinyServer - Label: default: EC2 Parameters: - ec2Name - ec2KeyPair - osVersion - instanceType - ec2TerminationProtection - Label: default: Network Parameters: - vpcID - subnetID - displayPublicIP - assignStaticIP - Label: default: Remote access Parameters: - ingressIPv4 - ingressIPv6 - allowSSHport - Label: default: EBS volume Parameters: - volumeSize - volumeType - Label: default: Amazon CloudFront Parameters: - enableCloudFront - originType - cloudFrontLogging - Label: default: AWS Backup Parameters: - enableBackup - scheduleExpression - scheduleExpressionTimezone - deleteAfterDays - Label: default: Posit download URLs (x86_64) Parameters: - RStudioServerURL - RStudioDesktopURL - PositronURL - ShinyServerURL - Label: default: Others Parameters: - enableR53acmeSupport ParameterLabels: installRStudioServer: default: "Install RStudio Server" installRStudioDesktop: default: "Install RStudio Desktop and Positron IDEs" installShinyServer: default: "Install Shiny Server" enableR53acmeSupport: default: "Enable Route 53 ACME protocol DNS-01 challenge support" ec2Name: default: "EC2 instance name" ec2KeyPair: default: "Key pair" osVersion: default: "OS version" instanceType: default: "Instance type (x86_64)" ec2TerminationProtection: default: "Enable EC2 termination protection to prevent accidental deletion" volumeSize: default: "Volume size (GiB)" volumeType: default: "Volume type" vpcID: default: "VPC with outbound internet connectivity" subnetID: default: "Subnet in selected VPC with outbound internet IPv4 connectivity" displayPublicIP: default: "EC2 instance in public subnet with public IP assigned?" assignStaticIP: default: "Elastic IP: assign static public internet IPv4 address" ingressIPv4: default: "Allowed source prefix (IPv4)" ingressIPv6: default: "Allowed source prefix (IPv6)" allowSSHport: default: "Allow SSH from network" enableBackup: default: "Backup EC2 instance" scheduleExpression: default: "CRON expression specifying when AWS Backup initiates a backup job" scheduleExpressionTimezone: default: "Timezone to set backup schedule" deleteAfterDays: default: "Number of days after creation that a recovery point (backup) is deleted" enableCloudFront: default: "Create Amazon CloudFront distribution" originType: default: "CloudFront origin type" cloudFrontLogging: default: "Enable CloudFront logging" RStudioServerURL: default: "RStudio Server (Ubuntu 24.04): https://posit.co/download/rstudio-server/" RStudioDesktopURL: default: "RStudio Desktop (Ubuntu 24.04): https://posit.co/download/rstudio-desktop/" PositronURL: default: "Positron: https://positron.posit.co/download.html" ShinyServerURL: default: "Shiny Server: https://posit.co/download/shiny-server/" Parameters: installRStudioServer: Type: String Description: https://posit.co/products/open-source/rstudio-server/ AllowedValues: - "Yes" - "No" Default: "Yes" installRStudioDesktop: Type: String Description: https://posit.co/products/open-source/rstudio/ https://posit.co/products/ide/positron/ AllowedValues: - "Yes" - "Yes-with-HTTPS-reverse-proxy" - "No" Default: "No" installShinyServer: Type: String Description: https://posit.co/products/open-source/shiny-server/ AllowedValues: - "Yes" - "No" Default: "No" enableR53acmeSupport: Type: String Description: https://certbot.eff.org/ https://certbot-dns-route53.readthedocs.io/ AllowedValues: - "Yes" - "No" Default: "Yes" osVersion: Type: String Description: Ubuntu ( https://ubuntu.com/aws ) AllowedValues: - Ubuntu 24.04 (x86_64) - Ubuntu Pro 24.04 (x86_64) Default: Ubuntu 24.04 (x86_64) ec2Name: Type: String # Description: EC2 instance name Default: R Server ec2KeyPair: Type: AWS::EC2::KeyPair::KeyName Description: https://console.aws.amazon.com/ec2/#KeyPairs AllowedPattern: .+ ConstraintDescription: Select a key pair instanceType: Type: String Description: https://console.aws.amazon.com/ec2/#InstanceTypes AllowedPattern: "^[a-z\\-\\d\\.]+$" ConstraintDescription: Specify valid EC2 instance type Default: m7i.large ec2TerminationProtection: Type: String Description: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/Using_ChangingDisableAPITermination.html Default: "Yes" AllowedValues: - "Yes" - "No" vpcID: Type: AWS::EC2::VPC::Id Description: "https://console.aws.amazon.com/vpcconsole/home#vpcs:" AllowedPattern: .+ ConstraintDescription: Select a VPC subnetID: Type: AWS::EC2::Subnet::Id Description: "https://console.aws.amazon.com/vpcconsole/home#subnets:" AllowedPattern: .+ ConstraintDescription: Select a Subnet displayPublicIP: Type: String Description: Select No if instance has no public IPv4 address AllowedValues: - "Yes" - "No" Default: "Yes" assignStaticIP: Type: String Description: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/elastic-ip-addresses-eip.html AllowedValues: - "Yes" - "No" Default: "Yes" ingressIPv4: Type: String Description: e.g. 1.2.3.4/32, get your internet IPv4 address from https://checkip.amazonaws.com AllowedPattern: "^\\d+\\.\\d+\\.\\d+\\.\\d+\\/\\d+$" ConstraintDescription: Specify valid IPv4 prefix Default: 0.0.0.0/0 ingressIPv6: Type: String Description: e.g. 1:2:3:4::/64, get your internet IPv6 address (if any) with tools such as https://ifconfig.co AllowedPattern: .+ ConstraintDescription: Specify valid IPv6 prefix Default: ::/0 allowSSHport: Type: String Description: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/connect-linux-inst-ssh.html AllowedValues: - "Yes" - "No" Default: "Yes" volumeSize: Type: Number Description: https://docs.aws.amazon.com/ebs/latest/userguide/volume_constraints.html MinValue: 20 MaxValue: 16384 Default: 50 volumeType: Type: String Description: https://aws.amazon.com/ebs/general-purpose/ AllowedValues: - gp3 - gp2 Default: gp3 enableBackup: Type: String Description: https://docs.aws.amazon.com/aws-backup/ AllowedValues: - "Yes" - "No" Default: "No" scheduleExpression: Type: String Description: https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-scheduled-rule-pattern.html AllowedPattern: .+ Default: "cron(0 1 ? * * *)" scheduleExpressionTimezone: # https://nodatime.org/TimeZones?version=2024a&format=json Type: String Description: https://docs.aws.amazon.com/scheduler/latest/UserGuide/schedule-types.html#time-zones AllowedValues: - Africa/Abidjan - Africa/Algiers - Africa/Bissau - Africa/Cairo - Africa/Casablanca - Africa/Ceuta - Africa/El_Aaiun - Africa/Johannesburg - Africa/Juba - Africa/Khartoum - Africa/Lagos - Africa/Maputo - Africa/Monrovia - Africa/Nairobi - Africa/Ndjamena - Africa/Sao_Tome - Africa/Tripoli - Africa/Tunis - Africa/Windhoek - America/Adak - America/Anchorage - America/Araguaina - America/Argentina/Buenos_Aires - America/Argentina/Catamarca - America/Argentina/Cordoba - America/Argentina/Jujuy - America/Argentina/La_Rioja - America/Argentina/Mendoza - America/Argentina/Rio_Gallegos - America/Argentina/Salta - America/Argentina/San_Juan - America/Argentina/San_Luis - America/Argentina/Tucuman - America/Argentina/Ushuaia - America/Asuncion - America/Bahia - America/Bahia_Banderas - America/Barbados - America/Belem - America/Belize - America/Boa_Vista - America/Bogota - America/Boise - America/Cambridge_Bay - America/Campo_Grande - America/Cancun - America/Caracas - America/Cayenne - America/Chicago - America/Chihuahua - America/Ciudad_Juarez - America/Costa_Rica - America/Cuiaba - America/Danmarkshavn - America/Dawson - America/Dawson_Creek - America/Denver - America/Detroit - America/Edmonton - America/Eirunepe - America/El_Salvador - America/Fort_Nelson - America/Fortaleza - America/Glace_Bay - America/Goose_Bay - America/Grand_Turk - America/Guatemala - America/Guayaquil - America/Guyana - America/Halifax - America/Havana - America/Hermosillo - America/Indiana/Indianapolis - America/Indiana/Knox - America/Indiana/Marengo - America/Indiana/Petersburg - America/Indiana/Tell_City - America/Indiana/Vevay - America/Indiana/Vincennes - America/Indiana/Winamac - America/Inuvik - America/Iqaluit - America/Jamaica - America/Juneau - America/Kentucky/Louisville - America/Kentucky/Monticello - America/La_Paz - America/Lima - America/Los_Angeles - America/Maceio - America/Managua - America/Manaus - America/Martinique - America/Matamoros - America/Mazatlan - America/Menominee - America/Merida - America/Metlakatla - America/Mexico_City - America/Miquelon - America/Moncton - America/Monterrey - America/Montevideo - America/New_York - America/Nome - America/Noronha - America/North_Dakota/Beulah - America/North_Dakota/Center - America/North_Dakota/New_Salem - America/Nuuk - America/Ojinaga - America/Panama - America/Paramaribo - America/Phoenix - America/Port-au-Prince - America/Porto_Velho - America/Puerto_Rico - America/Punta_Arenas - America/Rankin_Inlet - America/Recife - America/Regina - America/Resolute - America/Rio_Branco - America/Santarem - America/Santiago - America/Santo_Domingo - America/Sao_Paulo - America/Scoresbysund - America/Sitka - America/St_Johns - America/Swift_Current - America/Tegucigalpa - America/Thule - America/Tijuana - America/Toronto - America/Vancouver - America/Whitehorse - America/Winnipeg - America/Yakutat - Antarctica/Casey - Antarctica/Davis - Antarctica/Macquarie - Antarctica/Mawson - Antarctica/Palmer - Antarctica/Rothera - Antarctica/Troll - Antarctica/Vostok - Asia/Almaty - Asia/Amman - Asia/Anadyr - Asia/Aqtau - Asia/Aqtobe - Asia/Ashgabat - Asia/Atyrau - Asia/Baghdad - Asia/Baku - Asia/Bangkok - Asia/Barnaul - Asia/Beirut - Asia/Bishkek - Asia/Chita - Asia/Choibalsan - Asia/Colombo - Asia/Damascus - Asia/Dhaka - Asia/Dili - Asia/Dubai - Asia/Dushanbe - Asia/Famagusta - Asia/Gaza - Asia/Hebron - Asia/Ho_Chi_Minh - Asia/Hong_Kong - Asia/Hovd - Asia/Irkutsk - Asia/Jakarta - Asia/Jayapura - Asia/Jerusalem - Asia/Kabul - Asia/Kamchatka - Asia/Karachi - Asia/Kathmandu - Asia/Khandyga - Asia/Kolkata - Asia/Krasnoyarsk - Asia/Kuching - Asia/Macau - Asia/Magadan - Asia/Makassar - Asia/Manila - Asia/Nicosia - Asia/Novokuznetsk - Asia/Novosibirsk - Asia/Omsk - Asia/Oral - Asia/Pontianak - Asia/Pyongyang - Asia/Qatar - Asia/Qostanay - Asia/Qyzylorda - Asia/Riyadh - Asia/Sakhalin - Asia/Samarkand - Asia/Seoul - Asia/Shanghai - Asia/Singapore - Asia/Srednekolymsk - Asia/Taipei - Asia/Tashkent - Asia/Tbilisi - Asia/Tehran - Asia/Thimphu - Asia/Tokyo - Asia/Tomsk - Asia/Ulaanbaatar - Asia/Urumqi - Asia/Ust-Nera - Asia/Vladivostok - Asia/Yakutsk - Asia/Yangon - Asia/Yekaterinburg - Asia/Yerevan - Atlantic/Azores - Atlantic/Bermuda - Atlantic/Canary - Atlantic/Cape_Verde - Atlantic/Faroe - Atlantic/Madeira - Atlantic/South_Georgia - Atlantic/Stanley - Australia/Adelaide - Australia/Brisbane - Australia/Broken_Hill - Australia/Darwin - Australia/Eucla - Australia/Hobart - Australia/Lindeman - Australia/Lord_Howe - Australia/Melbourne - Australia/Perth - Australia/Sydney - CET - CST6CDT - EET - EST - EST5EDT - Etc/GMT - Etc/GMT+1 - Etc/GMT+10 - Etc/GMT+11 - Etc/GMT+12 - Etc/GMT+2 - Etc/GMT+3 - Etc/GMT+4 - Etc/GMT+5 - Etc/GMT+6 - Etc/GMT+7 - Etc/GMT+8 - Etc/GMT+9 - Etc/GMT-1 - Etc/GMT-10 - Etc/GMT-11 - Etc/GMT-12 - Etc/GMT-13 - Etc/GMT-14 - Etc/GMT-2 - Etc/GMT-3 - Etc/GMT-4 - Etc/GMT-5 - Etc/GMT-6 - Etc/GMT-7 - Etc/GMT-8 - Etc/GMT-9 - Etc/UTC - Europe/Andorra - Europe/Astrakhan - Europe/Athens - Europe/Belgrade - Europe/Berlin - Europe/Brussels - Europe/Bucharest - Europe/Budapest - Europe/Chisinau - Europe/Dublin - Europe/Gibraltar - Europe/Helsinki - Europe/Istanbul - Europe/Kaliningrad - Europe/Kirov - Europe/Kyiv - Europe/Lisbon - Europe/London - Europe/Madrid - Europe/Malta - Europe/Minsk - Europe/Moscow - Europe/Paris - Europe/Prague - Europe/Riga - Europe/Rome - Europe/Samara - Europe/Saratov - Europe/Simferopol - Europe/Sofia - Europe/Tallinn - Europe/Tirane - Europe/Ulyanovsk - Europe/Vienna - Europe/Vilnius - Europe/Volgograd - Europe/Warsaw - Europe/Zurich - HST - Indian/Chagos - Indian/Maldives - Indian/Mauritius - MET - MST - MST7MDT - PST8PDT - Pacific/Apia - Pacific/Auckland - Pacific/Bougainville - Pacific/Chatham - Pacific/Easter - Pacific/Efate - Pacific/Fakaofo - Pacific/Fiji - Pacific/Galapagos - Pacific/Gambier - Pacific/Guadalcanal - Pacific/Guam - Pacific/Honolulu - Pacific/Kanton - Pacific/Kiritimati - Pacific/Kosrae - Pacific/Kwajalein - Pacific/Marquesas - Pacific/Nauru - Pacific/Niue - Pacific/Norfolk - Pacific/Noumea - Pacific/Pago_Pago - Pacific/Palau - Pacific/Pitcairn - Pacific/Port_Moresby - Pacific/Rarotonga - Pacific/Tahiti - Pacific/Tarawa - Pacific/Tongatapu - WET Default: Etc/UTC deleteAfterDays: Type: Number Default: 35 enableCloudFront: Type: String Description: https://docs.aws.amazon.com/cloudfront/ AllowedValues: - "Yes" - "No" Default: "Yes" originType: Type: String Description: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-vpc-origins.html AllowedValues: - "Custom Origin" - "VPC Origin" Default: "Custom Origin" cloudFrontLogging: Type: String Description: Logging to new S3 bucket ( https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/AccessLogs.html ) AllowedValues: - "Yes" - "No" Default: "No" RStudioServerURL: Type: String AllowedPattern: .+ Default: https://download2.rstudio.org/server/jammy/amd64/rstudio-server-2026.01.1-403-amd64.deb RStudioDesktopURL: Type: String AllowedPattern: .+ Default: https://download1.rstudio.org/electron/jammy/amd64/rstudio-2026.01.1-403-amd64.deb PositronURL: Type: String AllowedPattern: .+ Default: https://cdn.posit.co/positron/releases/deb/x86_64/Positron-2026.03.0-212-x64.deb ShinyServerURL: Type: String AllowedPattern: .+ Default: https://download3.rstudio.org/ubuntu-20.04/x86_64/shiny-server-1.5.23.1030-amd64.deb Conditions: useUbuntu2404x86: !Equals [!Ref osVersion, "Ubuntu 24.04 (x86_64)"] displayPublicIP: !Equals [!Ref displayPublicIP, "Yes"] useElasticIP: !And [!Condition displayPublicIP, !Equals [!Ref assignStaticIP, "Yes"]] enableProtection: !Equals [!Ref ec2TerminationProtection, "Yes"] hasEIC: !Not [ !Equals [ !FindInMap [ EICprefixMap, !Ref AWS::Region, IpPrefix, DefaultValue: 127.0.0.1/32, ], 127.0.0.1/32, ], ] createSgEIC: !And [!Condition hasEIC, !Condition displayPublicIP] hasCFprefix: !Not [ !Equals [ !FindInMap [ CFprefixMap, !Ref AWS::Region, PrefixList, DefaultValue: pl-none, ], pl-none, ], ] createSgSSH: !Equals [!Ref allowSSHport, "Yes"] hasR53Zone: !Equals [!Ref enableR53acmeSupport, "Yes"] createBackup: !Equals [!Ref enableBackup, "Yes"] createSgCf: !And [!Condition createCloudFront, !Condition hasCFprefix] createSgCfRStudio: !And [!Condition installRStudioServer, !Condition createSgCf] createSgCfShiny: !And [!Condition installShinyServer, !Condition createSgCf] enableCloudFront: !Equals [!Ref enableCloudFront, "Yes"] createCloudFront: !Or [!Condition createCfRStudio, !Condition createCfShiny] cloudFrontLogging: !And [!Condition createCloudFront, !Equals [!Ref cloudFrontLogging, "Yes"]] installRStudioServer: !Equals [!Ref installRStudioServer, "Yes"] installRStudioDesktop: !Not [!Equals [!Ref installRStudioDesktop, "No"]] installShinyServer: !Equals [!Ref installShinyServer, "Yes"] installReverseProxy: !Equals [!Ref installRStudioDesktop, "Yes-with-HTTPS-reverse-proxy"] createCfRStudio: !And [!Condition enableCloudFront, !Condition installRStudioServer] cfVPCOriginRStudio: !And [!Condition createCfRStudio, !Equals [!Ref originType, "VPC Origin"]] createCfShiny: !And [!Condition enableCloudFront, !Condition installShinyServer] cfVPCOriginShiny: !And [!Condition createCfShiny, !Equals [!Ref originType, "VPC Origin"]] Mappings: # EC2 instance connect: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-connect-prerequisites.html#ec2-instance-connect-setup-security-group # curl -s https://ip-ranges.amazonaws.com/ip-ranges.json | jq -r '.prefixes[] | select (.service=="EC2_INSTANCE_CONNECT")' # curl -s https://ip-ranges.amazonaws.com/ip-ranges.json | jq -r '.ipv6_prefixes[] | select (.service=="EC2_INSTANCE_CONNECT")' EICprefixMap: af-south-1: IpPrefix: 13.244.121.196/30 Ipv6Prefix: 2406:da11:700:3b00::/56 ap-east-1: IpPrefix: 43.198.192.104/29 Ipv6Prefix: 2406:da1e:da1:3c00::/56 ap-northeast-1: IpPrefix: 3.112.23.0/29 Ipv6Prefix: 2406:da14:1c18:2100::/56 ap-northeast-2: IpPrefix: 13.209.1.56/29 Ipv6Prefix: 2406:da12:1e1:d900::/56 ap-northeast-3: IpPrefix: 15.168.105.160/29 Ipv6Prefix: 2406:da16:856:a500::/56 ap-south-1: IpPrefix: 13.233.177.0/29 Ipv6Prefix: 2406:da1a:74a:4b00::/56 ap-south-2: IpPrefix: 18.60.252.248/29 Ipv6Prefix: 2406:da1b:d1d:8800::/56 ap-southeast-1: IpPrefix: 3.0.5.32/29 Ipv6Prefix: 2406:da18:752:6600::/56 ap-southeast-2: IpPrefix: 13.239.158.0/29 Ipv6Prefix: 2406:da1c:90e:4a00::/56 ap-southeast-3: IpPrefix: 43.218.193.64/29 Ipv6Prefix: 2406:da19:14b:8c00::/56 ap-southeast-4: IpPrefix: 16.50.248.80/29 Ipv6Prefix: 2406:da1f:b4f:4600::/56 ap-southeast-5: IpPrefix: 43.216.87.48/29 Ipv6Prefix: 2406:da10:84f9:9e00::/56 ap-southeast-7: IpPrefix: 43.209.155.96/29 Ipv6Prefix: 2406:da14:85c5:5b00::/56 ca-central-1: IpPrefix: 35.183.92.176/29 Ipv6Prefix: 2600:1f11:ae3:700::/56 ca-west-1: IpPrefix: 40.176.213.168/29 Ipv6Prefix: 2600:1f1a:4ff6:d500::/56 cn-north-1: IpPrefix: 43.196.20.40/29 Ipv6Prefix: 2400:7fc0:86fd:e00::/56 cn-northwest-1: IpPrefix: 43.192.155.8/29 Ipv6Prefix: 2404:c2c0:87aa:4800::/56 eu-central-1: IpPrefix: 3.120.181.40/29 Ipv6Prefix: 2a05:d014:17a8:8b00::/56 eu-central-2: IpPrefix: 16.63.77.8/29 Ipv6Prefix: 2a05:d019:1d6:2100::/56 eu-north-1: IpPrefix: 13.48.4.200/30 Ipv6Prefix: 2a05:d016:494:f00::/56 eu-south-1: IpPrefix: 15.161.135.164/30 Ipv6Prefix: 2a05:d01a:c03:4a00::/56 eu-south-2: IpPrefix: 18.101.90.48/29 Ipv6Prefix: 2a05:d011:cbe:f700::/56 eu-west-1: IpPrefix: 18.202.216.48/29 Ipv6Prefix: 2a05:d018:403:4e00::/56 eu-west-2: IpPrefix: 3.8.37.24/29 Ipv6Prefix: 2a05:d01c:4ac:3100::/56 eu-west-3: IpPrefix: 35.180.112.80/29 Ipv6Prefix: 2a05:d012:c9e:d600::/56 il-central-1: IpPrefix: 51.16.183.224/29 Ipv6Prefix: 2a05:d025:451:7d00::/56 me-central-1: IpPrefix: 3.29.147.40/29 Ipv6Prefix: 2406:da17:1db:b00::/56 me-south-1: IpPrefix: 16.24.46.56/29 Ipv6Prefix: 2a05:d01e:27f:ac00::/56 mx-central-1: IpPrefix: 78.12.207.8/29 Ipv6Prefix: 22600:1f17:4ee0:b800::/56 sa-east-1: IpPrefix: 18.228.70.32/29 Ipv6Prefix: 2600:1f1e:d1d:e700::/56 us-east-1: IpPrefix: 18.206.107.24/29 Ipv6Prefix: 2600:1f18:6fe3:8c00::/56 us-east-2: IpPrefix: 3.16.146.0/29 Ipv6Prefix: 2600:1f16:138f:cf00::/56 us-gov-east-1: IpPrefix: 18.252.4.0/30 Ipv6Prefix: 2600:1f15:d63:bd00::/56 us-gov-west-1: IpPrefix: 15.200.28.80/30 Ipv6Prefix: 2600:1f12:fa9:5100::/56 us-west-1: IpPrefix: 13.52.6.112/29 Ipv6Prefix: 2600:1f1c:12d:e900::/56 us-west-2: IpPrefix: 18.237.140.160/29 Ipv6Prefix: 2600:1f13:a0d:a700::/56 CFprefixMap: # aws ec2 describe-managed-prefix-lists --query "PrefixLists[?PrefixListName=='com.amazonaws.global.cloudfront.origin-facing']" --region af-south-1: PrefixList: pl-c0aa4fa9 Ipv6PrefixList: pl-08e545c506fc11b3d ap-east-1: PrefixList: pl-14b2577d Ipv6PrefixList: pl-09eb2ebd84c23b987 ap-east-2: PrefixList: pl-0b51e244975ca1f58 Ipv6PrefixList: pl-0675c4405f2d19014 ap-northeast-1: PrefixList: pl-58a04531 Ipv6PrefixList: pl-0f28cd4a128e7b13a ap-northeast-2: PrefixList: pl-22a6434b Ipv6PrefixList: pl-07ac407da2b364d6c ap-northeast-3: PrefixList: pl-31a14458 Ipv6PrefixList: pl-04e68c40b871c8e6b ap-south-1: PrefixList: pl-9aa247f3 Ipv6PrefixList: pl-029b73ad1ccf6fe97 ap-south-2: PrefixList: pl-0a25c3463226fcc61 Ipv6PrefixList: pl-045b5138c20f83bab ap-southeast-1: PrefixList: pl-31a34658 Ipv6PrefixList: pl-02d26f62e3b1ed532 ap-southeast-2: PrefixList: pl-b8a742d1 Ipv6PrefixList: pl-033521892361c13a7 ap-southeast-3: PrefixList: pl-bca247d5 Ipv6PrefixList: pl-0b8932aa3ef329011 ap-southeast-4: PrefixList: pl-0fb7e7cfe038ae0e9 Ipv6PrefixList: pl-03292f9327ecaf81c ap-southeast-5: PrefixList: pl-09076f83e90b139d0 Ipv6PrefixList: pl-015db6a9a3f8b7f38 ap-southeast-6: PrefixList: pl-04ed52d45e258dfd3 Ipv6PrefixList: pl-04ec346b0299ab3e3 ap-southeast-7: PrefixList: pl-0857de2e2b1c7f2a2 Ipv6PrefixList: pl-0d394c8e84df4ca56 ca-central-1: PrefixList: pl-38a64351 Ipv6PrefixList: pl-0c0fd74227163049a ca-west-1: PrefixList: pl-0530d4c590b35122b Ipv6PrefixList: pl-0cd01c4f03b66d585 eu-central-1: PrefixList: pl-a3a144ca Ipv6PrefixList: pl-0624f1d638a3e93df eu-central-2: PrefixList: pl-00b37293991dbe6a8 Ipv6PrefixList: pl-05ff33f3c280b2eb8 eu-north-1: PrefixList: pl-fab65393 Ipv6PrefixList: pl-05de9757262c679ba eu-south-1: PrefixList: pl-1bbc5972 Ipv6PrefixList: pl-07b149cb3ccb7e2da eu-south-2: PrefixList: pl-052dcbe0f793f19da Ipv6PrefixList: pl-0454b1d06a3e15f2f eu-west-1: PrefixList: pl-4fa04526 Ipv6PrefixList: pl-010bae2278f1a872d eu-west-2: PrefixList: pl-93a247fa Ipv6PrefixList: pl-0d7c235a121ad5fd1 eu-west-3: PrefixList: pl-75b1541c Ipv6PrefixList: pl-06d246df64d68cb0a il-central-1: PrefixList: pl-0dd89524416301988 Ipv6PrefixList: pl-023722c7ce8718ca2 me-central-1: PrefixList: pl-05266a86378662c23 Ipv6PrefixList: pl-08b3f3c9dc8f45b09 me-south-1: PrefixList: pl-17b2577e Ipv6PrefixList: pl-04baf1fb5ff7e9290 mx-central-1: PrefixList: pl-0246509e78ddf0729 Ipv6PrefixList: pl-0df0f56c679a42f28 sa-east-1: PrefixList: pl-5da64334 Ipv6PrefixList: pl-0051b342e8bc54805 us-east-1: PrefixList: pl-3b927c52 Ipv6PrefixList: pl-02d12e369a4312e03 us-east-2: PrefixList: pl-b6a144df Ipv6PrefixList: pl-079a97b94f32e4ee7 us-west-1: PrefixList: pl-4ea04527 Ipv6PrefixList: pl-06dd7c6e345937257 us-west-2: PrefixList: pl-82a045eb Ipv6PrefixList: pl-07f8c64944f5dc195 Resources: instanceIamRole: Type: AWS::IAM::Role Metadata: cfn_nag: rules_to_suppress: - id: W11 reason: "* resource access is required for proper functionality" Properties: AssumeRolePolicyDocument: Statement: - Effect: Allow Principal: Service: [ec2.amazonaws.com] Action: [sts:AssumeRole] Path: / Policies: - !If - installRStudioDesktop - PolicyName: dcvLicensing PolicyDocument: # https://docs.aws.amazon.com/dcv/latest/adminguide/setting-up-license.html Version: "2012-10-17" Statement: - Effect: Allow Action: - s3:GetObject Resource: !Sub arn:*:s3:::dcv-license.${AWS::Region}/* - !Ref AWS::NoValue - !If - hasR53Zone - PolicyName: R53acmeAccess PolicyDocument: # Certbot dns_route53 : https://certbot-dns-route53.readthedocs.io/en/stable/ Version: "2012-10-17" Statement: # https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/specifying-rrset-conditions.html - Effect: Allow Action: - route53:ListHostedZones - route53:GetChange Resource: "*" - Effect: Allow Action: - route53:ChangeResourceRecordSets Resource: !Sub arn:${AWS::Partition}:route53:::hostedzone/* Condition: IpAddress: aws:SourceIp: 0.0.0.0/0 ForAllValues:StringEquals: route53:ChangeResourceRecordSetsRecordTypes: [TXT] ForAllValues:StringLike: route53:ChangeResourceRecordSetsNormalizedRecordNames: [_acme-challenge.*] - !Ref AWS::NoValue ManagedPolicyArns: - !Sub arn:${AWS::Partition}:iam::aws:policy/AmazonSSMManagedInstanceCore - !Sub arn:${AWS::Partition}:iam::aws:policy/CloudWatchAgentServerPolicy Tags: - Key: StackName Value: !Ref AWS::StackName - Key: StackId Value: !Ref AWS::StackId - Key: GitHub Value: https://github.com/aws-samples/sample-r-server instanceProfile: Type: AWS::IAM::InstanceProfile Properties: Path: / Roles: - !Ref instanceIamRole sgEC2Instance: Type: AWS::EC2::SecurityGroup Metadata: cfn_nag: rules_to_suppress: - id: W5 reason: "Unlimited egress required for proper functioning" - id: W9 reason: "Not /32 CIDR ingress is required for proper functionality" - id: W40 reason: "OS need unrestricted access to the internet for updates and patches" #checkov:skip=CKV_AWS_23:All rules have description Properties: GroupDescription: Allow inbound RStudio/Shiny/EIC/SSH/DCV/HTTPS VpcId: !Ref vpcID SecurityGroupIngress: - !If - createSgSSH - Description: SSH (IPv4) IpProtocol: tcp FromPort: 22 ToPort: 22 CidrIp: !Ref ingressIPv4 - !Ref AWS::NoValue - !If - createSgSSH - Description: SSH (IPv6) IpProtocol: tcp FromPort: 22 ToPort: 22 CidrIpv6: !Ref ingressIPv6 - !Ref AWS::NoValue - !If - installRStudioDesktop - Description: DCV (IPv4) IpProtocol: tcp FromPort: 8443 ToPort: 8443 CidrIp: !Ref ingressIPv4 - !Ref AWS::NoValue - !If - installRStudioDesktop - Description: DCV (IPv6) IpProtocol: tcp FromPort: 8443 ToPort: 8443 CidrIpv6: !Ref ingressIPv6 - !Ref AWS::NoValue - !If - installRStudioDesktop - Description: DCV QUIC (IPv4) IpProtocol: udp FromPort: 8443 ToPort: 8443 CidrIp: !Ref ingressIPv4 - !Ref AWS::NoValue - !If - installRStudioDesktop - Description: DCV QUIC (IPv6) IpProtocol: udp FromPort: 8443 ToPort: 8443 CidrIpv6: !Ref ingressIPv6 - !Ref AWS::NoValue - !If - installReverseProxy - Description: HTTPS DCV reverse proxy (IPv4) IpProtocol: tcp FromPort: 443 ToPort: 443 CidrIp: !Ref ingressIPv4 - !Ref AWS::NoValue - !If - installReverseProxy - Description: HTTPS DCV reverse proxy (IPv6) IpProtocol: tcp FromPort: 443 ToPort: 443 CidrIpv6: !Ref ingressIPv6 - !Ref AWS::NoValue - !If - installRStudioDesktop - Description: ACME HTTP-01 challenge (IPv4) IpProtocol: tcp FromPort: 80 ToPort: 80 CidrIp: 0.0.0.0/0 - !Ref AWS::NoValue - !If - installRStudioDesktop - Description: ACME HTTP-01 challenge (IPv6) IpProtocol: tcp FromPort: 80 ToPort: 80 CidrIpv6: ::/0 - !Ref AWS::NoValue - !If - createSgEIC - Description: SSH (EC2 Instance Connect IPv4) IpProtocol: tcp FromPort: 22 ToPort: 22 CidrIp: !FindInMap [EICprefixMap, !Ref AWS::Region, IpPrefix] - !Ref AWS::NoValue - !If - createSgEIC - Description: SSH (EC2 Instance Connect IPv6) IpProtocol: tcp FromPort: 22 ToPort: 22 CidrIpv6: !FindInMap [EICprefixMap, !Ref AWS::Region, Ipv6Prefix] - !Ref AWS::NoValue SecurityGroupEgress: - Description: Allow all outbound traffic (IPv4) IpProtocol: "-1" CidrIp: 0.0.0.0/0 - Description: Allow all outbound traffic (IPv6) IpProtocol: "-1" CidrIpv6: ::/0 Tags: - Key: StackName Value: !Ref AWS::StackName - Key: StackId Value: !Ref AWS::StackId - Key: Name Value: !Sub - "${AWS::StackName}-securityGroup-${UID}" - UID: !Select [ 3, !Split ["-", !Select [2, !Split ["/", !Ref AWS::StackId]]], ] - Key: GitHub Value: https://github.com/aws-samples/sample-r-server sgRStudioServerIPv4: Type: AWS::EC2::SecurityGroup Condition: createSgCfRStudio Properties: GroupDescription: Allow inbound RStudio Server from CloudFront IPv4 VpcId: !Ref vpcID SecurityGroupIngress: - Description: RStudio Server (CloudFront origin IPv4) IpProtocol: tcp FromPort: 8787 ToPort: 8787 SourcePrefixListId: !FindInMap [CFprefixMap, !Ref AWS::Region, PrefixList] SecurityGroupEgress: - Description: Ping (CloudFront origin IPv4) IpProtocol: icmp FromPort: -1 ToPort: -1 DestinationPrefixListId: !FindInMap [CFprefixMap, !Ref AWS::Region, PrefixList] Tags: - Key: StackName Value: !Ref AWS::StackName - Key: StackId Value: !Ref AWS::StackId - Key: Name Value: !Sub - "${AWS::StackName}-RStudio-IPv4-${UID}" - UID: !Select [ 3, !Split ["-", !Select [2, !Split ["/", !Ref AWS::StackId]]], ] - Key: GitHub Value: https://github.com/aws-samples/sample-r-server sgRStudioServerIPv6: Type: AWS::EC2::SecurityGroup Condition: createSgCfRStudio Properties: GroupDescription: Allow inbound RStudio Server from CloudFront IPv6 VpcId: !Ref vpcID SecurityGroupIngress: - Description: RStudio Server (CloudFront origin IPv6) IpProtocol: tcp FromPort: 8787 ToPort: 8787 SourcePrefixListId: !FindInMap [CFprefixMap, !Ref AWS::Region, Ipv6PrefixList] SecurityGroupEgress: - Description: Ping (CloudFront origin IPv6) IpProtocol: icmp FromPort: -1 ToPort: -1 DestinationPrefixListId: !FindInMap [CFprefixMap, !Ref AWS::Region, Ipv6PrefixList] Tags: - Key: StackName Value: !Ref AWS::StackName - Key: StackId Value: !Ref AWS::StackId - Key: Name Value: !Sub - "${AWS::StackName}-RStudio-IPv6-${UID}" - UID: !Select [ 3, !Split ["-", !Select [2, !Split ["/", !Ref AWS::StackId]]], ] - Key: GitHub Value: https://github.com/aws-samples/sample-r-server sgShinyServerIPv4: Type: AWS::EC2::SecurityGroup Condition: createSgCfShiny Properties: GroupDescription: Allow inbound Shiny Server from CloudFront IPv4 VpcId: !Ref vpcID SecurityGroupIngress: - Description: Shiny Server (CloudFront origin IPv4) IpProtocol: tcp FromPort: 3838 ToPort: 3838 SourcePrefixListId: !FindInMap [CFprefixMap, !Ref AWS::Region, PrefixList] SecurityGroupEgress: - Description: Ping (CloudFront origin IPv4) IpProtocol: icmp FromPort: -1 ToPort: -1 DestinationPrefixListId: !FindInMap [CFprefixMap, !Ref AWS::Region, PrefixList] Tags: - Key: StackName Value: !Ref AWS::StackName - Key: StackId Value: !Ref AWS::StackId - Key: Name Value: !Sub - "${AWS::StackName}-Shiny-IPv4-${UID}" - UID: !Select [ 3, !Split ["-", !Select [2, !Split ["/", !Ref AWS::StackId]]], ] - Key: GitHub Value: https://github.com/aws-samples/sample-r-server sgShinyServerIPv6: Type: AWS::EC2::SecurityGroup Condition: createSgCfShiny Properties: GroupDescription: Allow inbound Shiny Server from CloudFront IPv6 VpcId: !Ref vpcID SecurityGroupIngress: - Description: Shiny Server (CloudFront origin IPv6) IpProtocol: tcp FromPort: 3838 ToPort: 3838 SourcePrefixListId: !FindInMap [CFprefixMap, !Ref AWS::Region, Ipv6PrefixList] SecurityGroupEgress: - Description: Ping (CloudFront origin IPv6) IpProtocol: icmp FromPort: -1 ToPort: -1 DestinationPrefixListId: !FindInMap [CFprefixMap, !Ref AWS::Region, Ipv6PrefixList] Tags: - Key: StackName Value: !Ref AWS::StackName - Key: StackId Value: !Ref AWS::StackId - Key: Name Value: !Sub - "${AWS::StackName}-Shiny-IPv6-${UID}" - UID: !Select [ 3, !Split ["-", !Select [2, !Split ["/", !Ref AWS::StackId]]], ] - Key: GitHub Value: https://github.com/aws-samples/sample-r-server ec2Instance: Type: AWS::EC2::Instance CreationPolicy: ResourceSignal: Timeout: PT90M Metadata: Comment: Install Update files AWS::CloudFormation::Init: configSets: setup: - 00_setup dcv_install: - 00_dcv_install app_install: - 05_app_install 00_setup: # in the following order: packages, groups, users, sources, files, commands, and then services. files: "/home/ubuntu/update-dcv": content: | #!/bin/bash cd /tmp OS_VERSION=$(. /etc/os-release;echo $VERSION_ID | sed -e 's/\.//g') sudo rm -f /tmp/nice-dcv-ubuntu$OS_VERSION-$(arch).tgz wget https://d1uj6qtbmh3dt5.cloudfront.net/nice-dcv-ubuntu$OS_VERSION-$(arch).tgz tar -xvzf nice-dcv-ubuntu$OS_VERSION-$(arch).tgz && cd nice-dcv-*-ubuntu$OS_VERSION-$(arch) sudo apt-get install -y ./nice-dcv-server_*.deb sudo apt-get install -y ./nice-dcv-web-viewer_*.deb sudo apt-get install -y ./nice-xdcv_*.deb sudo systemctl daemon-reload mode: "000755" owner: "ubuntu" group: "ubuntu" "/etc/systemd/system/dcv-virtual-session.service": content: | [Unit] Description=Create DCV virtual session After=default.target network.target [Service] ExecStart=/opt/dcv-virtual-session.sh [Install] WantedBy=default.target mode: "000644" owner: "root" group: "root" "/opt/dcv-virtual-session.sh": content: | #!/bin/bash dcvUsers=( "ubuntu" ) while true; do for dcvUser in "${dcvUsers[@]}" do if (! /usr/bin/dcv list-sessions | grep -q $dcvUser); then /usr/bin/dcv create-session $dcvUser --owner $dcvUser --storage-root %home% --type virtual fi done date /usr/bin/dcv list-sessions sleep 5 done mode: "000744" owner: "root" group: "root" "/etc/systemd/system/dcv-post-reboot.service": content: | [Unit] Description=Post install tasks After=default.target network.target [Service] ExecStart=/bin/sh -c "/opt/dcv-post-reboot.sh 2>&1 | tee -a /var/log/install-sw.log" [Install] WantedBy=default.target mode: "000644" owner: "root" group: "root" "/opt/dcv-post-reboot.sh": content: !Sub | #!/bin/bash export DEBIAN_FRONTEND=noninteractive apt-get update -q apt-get upgrade -q -y # Add DCV ports if (systemctl status ufw | grep -q Loaded); then ufw allow 8443/tcp ufw allow 8443/udp ufw allow 8787/tcp ufw allow 3838/tcp ufw allow https ufw allow http fi export installRStudioDesktop="${installRStudioDesktop}" case $installRStudioDesktop in No) rm -f /etc/systemd/system/dcv-virtual-session.service rm -f /opt/dcv-virtual-session.sh rm -f /home/ubuntu/update-dcv ;; *) systemctl enable --now dcv-virtual-session systemctl enable --now dcvserver ;; esac # GPU driver for G/P instance : https://repost.aws/articles/ARWGxLArMBQ4y1MKoSHTq3gQ/ apt-get install -q -y pciutils apt-get install -q -y amazon-ec2-utils if ( lspci | grep -q NVIDIA ); then cd /tmp DISTRO=$(. /etc/os-release;echo $ID$VERSION_ID | sed -e 's/\.//g') if (arch | grep -q x86); then ARCH=x86_64 else ARCH=sbsa fi curl -L -O https://developer.download.nvidia.com/compute/cuda/repos/$DISTRO/$ARCH/cuda-keyring_1.1-1_all.deb apt-get install -q -y ./cuda-keyring_1.1-1_all.deb apt-get update if ( ec2-metadata -t | grep -q 6f\. ); then # NVIDIA GRID: https://repost.aws/articles/ARBXrCH5wuStKWCXfIDdG4Ig/ apt-get install -q -y dkms linux-headers-aws linux-modules-extra-aws unzip gcc make libglvnd-dev pkg-config if (lsb_release -r -s | grep -q 22); then if (cat /proc/version | grep -q gcc-12); then update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-12 12 update-alternatives --config gcc fi fi aws s3 cp --recursive s3://ec2-linux-nvidia-drivers/latest/ . --no-sign-request chmod +x NVIDIA-Linux-x86_64*.run KERNEL_VERSION=$(ls -a /lib/modules | sort -V -r | head -n 1) if (! uname -r | grep -q $KERNEL_VERSION ); then ./NVIDIA-Linux-x86_64*.run -s --kernel-source-path=/lib/modules/$KERNEL_VERSION/build --kernel-name=$KERNEL_VERSION --skip-module-load dkms autoinstall else ./NVIDIA-Linux-x86_64*.run -s --skip-module-load fi modprobe nvidia nvidia-smi echo "options nvidia NVreg_EnableGpuFirmware=0" | tee --append /etc/modprobe.d/nvidia.conf else VERSION=$(apt-cache search "nvidia-driver" | grep "^nvidia-driver-.*-server-open" | cut -d"-" -f3 | sort -r | head -1) apt-get install -q -y linux-modules-nvidia-$VERSION-server-open-aws nvidia-headless-no-dkms-$VERSION-server-open nvidia-driver-$VERSION-server-open nvidia-utils-$VERSION-server apt-get install -q -y nvidia-settings if ( ec2-metadata -t | grep -q " p[0-9]" ); then apt-get install -q -y nvidia-fabricmanager-$VERSION libnvidia-nscq-$VERSION apt-get install -q -y nvidia-imex-$VERSION if ( ec2-metadata -t | grep -q " p[6-9]" ); then apt-get install -q -y nvlsm apt-get install -q -y infiniband-diags echo "ib_umad" | tee -a /etc/modules-load.d/modules.conf modprobe ib_umad fi systemctl enable --now nvidia-fabricmanager fi fi fi # NVIDIA Container Toolkit: https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html if ( which nvidia-smi ); then if ( which docker ); then if (! apt-cache search nvidia-container-toolkit | grep -q nvidia-container-toolkit); then cd /tmp curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg \ && curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list | \ sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \ sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list fi apt-get update apt-get install -q -y nvidia-container-toolkit nvidia-ctk runtime configure --runtime=docker systemctl restart docker fi fi # IP address cert if ( echo "${installRStudioDesktop}" | grep -q -i "Yes" ); then if [ "${displayPublicIP}" = "Yes" ]; then # Some Regions create Elastic IP after EC2 instance is created python3 /usr/local/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource ec2Instance --region ${AWS::Region} sleep 45 IP_ADDR=$(curl -s checkip.amazonaws.com) certbot certonly -n --agree-tos --standalone --preferred-profile shortlived --ip-address $IP_ADDR # DCV TLS cert: https://docs.aws.amazon.com/dcv/latest/adminguide/manage-cert.html if [ -d "/etc/letsencrypt/live/$IP_ADDR/" ]; then cp -f /etc/letsencrypt/live/$IP_ADDR/fullchain.pem /etc/dcv/dcv.pem cp -f /etc/letsencrypt/live/$IP_ADDR/privkey.pem /etc/dcv/dcv.key chown dcv /etc/dcv/dcv.pem /etc/dcv/dcv.key chmod 600 /etc/dcv/dcv.pem /etc/dcv/dcv.key # post cert renewal hook mv /opt/dcv-renew-cert.sh /etc/letsencrypt/renewal-hooks/post/ # HTTPS reverse proxy if ( echo "${installRStudioDesktop}" | grep -q -i "proxy" ); then rm -f /etc/ssl/certs/ssl-cert-snakeoil.pem rm -f /etc/ssl/private/ssl-cert-snakeoil.key ln -s -f /etc/letsencrypt/live/$IP_ADDR/fullchain.pem /etc/ssl/certs/ssl-cert-snakeoil.pem ln -s -f /etc/letsencrypt/live/$IP_ADDR/privkey.pem /etc/ssl/private/ssl-cert-snakeoil.key systemctl reload nginx fi fi fi fi if [ -f "/opt/dcv-renew-cert.sh" ]; then rm /opt/dcv-renew-cert.sh fi python3 /usr/local/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource ec2Instance --region ${AWS::Region} rm -f /etc/systemd/system/dcv-post-reboot.service rm -f ${!0} systemctl daemon-reload mode: "000755" owner: "root" group: "root" "/opt/dcv-renew-cert.sh": content: | #!/bin/bash CERT_FOLDER=$(curl -s checkip.amazonaws.com) if [ -d "/etc/letsencrypt/live/$CERT_FOLDER/" ]; then cp -f /etc/letsencrypt/live/$CERT_FOLDER/fullchain.pem /etc/dcv/dcv.pem cp -f /etc/letsencrypt/live/$CERT_FOLDER/privkey.pem /etc/dcv/dcv.key chown dcv /etc/dcv/dcv.pem /etc/dcv/dcv.key chmod 600 /etc/dcv/dcv.pem /etc/dcv/dcv.key if (systemctl status nginx | grep -q running); then systemctl reload nginx fi fi mode: "000755" owner: "root" group: "root" "/opt/aws/amazon-cloudwatch-agent/bin/config.json": content: | { "agent": { "metrics_collection_interval": 60, "run_as_user": "cwagent" }, "metrics": { "namespace": "CWAgent", "append_dimensions": { "InstanceId": "${aws:InstanceId}" }, "metrics_collected": { "mem": { "measurement": [ "used_percent" ] }, "disk": { "measurement": [ "used_percent" ], "resources": [ "/" ] } } } } mode: "000644" owner: "root" group: "root" "/etc/systemd/system/systemd-networkd-wait-online.service.d/override.conf": content: | [Service] ExecStart= ExecStart=/usr/lib/systemd/systemd-networkd-wait-online --timeout=3 mode: "000644" owner: "root" group: "root" "/root/install-sw.sh": content: !Sub | #!/bin/bash mkdir -p /tmp/cfn cd /tmp/cfn export DEBIAN_FRONTEND=noninteractive # Change password PWD=$(cat /var/lib/cloud/data/instance-id) USER=ubuntu echo "$USER:$PWD" | chpasswd # Update OS apt-get update -q apt-get upgrade -q -y apt-get autoremove -q -y # Unattended apt-get install -q -y unattended-upgrades # CloudWatch agent: https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/install-CloudWatch-Agent-commandline-fleet.html#download-CloudWatch-Agent-on-EC2-Instance-commandline-fleet if (arch | grep -q x86); then curl -s -L -O https://amazoncloudwatch-agent.s3.amazonaws.com/ubuntu/amd64/latest/amazon-cloudwatch-agent.deb else curl -s -L -O https://amazoncloudwatch-agent.s3.amazonaws.com/ubuntu/arm64/latest/amazon-cloudwatch-agent.deb fi apt-get install -q -y ./amazon-cloudwatch-agent.deb /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl -a fetch-config -m ec2 -c file:/opt/aws/amazon-cloudwatch-agent/bin/config.json -s systemctl enable --now amazon-cloudwatch-agent # USB and GPU driver DKMS apt-get update -q apt-get install -q -y dkms # Kernel headers for GPU and USB remotization apt-get install -q -y linux-image-aws apt-get install -q -y linux-headers-aws apt-get install -q -y linux-modules-extra-aws apt-get install -q -y usbutils # Docker: https://docs.docker.com/engine/install/ubuntu/ # Add Docker's official GPG key apt-get install -q -y ca-certificates curl install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc chmod a+r /etc/apt/keyrings/docker.asc # Add the repository to Apt sources echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \ tee /etc/apt/sources.list.d/docker.list > /dev/null apt-get update -q apt-get install -q -y docker-ce systemctl enable docker usermod -aG docker ubuntu # AWS CLI: https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html apt-get remove -q -y awscli sudo snap install aws-cli --classic if [ -e /snap/bin/aws ]; then rm -f /home/ubuntu/update-awscli else /home/ubuntu/update-awscli fi echo "export AWS_CLI_AUTO_PROMPT=on-partial" >> /home/ubuntu/.bashrc # Certbot: https://eff-certbot.readthedocs.io/en/stable/install.html#snap-recommended sudo snap install certbot --classic if [ -e /snap/bin/certbot ]; then ln -s /snap/bin/certbot /usr/bin/certbot sudo snap set certbot trust-plugin-with-root=ok sudo snap install certbot-dns-route53 else apt-get install -q -y python3-certbot python3-certbot-apache python3-certbot-nginx python3-certbot-dns-route53 fi # Ubuntu 24.04: reduce systemd-networkd-wait-online.service 2+ min timeout if (lsb_release -r -s | grep -q 24); then sed -i "s/systemd-networkd-wait-online$/& --timeout=3/g" /usr/lib/systemd/system/systemd-networkd-wait-online.service else rm -f /etc/systemd/system/systemd-networkd-wait-online.service.d/override.conf fi systemctl daemon-reload # Disable Nouveau: https://docs.nvidia.com/ai-enterprise/deployment-guide-vmware/0.1.0/nouveau.html#ubuntu if (! cat /etc/modprobe.d/blacklist.conf | grep -q nouveau); then echo "blacklist vga16fb" >> /etc/modprobe.d/blacklist.conf echo "blacklist nouveau" >> /etc/modprobe.d/blacklist.conf echo "blacklist rivafb" >> /etc/modprobe.d/blacklist.conf echo "blacklist nvidiafb" >> /etc/modprobe.d/blacklist.conf echo "blacklist rivatv" >> /etc/modprobe.d/blacklist.conf fi sed -i 's/^GRUB_CMDLINE_LINUX_DEFAULT="/GRUB_CMDLINE_LINUX_DEFAULT="rdblacklist=nouveau /g' /etc/default/grub update-grub # MountPoint-S3: https://github.com/awslabs/mountpoint-s3 curl -s -L -O https://s3.amazonaws.com/mountpoint-s3-release/latest/x86_64/mount-s3.deb apt-get install -q -y ./mount-s3.deb rm -f ${!0} mode: "000740" owner: "root" group: "root" commands: install: command: "/root/install-sw.sh >> /var/log/install-sw.log 2>&1" ignoreErrors: "true" 00_dcv_install: files: "/opt/ReverseProxy": content: !Sub | # server { # HTTPS listen 443 ssl http2 default_server; listen [::]:443 ssl http2 default_server; server_name _; ssl_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem; ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key; ssl_protocols TLSv1.3; ssl_prefer_server_ciphers on; ssl_session_cache shared:SSL:1m; ssl_session_timeout 10m; location / { proxy_pass https://localhost:8443; proxy_redirect https://localhost:8443 $scheme://$http_host/; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_buffering off; client_max_body_size 20M; proxy_read_timeout 10m; proxy_send_timeout 10m; } } mode: "000644" owner: "root" group: "root" "/root/install-dcv.sh": content: !Sub | #!/bin/bash mkdir -p /tmp/cfn cd /tmp/cfn export DEBIAN_FRONTEND=noninteractive # Update OS apt-get update -q apt-get upgrade -q -y # DCV prereq: https://docs.aws.amazon.com/dcv/latest/adminguide/setting-up-installing-linux-prereq.html apt-get install -q -y ubuntu-desktop-minimal apt-get install -q -y gdm3 apt-get install -q -y amazon-ec2-utils # Disable the Wayland protocol: https://docs.aws.amazon.com/dcv/latest/adminguide/setting-up-installing-linux-prereq.html#linux-prereq-wayland sed -i '/^\[daemon\]/a WaylandEnable=false' /etc/gdm3/custom.conf # resolve "/var/lib/dpkg/info/nice-dcv-server.postinst: 8: dpkg-architecture: not found" when installing dcv-server apt-get install -q -y dpkg-dev # Microphone redirection: https://docs.aws.amazon.com/dcv/latest/adminguide/setting-up-installing-linux-server.html apt-get install -q -y pulseaudio-utils apt-get install -q -y gnome-tweaks gnome-shell-extension-ubuntu-dock apt-get install -q -y gnome-shell-extension-manager # DCV: https://docs.aws.amazon.com/dcv/latest/adminguide/setting-up-installing-linux-server.html curl -s -L -O https://d1uj6qtbmh3dt5.cloudfront.net/NICE-GPG-KEY gpg --import NICE-GPG-KEY OS_VERSION=$(. /etc/os-release;echo $VERSION_ID | sed -e 's/\.//g') curl -s -L -O https://d1uj6qtbmh3dt5.cloudfront.net/nice-dcv-ubuntu$OS_VERSION-$(arch).tgz tar -xvzf nice-dcv-ubuntu*.tgz && cd nice-dcv-*-$(arch) apt-get install -q -y ./nice-dcv-server_*.deb apt-get install -q -y ./nice-dcv-web-viewer_*.deb usermod -aG video dcv apt-get install -q -y ./nice-xdcv_*.deb # Printer redirection: https://docs.aws.amazon.com/dcv/latest/adminguide/manage-printer.html apt-get install -q -y cups GROUP=$(cat /etc/cups/cups-files.conf | grep -oP "SystemGroup\s\K\w+") usermod -a -G $GROUP dcv systemctl enable cups # QUIC: https://docs.aws.amazon.com/dcv/latest/adminguide/enable-quic.html cp /etc/dcv/dcv.conf /etc/dcv/dcv.conf."`date +"%Y-%m-%d"`" sed -i "s/^#enable-quic-frontend=true/enable-quic-frontend=true/g" /etc/dcv/dcv.conf # Higher web client max resolution: https://docs.aws.amazon.com/dcv/latest/adminguide/config-param-ref.html sed -i "/^\[display/a web-client-max-head-resolution=(4096, 2160)" /etc/dcv/dcv.conf # Console session support sed -i "/^\[session-management\/automatic-console-session/a owner=\"ubuntu\"\nstorage-root=\"%home%\"" /etc/dcv/dcv.conf # Disable reporting : https://wiki.ubuntu.com/Apport sed -i "s/enabled=1/enable=0/g" /etc/default/apport apt-get remove -q -y ubuntu-report whoopsie apport apt-get autoremove -q -y # Disable upgrade prompt: https://help.ubuntu.com/community/Upgrades#Upgrade_policy sed -i "s/Prompt=lts/Prompt=never/g" /etc/update-manager/release-upgrades chown -R ubuntu:ubuntu /home/ubuntu/.config # Ubuntu 24.04: reduce systemd-networkd-wait-online.service 2+ min timeout if (lsb_release -r -s | grep -q 24); then sed -i "s/systemd-networkd-wait-online$/& --timeout=3/g" /usr/lib/systemd/system/systemd-networkd-wait-online.service fi # Google Chrome: https://www.google.com/linuxrepositories/ curl -fSsL https://dl.google.com/linux/linux_signing_key.pub | gpg --dearmor | sudo tee /usr/share/keyrings/google-chrome.gpg > /dev/null echo deb [arch=amd64 signed-by=/usr/share/keyrings/google-chrome.gpg] http://dl.google.com/linux/chrome/deb/ stable main | sudo tee /etc/apt/sources.list.d/google-chrome.list apt-get update -q apt-get install -q -y google-chrome-stable # Microsoft Edge: https://learn.microsoft.com/en-us/linux/packages curl -fSsL https://packages.microsoft.com/keys/microsoft.asc | sudo gpg --dearmor | sudo tee /usr/share/keyrings/microsoft-edge.gpg > /dev/null echo 'deb [arch=amd64 signed-by=/usr/share/keyrings/microsoft-edge.gpg] https://packages.microsoft.com/repos/edge stable main' | sudo tee /etc/apt/sources.list.d/microsoft-edge.list apt-get update -q apt-get install -q -y microsoft-edge-stable # systemctl daemon-reload systemctl enable dcv-virtual-session systemctl enable dcvserver # Nginx reverse proxy to DCV if ( echo "${installRStudioDesktop}" | grep -q -i "proxy" ); then # Nginx HTTPS proxy openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -keyout /etc/ssl/private/ssl-cert-snakeoil.key -out /etc/ssl/certs/ssl-cert-snakeoil.pem -nodes -subj "/C=XX/ST=State/L=City/O=Company/OU=Dept/CN=Host" apt-get install -q -y nginx cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf."`date +"%Y-%m-%d"`" # disable gzip as per https://bugs.debian.org/773332 sed -i "s/gzip on/gzip off/g" /etc/nginx/nginx.conf # disable default site rm -f /etc/nginx/sites-enabled/default # HTTPS proxy mv /opt/ReverseProxy /etc/nginx/sites-available/ReverseProxy ln -s /etc/nginx/sites-available/ReverseProxy /etc/nginx/sites-enabled/ReverseProxy systemctl enable --now nginx else rm -f /opt/ReverseProxy fi rm -f ${!0} mode: "000740" owner: "root" group: "root" "/home/ubuntu/.gnomerc": content: | export XDG_CURRENT_DESKTOP=ubuntu:GNOME export GNOME_SHELL_SESSION_MODE=ubuntu export XDG_DATA_DIRS=/usr/share/gnome:/usr/local/share:/usr/share:/var/lib/snapd/desktop mode: "000644" owner: "ubuntu" group: "ubuntu" "/etc/skel/.gnomerc": content: | export XDG_CURRENT_DESKTOP=ubuntu:GNOME export GNOME_SHELL_SESSION_MODE=ubuntu export XDG_DATA_DIRS=/usr/share/gnome:/usr/local/share:/usr/share:/var/lib/snapd/desktop mode: "000644" owner: "root" group: "root" "/home/ubuntu/.config/gnome-initial-setup-done": content: | yes mode: "000664" owner: "ubuntu" group: "ubuntu" "/etc/skel/.config/gnome-initial-setup-done": content: | yes mode: "000644" owner: "root" group: "root" commands: install: command: "/root/install-dcv.sh > /var/log/install-dcv.log 2>&1" ignoreErrors: "true" 05_app_install: files: "/root/install-r.sh": content: !Sub | #!/bin/bash mkdir -p /tmp/cfn cd /tmp/cfn export DEBIAN_FRONTEND=noninteractive # R: https://cran.r-project.org/bin/linux/ubuntu/ apt-get install -q -y --no-install-recommends software-properties-common dirmngr # add the signing key (by Michael Rutter) for these repos # To verify key, run gpg --show-keys /etc/apt/trusted.gpg.d/cran_ubuntu_key.asc # Fingerprint: E298A3A825C0D65DFD57CBB651716619E084DAB9 wget -qO- https://cloud.r-project.org/bin/linux/ubuntu/marutter_pubkey.asc | tee -a /etc/apt/trusted.gpg.d/cran_ubuntu_key.asc # add the repo from CRAN -- lsb_release adjusts to 'noble' or 'jammy' or ... as needed add-apt-repository -y "deb https://cloud.r-project.org/bin/linux/ubuntu $(lsb_release -cs)-cran40/" # install R itself apt-get -q -y install --no-install-recommends r-base # https://cran.r-project.org/bin/linux/ubuntu/fullREADME.html # r2u: https://github.com/eddelbuettel/r2u apt-get install -q -y --no-install-recommends wget ca-certificates gnupg wget -q -O- https://eddelbuettel.github.io/r2u/assets/dirk_eddelbuettel_key.asc | tee -a /etc/apt/trusted.gpg.d/cranapt_key.asc echo "deb [arch=amd64] https://r2u.stat.illinois.edu/ubuntu noble main" > /etc/apt/sources.list.d/cranapt.list echo "Package: *" > /etc/apt/preferences.d/99cranapt echo "Pin: release o=CRAN-Apt Project" >> /etc/apt/preferences.d/99cranapt echo "Pin: release l=CRAN-Apt Packages" >> /etc/apt/preferences.d/99cranapt echo "Pin-Priority: 700" >> /etc/apt/preferences.d/99cranapt apt-get update -q apt-get install -q -y python3-{dbus,gi,apt} make sudo su - -c "R -e \"install.packages('bspm')\"" RHOME=$(R RHOME) echo "suppressMessages(bspm::enable())" >> $RHOME/etc/Rprofile.site echo "options(bspm.version.check=FALSE)" >> $RHOME/etc/Rprofile.site # RStudio server: https://posit.co/download/rstudio-server/ rm -f *.deb if [ "${installRStudioServer}" = "Yes" ]; then curl -s -L -O ${RStudioServerURL} apt-get install -q -y ./*.deb systemctl enable rstudio-server fi # RStudio desktop: https://posit.co/download/rstudio-desktop/ rm -f *.deb if ( echo "${installRStudioDesktop}" | grep -q -i "Yes" ); then curl -s -L -O ${RStudioDesktopURL} apt-get install -q -y ./*.deb rm -f *.deb curl -s -L -O ${PositronURL} apt-get install -q -y ./*.deb fi # Paws (AWS SDK for R): https://github.com/paws-r/paws#installation #apt-get install -q -y libcurl4-openssl-dev libssl-dev libxml2-dev sudo su - -c "R -e \"install.packages('paws')\"" # reticulate (R Interface to Python): https://rstudio.github.io/reticulate/ #apt-get install -q -y python3-venv libpng-dev sudo su - -c "R -e \"install.packages('reticulate')\"" # tidyverse (collection of R packages for data science): https://www.tidyverse.org/ sudo su - -c "R -e \"install.packages('tidyverse')\"" # Shiny Server: https://posit.co/download/shiny-server/ rm -f *.deb if [ "${installShinyServer}" = "Yes" ]; then sudo su - -c "R -e \"install.packages('shiny')\"" sudo su - -c "R -e \"install.packages('rmarkdown')\"" curl -s -L -O ${ShinyServerURL} apt-get install -q -y ./*.deb systemctl enable shiny-server fi # git apt-get install -q -y git # GDebi apt-get install -q -y gdebi-core # New Python Versions: https://launchpad.net/~deadsnakes/+archive/ubuntu/ppa add-apt-repository ppa:deadsnakes/ppa -y apt-get update -q rm -f ${!0} mode: "000740" owner: "root" group: "root" commands: install: command: "/root/install-r.sh > /var/log/install-r.log 2>&1" ignoreErrors: "true" Properties: ImageId: # https://ubuntu.com/server/docs/cloud-images/amazon-ec2 https://ubuntu.com/blog/ubuntu-pro-is-now-part-of-the-aws-ec2-console !If [ useUbuntu2404x86, "{{resolve:ssm:/aws/service/canonical/ubuntu/server/24.04/stable/current/amd64/hvm/ebs-gp3/ami-id}}", "{{resolve:ssm:/aws/service/canonical/ubuntu/pro-server/24.04/stable/current/amd64/hvm/ebs-gp3/ami-id}}", ] InstanceType: !Ref instanceType IamInstanceProfile: !Ref instanceProfile KeyName: !Ref ec2KeyPair SubnetId: !Ref subnetID Monitoring: true DisableApiTermination: !If [enableProtection, true, false] EbsOptimized: true SecurityGroupIds: - !Ref sgEC2Instance - !If [createSgCfRStudio, !Ref sgRStudioServerIPv4, !Ref AWS::NoValue] - !If [createSgCfRStudio, !Ref sgRStudioServerIPv6, !Ref AWS::NoValue] - !If [createSgCfShiny, !Ref sgShinyServerIPv4, !Ref AWS::NoValue] - !If [createSgCfShiny, !Ref sgShinyServerIPv6, !Ref AWS::NoValue] BlockDeviceMappings: - DeviceName: /dev/sda1 Ebs: VolumeType: !Ref volumeType VolumeSize: !Ref volumeSize DeleteOnTermination: true Encrypted: true UserData: Fn::Base64: !Sub | #!/bin/bash mkdir -p /tmp/cfn cd /tmp/cfn export DEBIAN_FRONTEND=noninteractive systemctl stop apt-daily.timer apt-daily-upgrade.timer apt-get clean all apt-get update -q sleep 20 apt-get install -q -y procps pkill apt pkill dpkg apt-get install -q -y wget tmux unzip tar curl sed # CfN scripts: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-helper-scripts-reference.html apt-get install -q -y python3 python3-pip python3-setuptools python3-docutils python3-daemon curl -s -L -O https://s3.amazonaws.com/cloudformation-examples/aws-cfn-bootstrap-py3-latest.tar.gz tar -xf aws-cfn-bootstrap-py3-latest.tar.gz cd aws-cfn-bootstrap-2.0 python3 setup.py build > /var/log/install-cfn-helper.log 2>&1 python3 setup.py install >> /var/log/install-cfn-helper.log 2>&1 cd /tmp/cfn export CFN_INIT="python3 /usr/local/bin/cfn-init" $CFN_INIT -v --stack ${AWS::StackName} --resource ec2Instance --region ${AWS::Region} -c setup # Install desktop environment and DCV? if ( echo "${installRStudioDesktop}" | grep -q -i "Yes" ); then $CFN_INIT -v --stack ${AWS::StackName} --resource ec2Instance --region ${AWS::Region} -c dcv_install fi # R $CFN_INIT -v --stack ${AWS::StackName} --resource ec2Instance --region ${AWS::Region} -c app_install # systemctl set-default multi-user.target systemctl daemon-reload systemctl enable dcv-post-reboot sleep 1 && reboot Tags: - Key: Name Value: !Ref ec2Name - Key: StackName Value: !Ref AWS::StackName - Key: StackId Value: !Ref AWS::StackId - Key: GitHub Value: https://github.com/aws-samples/sample-r-server elasticIP: Condition: useElasticIP Type: AWS::EC2::EIP Properties: Domain: vpc NetworkBorderGroup: !Ref AWS::Region InstanceId: !Ref ec2Instance Tags: - Key: StackName Value: !Ref AWS::StackName - Key: StackId Value: !Ref AWS::StackId - Key: Name Value: !Sub - "${AWS::StackName}-elasticIP-${UID}" - UID: !Select [ 3, !Split ["-", !Select [2, !Split ["/", !Ref AWS::StackId]]], ] - Key: GitHub Value: https://github.com/aws-samples/sample-r-server backupPlan: Type: AWS::Backup::BackupPlan Condition: createBackup Properties: BackupPlan: BackupPlanName: !Sub - "${AWS::StackName}-backupPlan-${UID}" - UID: !Select [ 3, !Split ["-", !Select [2, !Split ["/", !Ref AWS::StackId]]], ] BackupPlanRule: - RuleName: !Sub - "${AWS::StackName}-backupRule-${UID}" - UID: !Select [ 3, !Split ["-", !Select [2, !Split ["/", !Ref AWS::StackId]]], ] TargetBackupVault: !Ref backupVault ScheduleExpression: !Ref scheduleExpression ScheduleExpressionTimezone: !Ref scheduleExpressionTimezone Lifecycle: DeleteAfterDays: !Ref deleteAfterDays BackupPlanTags: { "StackName": !Ref AWS::StackName, "StackId": !Ref AWS::StackId, "GitHub": "https://github.com/aws-samples/sample-r-server", } backupVault: Type: AWS::Backup::BackupVault Condition: createBackup UpdateReplacePolicy: Delete Properties: BackupVaultName: !Sub - "${AWS::StackName}-backupVault-${UID}" - UID: !Select [ 3, !Split ["-", !Select [2, !Split ["/", !Ref AWS::StackId]]], ] BackupVaultTags: { "StackName": !Ref AWS::StackName, "StackId": !Ref AWS::StackId, "GitHub": "https://github.com/aws-samples/sample-r-server", } backupSelection: Type: AWS::Backup::BackupSelection Condition: createBackup Properties: BackupPlanId: !Ref backupPlan BackupSelection: IamRoleArn: !GetAtt backupRestoreRole.Arn Resources: - !Sub arn:${AWS::Partition}:ec2:${AWS::Region}:${AWS::AccountId}:instance/${ec2Instance} SelectionName: !Sub - "${AWS::StackName}-backupSelection-${UID}" - UID: !Select [ 3, !Split ["-", !Select [2, !Split ["/", !Ref AWS::StackId]]], ] backupRestoreRole: Type: AWS::IAM::Role Condition: createBackup Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Service: backup.amazonaws.com Action: sts:AssumeRole Policies: - PolicyName: restore-EC2-instance-profile PolicyDocument: # https://docs.aws.amazon.com/aws-backup/latest/devguide/restoring-ec2.html Version: "2012-10-17" Statement: - Effect: Allow Action: - iam:PassRole Resource: !GetAtt instanceIamRole.Arn ManagedPolicyArns: - !Sub arn:${AWS::Partition}:iam::aws:policy/service-role/AWSBackupServiceRolePolicyForBackup - !Sub arn:${AWS::Partition}:iam::aws:policy/service-role/AWSBackupServiceRolePolicyForRestores Tags: - Key: StackName Value: !Ref AWS::StackName - Key: StackId Value: !Ref AWS::StackId - Key: GitHub Value: https://github.com/aws-samples/sample-r-server cfVPCOriginRStudio: Type: AWS::CloudFront::VpcOrigin Condition: cfVPCOriginRStudio Properties: VpcOriginEndpointConfig: Arn: !Sub arn:${AWS::Partition}:ec2:${AWS::Region}:${AWS::AccountId}:instance/${ec2Instance} Name: !Sub ${AWS::StackName}-${ec2Instance}-RStudioServer OriginProtocolPolicy: http-only HTTPPort: 8787 OriginSSLProtocols: - TLSv1.2 Tags: - Key: StackName Value: !Ref AWS::StackName - Key: StackId Value: !Ref AWS::StackId - Key: GitHub Value: https://github.com/aws-samples/sample-r-server cfDistributionRStudio: Type: AWS::CloudFront::Distribution Condition: createCfRStudio DependsOn: ec2Instance Metadata: cfn_nag: rules_to_suppress: - id: W70 reason: "TLSv1.2_2025 is enabled" #checkov:skip=CKV_AWS_68: "Unable to associate WAF for stacks launched outside us-east-1 Region. User to associate WAF manually or subscribe flat-rate CloudFront plan with WAF" #checkov:skip=CKV_AWS_86: "Access Logging is enabled" #checkov:skip=CKV_AWS_174: "TLSv1.2_2025 is enabled" Properties: DistributionConfig: Origins: - !If - cfVPCOriginRStudio - DomainName: !GetAtt ec2Instance.PrivateDnsName Id: !Ref ec2Instance VpcOriginConfig: VpcOriginId: !GetAtt cfVPCOriginRStudio.Id - DomainName: !GetAtt ec2Instance.PublicDnsName Id: !Ref ec2Instance CustomOriginConfig: OriginProtocolPolicy: http-only HTTPPort: 8787 OriginSSLProtocols: - TLSv1.2 Enabled: true Comment: !Sub ${AWS::StackName}-RStudioServer HttpVersion: http2and3 ViewerCertificate: CloudFrontDefaultCertificate: true MinimumProtocolVersion: TLSv1.2_2025 DefaultCacheBehavior: AllowedMethods: !Split [",", "GET,HEAD,OPTIONS,PUT,PATCH,POST,DELETE"] CachedMethods: - "HEAD" - "GET" Compress: true CachePolicyId: "4cc15a8a-d715-48a4-82b8-cc0b614638fe" OriginRequestPolicyId: "216adef6-5c7f-47e4-b989-5492eafa07d3" ResponseHeadersPolicyId: "67f7725c-6f97-4210-82d7-5512b31e9d03" TargetOriginId: !Ref ec2Instance ViewerProtocolPolicy: "redirect-to-https" Logging: !If - cloudFrontLogging - Bucket: !GetAtt cfLogBucket.DomainName IncludeCookies: false Prefix: "RStudioServer/" - !Ref AWS::NoValue Tags: - Key: StackName Value: !Ref AWS::StackName - Key: StackId Value: !Ref AWS::StackId - Key: GitHub Value: https://github.com/aws-samples/sample-r-server cfLogBucket: Type: AWS::S3::Bucket Condition: cloudFrontLogging Metadata: Properties: BucketEncryption: ServerSideEncryptionConfiguration: - ServerSideEncryptionByDefault: SSEAlgorithm: AES256 BucketKeyEnabled: true OwnershipControls: Rules: - ObjectOwnership: BucketOwnerPreferred PublicAccessBlockConfiguration: BlockPublicAcls: true BlockPublicPolicy: true IgnorePublicAcls: true RestrictPublicBuckets: true VersioningConfiguration: Status: Enabled LifecycleConfiguration: Rules: - Id: Delete-Previous-Versions NoncurrentVersionExpiration: NoncurrentDays: 1 Status: Enabled - Id: Delete-Expired-Delete-Marker ExpiredObjectDeleteMarker: true Status: Enabled LoggingConfiguration: DestinationBucketName: !Ref cfS3AccessLogBucket LogFilePrefix: !Sub "${AWS::StackName}/" Tags: - Key: StackName Value: !Ref AWS::StackName - Key: StackId Value: !Ref AWS::StackId - Key: GitHub Value: https://github.com/aws-samples/sample-r-server cfLogBucketPolicy: Type: AWS::S3::BucketPolicy Condition: cloudFrontLogging Properties: Bucket: !Ref cfLogBucket PolicyDocument: Statement: - Effect: Deny Action: "*" Condition: Bool: "aws:SecureTransport": "false" Principal: "*" Resource: !Sub "${cfLogBucket.Arn}/*" - !If - createCfRStudio - Sid: AllowCloudFrontServicePrincipalRStudio Effect: Allow Principal: Service: "cloudfront.amazonaws.com" Action: "s3:PutObject" Resource: !Sub "arn:${AWS::Partition}:s3:::${cfLogBucket}/*" Condition: StringEquals: "AWS:SourceArn": !Sub "arn:${AWS::Partition}:cloudfront::${AWS::AccountId}:distribution/${cfDistributionRStudio}" - !Ref AWS::NoValue - !If - createCfShiny - Sid: AllowCloudFrontServicePrincipalShiny Effect: Allow Principal: Service: "cloudfront.amazonaws.com" Action: "s3:PutObject" Resource: !Sub "arn:${AWS::Partition}:s3:::${cfLogBucket}/*" Condition: StringEquals: "AWS:SourceArn": !Sub "arn:${AWS::Partition}:cloudfront::${AWS::AccountId}:distribution/${cfDistributionShiny}" - !Ref AWS::NoValue cfS3AccessLogBucket: Type: AWS::S3::Bucket Condition: cloudFrontLogging Metadata: cfn_nag: rules_to_suppress: - id: W35 reason: "Bucket stores access logs" #checkov:skip=CKV_AWS_18: "Bucket stores access logs" Properties: BucketEncryption: ServerSideEncryptionConfiguration: - ServerSideEncryptionByDefault: SSEAlgorithm: AES256 BucketKeyEnabled: true PublicAccessBlockConfiguration: BlockPublicAcls: true BlockPublicPolicy: true IgnorePublicAcls: true RestrictPublicBuckets: true VersioningConfiguration: Status: Enabled LifecycleConfiguration: Rules: - Id: Delete-Previous-Versions NoncurrentVersionExpiration: NoncurrentDays: 1 Status: Enabled - Id: Delete-Expired-Delete-Marker ExpiredObjectDeleteMarker: true Status: Enabled Tags: - Key: StackName Value: !Ref AWS::StackName - Key: StackId Value: !Ref AWS::StackId - Key: GitHub Value: https://github.com/aws-samples/sample-r-server cfS3AccessLogBucketPolicy: Type: AWS::S3::BucketPolicy Condition: cloudFrontLogging Properties: Bucket: !Ref cfS3AccessLogBucket PolicyDocument: # https://docs.aws.amazon.com/AmazonS3/latest/userguide/enable-server-access-logging.html Statement: - Effect: Deny Action: "*" Condition: Bool: "aws:SecureTransport": "false" Principal: "*" Resource: !Sub "${cfS3AccessLogBucket.Arn}/*" - Effect: Allow Principal: Service: logging.s3.amazonaws.com Action: s3:PutObject Resource: !Sub "${cfS3AccessLogBucket.Arn}/*" Condition: StringEquals: aws:SourceAccount: !Ref AWS::AccountId cfVPCOriginShiny: Type: AWS::CloudFront::VpcOrigin Condition: cfVPCOriginShiny Properties: VpcOriginEndpointConfig: Arn: !Sub arn:${AWS::Partition}:ec2:${AWS::Region}:${AWS::AccountId}:instance/${ec2Instance} Name: !Sub ${AWS::StackName}-${ec2Instance}-ShinyServer OriginProtocolPolicy: http-only HTTPPort: 3838 OriginSSLProtocols: - TLSv1.2 Tags: - Key: StackName Value: !Ref AWS::StackName - Key: StackId Value: !Ref AWS::StackId - Key: GitHub Value: https://github.com/aws-samples/sample-r-server cfDistributionShiny: Type: AWS::CloudFront::Distribution Condition: createCfShiny DependsOn: ec2Instance Metadata: cfn_nag: rules_to_suppress: - id: W70 reason: "TLSv1.2_2025 is enabled" #checkov:skip=CKV_AWS_68: "Unable to associate WAF for stacks launched outside us-east-1 Region. User to associate WAF manually or subscribe flat-rate CloudFront plan with WAF" #checkov:skip=CKV_AWS_86: "Access Logging is enabled" #checkov:skip=CKV_AWS_174: "TLSv1.2_2025 is enabled" Properties: DistributionConfig: Origins: - !If - cfVPCOriginShiny - DomainName: !GetAtt ec2Instance.PrivateDnsName Id: !Ref ec2Instance VpcOriginConfig: VpcOriginId: !GetAtt cfVPCOriginShiny.Id - DomainName: !GetAtt ec2Instance.PublicDnsName Id: !Ref ec2Instance CustomOriginConfig: OriginProtocolPolicy: http-only HTTPPort: 3838 OriginSSLProtocols: - TLSv1.2 Enabled: true Comment: !Sub ${AWS::StackName}-ShinyServer HttpVersion: http2and3 WebACLId: !Ref AWS::NoValue ViewerCertificate: CloudFrontDefaultCertificate: true MinimumProtocolVersion: TLSv1.2_2025 DefaultCacheBehavior: AllowedMethods: !Split [",", "GET,HEAD,OPTIONS,PUT,PATCH,POST,DELETE"] CachedMethods: - "HEAD" - "GET" Compress: true CachePolicyId: "4cc15a8a-d715-48a4-82b8-cc0b614638fe" OriginRequestPolicyId: "216adef6-5c7f-47e4-b989-5492eafa07d3" ResponseHeadersPolicyId: "67f7725c-6f97-4210-82d7-5512b31e9d03" TargetOriginId: !Ref ec2Instance ViewerProtocolPolicy: "redirect-to-https" Logging: !If - cloudFrontLogging - Bucket: !GetAtt cfLogBucket.DomainName IncludeCookies: false Prefix: "ShinyServer/" - !Ref AWS::NoValue Tags: - Key: StackName Value: !Ref AWS::StackName - Key: StackId Value: !Ref AWS::StackId - Key: GitHub Value: https://github.com/aws-samples/sample-r-server Outputs: EC2console: Description: EC2 console Value: !Sub "https://${AWS::Region}.console.aws.amazon.com/ec2/home?region=${AWS::Region}#InstanceDetails:instanceId=${ec2Instance}" EC2instanceID: Description: EC2 Instance ID Value: !Ref ec2Instance EC2instanceConnect: Condition: createSgEIC Description: EC2 Instance Connect Value: !Sub "https://${AWS::Region}.console.aws.amazon.com/ec2-instance-connect/ssh?connType=standard&instanceId=${ec2Instance}&osUser=ubuntu&sshPort=22#/" EC2serialConsole: Description: EC2 Serial Console Value: !Sub "https://${AWS::Region}.console.aws.amazon.com/ec2-instance-connect/ssh?connType=serial&instanceId=${ec2Instance}&serialPort=0#/" EC2iamRole: Description: EC2 IAM role Value: !Sub - "https://console.aws.amazon.com/iam/home#/roles/details/${role}" - role: !Select [1, !Split ["/", !GetAtt instanceIamRole.Arn]] SSMsessionManager: Description: SSM Session Manager (" sudo passwd ubuntu " to change login user password) Value: !Sub "https://${AWS::Region}.console.aws.amazon.com/systems-manager/session-manager/${ec2Instance}" DCVUrl: Condition: installRStudioDesktop Description: DCV URL ( login as ubuntu ) Value: !If [ displayPublicIP, !Sub "https://${ec2Instance.PublicIp}:8443/?username=ubuntu ( dcv://ubuntu:@${ec2Instance.PublicIp} )", !Sub "https://${ec2Instance.PrivateIp}:8443/?username=ubuntu ( dcv://ubuntu:@${ec2Instance.PrivateIp} )", ] CloudFrontConsole: Condition: createCloudFront Description: CloudFront console Value: !Sub "https://console.aws.amazon.com/cloudfront/?region=${AWS::Region}" RStudioServerURL: Condition: createCfRStudio Description: RStudio Server URL ( login as ubuntu ) Value: !Sub "https://${cfDistributionRStudio.DomainName}" ShinyServerUrl: Condition: createCfShiny Description: Shiny Server URL Value: !Sub "https://${cfDistributionShiny.DomainName}" ReverseProxyURL: Condition: installReverseProxy Description: DCV reverse proxy URL ( login as ubuntu ) Value: !Sub - "https://${IpAddress}/?username=ubuntu ( dcv://ubuntu:@${IpAddress}:443 )" - IpAddress: !If [ displayPublicIP, !GetAtt ec2Instance.PublicIp, !GetAtt ec2Instance.PrivateIp, ]