├── .gitignore ├── LICENSE ├── README-template.md ├── README.md ├── misc ├── Slack_Polls.md ├── TechTuesdayImage.jpg └── cli_kungfu.md ├── slides ├── Use Terraform to Provision Your Own Cloud-Based Remote Browsing Workstation - S4.png └── Use Terraform to Provision Your Own Cloud-Based Remote Browsing Workstation.pptx └── workbook ├── Step_01.md ├── Step_02.md ├── Step_03.md ├── Step_04.md ├── Step_05.md ├── Step_06.md ├── Step_07.md ├── Step_08.md ├── Step_09.md ├── Step_10.md ├── Step_11.md ├── Step_12.md ├── Step_13.md ├── Step_14.md └── Step_15.md /.gitignore: -------------------------------------------------------------------------------- 1 | slides/*.tmp 2 | 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Kenneth G. Hartman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README-template.md: -------------------------------------------------------------------------------- 1 | # ZZZZZZZZZZ 2 | This terraform script deploys an Ubuntu Workstation with minimal additional software 3 | installed. An example use case if a temporary sandbox system for surfing potentially 4 | dangerous websites. _NOTE: Don't break the law, as AWS Terms of Service still apply 5 | and this is not exactly covert._ 6 | 7 | ## DEPLOYMENT 8 | Git clone the repository and provision the cloud workstation by running the following 9 | commands, one at a time: 10 | 11 | ``` 12 | git clone XXXXXXXXXX 13 | ``` 14 | and then 15 | 16 | ``` 17 | cd ZZZZZZZZZZ 18 | ``` 19 | 20 | Next modify the `terraform.tfvars` contents with the settings that are appropriate 21 | for your AWS Account. 22 | 23 | Now run these commands: 24 | 25 | ``` 26 | terraform init 27 | terraform apply 28 | ``` 29 | 30 | Obviously, this assumes that you have both `git` and `terraform` installed. 31 | 32 | ## USAGE 33 | The cloud workstation can be accessed via either SSH or RDP. The necessary information 34 | will be an output when the script is complete. 35 | 36 | 37 | ## ADDITIONAL NOTES 38 | The `workstation1.sh` is passed as user data to the instance and runs the first time 39 | the system boots. The script generates a log entry for almost every command as it runs 40 | and sends it to `/tmp/first-boot.log` 41 | 42 | Since the script installs updates and some software, it may take a while. I like to watch 43 | the first boot script by watching its log when connected via SSH. Just run: 44 | 45 | ``` 46 | tail -f /tmp/first-boot.log 47 | ``` 48 | 49 | Otherwise, if you are connected via SSH, you will get a system message when the boot 50 | script has completed: 51 | 52 | ``` 53 | NOTICE: First Boot Setup Has Completed 54 | ``` 55 | 56 | After that point it is ready for an Remote Desktop (RDP) Connection. Use an RDP client, 57 | such as Microsoft Terminal Services Client (MSTSC) to RDP to the IP address that is output 58 | by the Terraform script. 59 | 60 | **DON'T FORGET TO CHANGE THE RDP PASSWORD AFTER LOGON** 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # terraform-cloud-workstation 2 | 3 | ## Tech Tuesday Workshop - Use Terraform to Provision Your Own Cloud-Based Remote Browsing Workstation 4 | Tuesday, March 02, 2021 at 1:00 PM EST (2021-03-02 18:00:00 UTC) 5 | Kenneth G. Hartman 6 | 7 | ## Overview 8 | **Abstract:** This workshop will teach you everything you need to know to provision your own Cloud-Based Ubuntu Workstation in AWS for Remote Browsing. Sometimes there are valid security and privacy reasons to use a temporary workstation for potentially malicious websites or to avoid tracking. 9 | 10 | **Prerequisites:** Attendees will need an AWS Account and should be comfortable launching an EC2 instance and connecting to it. Here is a tutorial on launching an EC2 virtual machine instance: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EC2_GetStarted.html 11 | 12 | During the session we will briefly cover some basic git commands as well as Terraform basics, including installation. 13 | 14 | ## System Requirements: 15 | 16 | Link to Sign Up for an AWS Account: https://aws.amazon.com/free 17 | Terraform is available for download here: https://www.terraform.io/downloads.html 18 | Up to date Web Browser 19 | For a consistent experience, we will be using the new AWS CloudShell. 20 | 21 | ## Register: 22 | https://www.sans.org/account/login/?url=webcasts/118665/ 23 | -------------------------------------------------------------------------------- /misc/Slack_Polls.md: -------------------------------------------------------------------------------- 1 | # Slack Polls 2 | 3 | ## Ready for Class 4 | ``` 5 | /poll "Are you ready to rock this?" "Beverage is handy" ☕ "RDP Installed (if using a MAC)" :customs: "AWS Account set up… and ready to go!" :aws-icon2: "AWS? Wait… Cloud? What class am I in?" 😳 anonymous 6 | ``` 7 | 8 | ## Step 01 9 | ``` 10 | /poll "Where are you with Step 1 - Prepare the Environment?" "Done" :white_check_mark: "Working on Prerequisites" :clock3: "Still working, but I got this!" :clock6: "I need help!! (Please post msg in #help Slack channel)" :sos: limit 1 anonymous 11 | ``` 12 | 13 | ## Step 02 14 | ``` 15 | /poll "Where are you with Step 2 - Create a Github Account?" "Done - I had a Github Account" :white_check_mark: "Done - I set it up just now" :heavy_check_mark: "Working on Step 01" :clock3: "Still working on this step, but I got this!" :clock6: "I need help!! (Please post msg in #help Slack channel)" :sos: limit 1 anonymous 16 | ``` 17 | 18 | ## Step 03 19 | ``` 20 | /poll "Where are you with Step 3 - Create a Github Repository?" "Done" :white_check_mark: "Still working on this step, but I got this!" :clock6: "Working on Step 02" :clock3: "Working on previous steps" :clock9: "Just watching, I plan to do the work later" :eyeglasses: "I need help!! (Please post msg in #help Slack channel)" :sos: limit 1 anonymous 21 | ``` 22 | 23 | ## Step 04 24 | ``` 25 | /poll "Where are you with Step 4 - Create an AWS CLI profile?" "Done" :white_check_mark: "Still working on this step, but I got this!" :clock6: "Working on Step 03" :clock3: "Working on previous steps" :clock9: "Just watching, I plan to do the work later" :eyeglasses: "I need help!! (Please post msg in #help Slack channel)" :sos: limit 1 anonymous 26 | ``` 27 | 28 | ## Step 05 29 | ``` 30 | /poll "Where are you with Step 5 - Create a SSH Key Pair?" "Done" :white_check_mark: "Still working on this step, but I got this!" :clock6: "Working on Step 04" :clock3: "Working on previous steps" :clock9: "Just watching, I plan to do the work later" :eyeglasses: "I need help!! (Please post msg in #help Slack channel)" :sos: limit 1 anonymous 31 | ``` 32 | 33 | ## Step 06 34 | ``` 35 | /poll "Where are you with Step 6 - Create a terraform.tfvars File?" "Done" :white_check_mark: "Still working on this step, but I got this!" :clock6: "Working on Step 05" :clock3: "Working on previous steps" :clock9: "Just watching, I plan to do the work later" :eyeglasses: "I need help!! (Please post msg in #help Slack channel)" :sos: limit 1 anonymous 36 | ``` 37 | 38 | ## Step 07 39 | ``` 40 | /poll "Where are you with Step 7 - Perform First git commit?" "Done" :white_check_mark: "Still working on this step, but I got this!" :clock6: "Working on Step 06" :clock3: "Working on previous steps" :clock9: "Just watching, I plan to do the work later" :eyeglasses: "I need help!! (Please post msg in #help Slack channel)" :sos: limit 1 anonymous 41 | ``` 42 | 43 | ## Step 08 44 | ``` 45 | /poll "Where are you with Step 8 - main.tf - Add Variables, Provider & Data Blocks?" "Done" :white_check_mark: "Still working on this step, but I got this!" :clock6: "Working on Step 07" :clock3: "Working on previous steps" :clock9: "Just watching, I plan to do the work later" :eyeglasses: "I need help!! (Please post msg in #help Slack channel)" :sos: limit 1 anonymous 46 | ``` 47 | 48 | ## Step 09 49 | ``` 50 | /poll "Where are you with Step 9 - Terraform Init and .gitignore?" "Done" :white_check_mark: "Still working on this step, but I got this!" :clock6: "Working on Step 08" :clock3: "Working on previous steps" :clock9: "Just watching, I plan to do the work later" :eyeglasses: "I need help!! (Please post msg in #help Slack channel)" :sos: limit 1 anonymous 51 | ``` 52 | 53 | ## Step 10 54 | ``` 55 | /poll "Where are you with Step 10 - main.tf - Add VPC resources?" "Done" :white_check_mark: "Still working on this step, but I got this!" :clock6: "Working on Step 09" :clock3: "Working on previous steps" :clock9: "Just watching, I plan to do the work later" :eyeglasses: "I need help!! (Please post msg in #help Slack channel)" :sos: limit 1 anonymous 56 | ``` 57 | 58 | ## Step 11 59 | ``` 60 | /poll "Where are you with Step 11 - main.tf - Add Security Group?" "Done" :white_check_mark: "Still working on this step, but I got this!" :clock6: "Working on Step 10" :clock3: "Working on previous steps" :clock9: "Just watching, I plan to do the work later" :eyeglasses: "I need help!! (Please post msg in #help Slack channel)" :sos: limit 1 anonymous 61 | ``` 62 | 63 | ## Step 12 64 | ``` 65 | /poll "Where are you with Step 12 - main.tf - Add Ubuntu Instance?" "Done" :white_check_mark: "Still working on this step, but I got this!" :clock6: "Working on Step 11" :clock3: "Working on previous steps" :clock9: "Just watching, I plan to do the work later" :eyeglasses: "I need help!! (Please post msg in #help Slack channel)" :sos: limit 1 anonymous 66 | ``` 67 | 68 | ## Step 13 69 | ``` 70 | /poll "Where are you with Step 13 - Customize the Ubuntu Instance?" "Done" :white_check_mark: "Still working on this step, but I got this!" :clock6: "Working on Step 12" :clock3: "Working on previous steps" :clock9: "Just watching, I plan to do the work later" :eyeglasses: "I need help!! (Please post msg in #help Slack channel)" :sos: limit 1 anonymous 71 | ``` 72 | 73 | ## Step 14 74 | ``` 75 | /poll "Where are you with Step 14 - Finish the Documentation for the Project?" "Done" :white_check_mark: "Still working on this step, but I got this!" :clock6: "Working on Step 13" :clock3: "Working on previous steps" :clock9: "Just watching, I plan to do the work later" :eyeglasses: "I need help!! (Please post msg in #help Slack channel)" :sos: limit 1 anonymous 76 | ``` 77 | 78 | ## Step 15 79 | ``` 80 | /poll "Where are you with Step 15 - Teardown and Test?" "Done" :white_check_mark: "Still working on this step, but I got this!" :clock6: "Working on Step 14" :clock3: "Working on previous steps" :clock9: "Just watching, I plan to do the work later" :eyeglasses: "I need help!! (Please post msg in #help Slack channel)" :sos: limit 1 anonymous 81 | ``` 82 | -------------------------------------------------------------------------------- /misc/TechTuesdayImage.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Resistor52/terraform-cloud-workstation/fcaa5ad13b4580c7945f92eea60101fe9030f689/misc/TechTuesdayImage.jpg -------------------------------------------------------------------------------- /misc/cli_kungfu.md: -------------------------------------------------------------------------------- 1 | # CLI Kung Fu 2 | 3 | This is just a list of AWS CLI commands that may come in handy. Listed here just to 4 | streamline the presentation. 5 | 6 | ## Delete the Tech Tuesday IAM User 7 | 8 | ``` 9 | aws iam delete-user --user-name tech-tuesday 10 | ``` 11 | 12 | ## Delete the cloud_workstation in AWS 13 | 14 | ``` 15 | aws ec2 delete-key-pair --key-name cloud_workstation 16 | ``` 17 | 18 | ## List the AWS Access Keys 19 | 20 | ``` 21 | aws iam list-access-keys --user-name tech-tuesday --query AccessKeyMetadata[0].AccessKeyId --output text 22 | 23 | ``` 24 | 25 | ## Disable the AWS Access Key (without deleting it) 26 | 27 | ``` 28 | aws iam update-access-key --access-key-id $(aws iam list-access-keys --user-name tech-tuesday --query AccessKeyMetadata[0].AccessKeyId --output text) --status Inactive --user-name tech-tuesday 29 | ``` 30 | 31 | ## Delete the AWS Access Key 32 | 33 | ``` 34 | aws iam delete-access-key --access-key-id $(aws iam list-access-keys --user-name tech-tuesday --query AccessKeyMetadata[0].AccessKeyId --output text) --user-name tech-tuesday 35 | ``` 36 | 37 | # Rotate AWS Access Key during demo 38 | 39 | ``` 40 | aws iam delete-access-key --access-key-id $(aws iam list-access-keys --user-name tech-tuesday --query AccessKeyMetadata[0].AccessKeyId --output text) --user-name tech-tuesday && SECRET=$(aws iam create-access-key --user-name tech-tuesday | jq '.AccessKey.SecretAccessKey') && KEY=$(aws iam list-access-keys --user-name tech-tuesday --query AccessKeyMetadata[0].AccessKeyId --output text) && echo -e "[myterraform]\naws_access_key_id = $KEY\naws_secret_access_key = ${SECRET:1:-1}" > ~/.aws/credentials 41 | ``` 42 | 43 | # Rotate PEM During Demo 44 | ``` 45 | aws ec2 delete-key-pair --key-name cloud_workstation && rm -f ~/cloud_workstation.pem && aws ec2 create-key-pair --key-name cloud_workstation --query 'KeyMaterial' --output text > ~/cloud_workstation.pem && chmod 400 ~/cloud_workstation.pem 46 | ``` 47 | -------------------------------------------------------------------------------- /slides/Use Terraform to Provision Your Own Cloud-Based Remote Browsing Workstation - S4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Resistor52/terraform-cloud-workstation/fcaa5ad13b4580c7945f92eea60101fe9030f689/slides/Use Terraform to Provision Your Own Cloud-Based Remote Browsing Workstation - S4.png -------------------------------------------------------------------------------- /slides/Use Terraform to Provision Your Own Cloud-Based Remote Browsing Workstation.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Resistor52/terraform-cloud-workstation/fcaa5ad13b4580c7945f92eea60101fe9030f689/slides/Use Terraform to Provision Your Own Cloud-Based Remote Browsing Workstation.pptx -------------------------------------------------------------------------------- /workbook/Step_01.md: -------------------------------------------------------------------------------- 1 | # Step 1 - Prepare the Environment 2 | 3 | 1. Use your web browser and log into https://aws.amazon.com 4 | 2. Navigate to the AWS CloudShell 5 | 3. Close any pop-up windows 6 | 4. Verify that `git` is already installed by typing: 7 | 8 | ``` 9 | which git 10 | ``` 11 | 12 | Note the path to the executable. This shows it is installed. 13 | 14 | 5. Check to see if terraform is installed by typing: 15 | 16 | ``` 17 | which terraform 18 | ``` 19 | 20 | Results show that it is not installed, so let's install it. 21 | 22 | 6. Open a new browser tab and navigate to 23 | https://learn.hashicorp.com/tutorials/terraform/install-cli. Scroll down to 24 | the second **Install terraform** heading. Select the **Linux** tab and then the 25 | **Amazon Linux** Tab. This will show the following three commands: 26 | 27 | ``` 28 | sudo yum install -y yum-utils 29 | sudo yum-config-manager --add-repo https://rpm.releases.hashicorp.com/AmazonLinux/hashicorp.repo 30 | sudo yum -y install terraform 31 | 32 | ``` 33 | 34 | The above commands work, but the problem is that anything which is not installed in the 35 | user's home directory will not persist after the CloudShell restarts. The work-around 36 | is to perform a manual installation of the pre-compiled binary. 37 | 38 | 7. Just run the following commands: 39 | 40 | ``` 41 | wget https://releases.hashicorp.com/terraform/0.14.7/terraform_0.14.7_linux_amd64.zip 42 | unzip terraform*.zip 43 | mkdir ~/bin 44 | mv terraform ~/bin/ 45 | rm terraform*.zip 46 | ``` 47 | 48 | 8. Finally, verify that terraform is installed by typing: 49 | 50 | ``` 51 | which terraform 52 | ``` 53 | 54 | and then type: 55 | 56 | ``` 57 | terraform help 58 | ``` 59 | 60 | **[Watch the Video](https://youtu.be/INC9MbsmYAM)** 61 | -------------------------------------------------------------------------------- /workbook/Step_02.md: -------------------------------------------------------------------------------- 1 | # Step 2 - Create a Github Account (if you don’t have one) 2 | 3 | **NOTE: If you already have a Github account, skip this step and simply login 4 | to Github.** 5 | 6 | 1. Open a new browser tab and navigate to https://github.com 7 | 2. Click the **Sign Up** button 8 | 3. Complete the form and then, 9 | 4. Verify your email address by clicking the link in the email from Github 10 | 11 | **[Watch the Video](https://youtu.be/3jiey--sjJ4)** 12 | -------------------------------------------------------------------------------- /workbook/Step_03.md: -------------------------------------------------------------------------------- 1 | # Step 3 - Create a Github Repository 2 | 3 | 1. While still logged into Github.com, click the **+** icon up in the top right 4 | corner. 5 | 2. Enter a name for your repository, for example: "tech-tue-cloud-workstation" 6 | 7 | _If the webpage indicates that the repository name is not available, append some 8 | digits to the end of the repository name._ 9 | 10 | 3. Provide a description of the repository, such as: "Tech Tuesday - Use TF to 11 | create a cloud workstation" 12 | 13 | 4. Make the repository private for now. (You can always make it public later by 14 | changing it the Repository Settings.) 15 | 16 | 5. Click the **Add a README file** checkbox and then, 17 | 18 | 6. Click the **Create Repository** button. In a moment, you repository will be 19 | created. 20 | 21 | 7. Now, lets clone this barebones repo to our AWS CloudShell. Click the green 22 | **Code** button. This will open a window with the URL to your repository. 23 | 24 | 8. Click the clipboard icon to the right of the repo URL (in the popup that just 25 | opened). 26 | 27 | 9. Now, navigate to the tab with CloudShell open. Click to plant your curser. 28 | Type `git clone ` and then paste in the url from your clipboard. Hit enter. 29 | 30 | 10. When prompted enter your Github username and password. **NOTE: You will NOT 31 | see your password reflected back to you as you type it into the CloudShell terminal. 32 | 33 | 11. Type `ls` at the prompt in the CloudShell terminal and hit enter. You should 34 | see a response that corresponds to the name you gave your repo. 35 | 36 | 12. Next type `cd tech` (where "tech" is the first few letters of your repo name) 37 | and the hit TAB and then ENTER. Isn't tab completion wonderful? You should now be in 38 | the directory that contains your barebones repository code. 39 | 40 | **[Watch the Video](https://youtu.be/m5DiBfHEJ2s)** 41 | -------------------------------------------------------------------------------- /workbook/Step_04.md: -------------------------------------------------------------------------------- 1 | # Step 4 - Create an AWS CLI profile 2 | 3 | ## Important Notes 4 | * Technically, the AWS CloudShell can run AWS CLI commands without running the 5 | `aws configure` command, but we want this script to be able to run anywhere, not 6 | just the CloudShell. 7 | * Normally, the CloudShell uses the permissions of the user who has logged into 8 | the Web Console. 9 | * DO NOT run aws configure on an EC2 instance, lest your `~/.aws/credentials` file 10 | get compromised! 11 | * Since the CloudShell can only be accessed via the Web Console, running 12 | `aws configure` is an acceptable risk for this workshop. 13 | 14 | ## Create a new IAM User 15 | 1. Navigate to the IAM Service in the AWS Web Console 16 | 2. Click on **Users** and then **Add user** 17 | 3. Create a new user named "tech-tuesday" and click only the "Programmatic acess" 18 | checkbox. 19 | 4. Click the blue **Next: Permissions** button. 20 | 5. Click the **Attach existing policies directly** button and click the checkbox 21 | to the right of the "AdministratorAccess policy." Click the blue **Next: Tags** 22 | button. 23 | 6. Click the blue **Create user** button. 24 | 7. Copy the Access key ID to a temporary text file (Don't even save this file.) 25 | 8. Click the **Show** hyperlink to expose the Secret Access key. Copy this to the 26 | temporary text file as well. Click the **Close** button. 27 | 28 | ## Configure the User Profile in the CloudShell 29 | 1. Navigate back to the AWS CloudShell. Click to plant your curser in the Cloudshell 30 | terminal. 31 | 2. Enter the command `aws configure --profile myterraform` into the CloudShell and 32 | press enter. 33 | 3. Next paste in the Access key ID from the temporary text file and then the Secret 34 | Access Key. 35 | 4. Enter `us-east-1` for the Default region name and `json` for the Default output 36 | format. 37 | 5. Test it by running the following command in the terminal: `aws sts get-caller-identity` 38 | 39 | Did you get the results you expected? This command shows the user that you logged 40 | into the web console with and not the user that we just created. 41 | 42 | 6. Let's run it again, with the profile specified: 43 | `aws sts get-caller-identity --profile myterraform` 44 | 45 | **[Watch the Video](https://youtu.be/UlP6B4B0bUY)** 46 | -------------------------------------------------------------------------------- /workbook/Step_05.md: -------------------------------------------------------------------------------- 1 | # Step 5 - Create a SSH Key Pair 2 | 3 | 1. Open a new browser tab and navigate to 4 | https://docs.aws.amazon.com/cli/latest/userguide/cli-services-ec2-keypairs.html#creating-a-key-pair 5 | This is the reference for how to use the AWS CLI to create a SSH Key Pair. 6 | 7 | 2. Navigate back to the CloudShell and click in the terminal to plant your curser. 8 | 3. Paste in the following command: 9 | `aws ec2 create-key-pair --key-name cloud_workstation --query 'KeyMaterial' --output text > ~/cloud_workstation.pem` 10 | 4. Type `ls -als ~/cloud_workstation.pem` to see the new file with its permissions. 11 | 5. Type `chmod 400 ~/cloud_workstation.pem` to change them so that the SSH client does 12 | not throw an error. 13 | 6. Rerun `ls -als ~/cloud_workstation.pem` to see the new permissions on the PEM file. 14 | 7. Type `cat ~/cloud_workstation.pem` to view the contents of the PEM file. 15 | 8. Copy the contents of the PEM file to a file saved somewhere besides the CloudShell 16 | to back it up. 17 | 18 | **[Watch the Video](https://youtu.be/_t82air0vss)** 19 | -------------------------------------------------------------------------------- /workbook/Step_06.md: -------------------------------------------------------------------------------- 1 | # Step 6 - Create a terraform.tfvars File 2 | 3 | A *.tfvars file holds parameters that the terraforms scripts will use. The purpose 4 | of the file is to abstract the customization parameters from the code that will 5 | typically not change. 6 | 7 | 1. Back in the CloudShell, make sure you are in the local directory for your 8 | terraform project. Type `cd ~/tech` and then press TAB, then ENTER. 9 | 2. Run the following command to open the Vim text editor: 10 | `vim terraform.tfvars` 11 | 3. Press the "i" key to enter insert mode. 12 | 4. Paste in the following lines: 13 | 14 | ``` 15 | aws_pem = "~/cloud_workstation.pem" 16 | aws_region = "us-east-1" 17 | aws_creds_file = "~/.aws/credentials" 18 | aws_profile = "myterraform" 19 | ``` 20 | 21 | 5. Next hit ESC and then type `:wq` to save the file and then quit Vim 22 | 23 | **NOTE:** Use ":q!" to quit without saving. 24 | 25 | **REFERENCE:** [Vim Cheatsheet](https://vim.rtorr.com/) 26 | 27 | 6. Type `ls` to see that the `terraform.tfvars` file has been added. 28 | 29 | **[Watch the Video](https://youtu.be/EtXHvlSiQb0)** 30 | -------------------------------------------------------------------------------- /workbook/Step_07.md: -------------------------------------------------------------------------------- 1 | # Step 7 - Perform First git commit 2 | 3 | 1. Back in the CloudShell, run the following commands, replacing "you@example.com" with your email and 4 | "Your Name" with your actual name (not your Github Username): 5 | 6 | ``` 7 | git config --global user.email "you@example.com" 8 | git config --global user.name "Your Name" 9 | ``` 10 | 11 | **REFERENCE:** https://git-scm.com/book/en/v2/Getting-Started-First-Time-Git-Setup 12 | 13 | 2. Make sure you are in the local directory for your terraform project. Type 14 | `cd ~/tech` and then press TAB. 15 | 16 | 3. In the terminal, type `ls` and you should see two files: 17 | 18 | ``` 19 | README.md terraform.tfvars 20 | ``` 21 | 22 | 4. Type `git status` to see if you have anything that needs to be committed. You 23 | should see: 24 | 25 | ``` 26 | On branch main 27 | Your branch is up to date with 'origin/main'. 28 | 29 | Untracked files: 30 | (use "git add ..." to include in what will be committed) 31 | terraform.tfvars 32 | 33 | nothing added to commit but untracked files present (use "git add" to track) 34 | ``` 35 | 36 | NOTE: If you see the following, you are in the wrong directory: 37 | 38 | ``` 39 | fatal: not a git repository (or any parent up to mount point /) 40 | Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set). 41 | ``` 42 | 43 | 5. Type `git add terr` and then the TAB key and then ENTER. 44 | 6. Run `git status` again. You should see: 45 | 46 | ``` 47 | On branch main 48 | Your branch is up to date with 'origin/main'. 49 | 50 | Changes to be committed: 51 | (use "git restore --staged ..." to unstage) 52 | new file: terraform.tfvars 53 | ``` 54 | 55 | 7. Now, lets commit the change. Type `git commit -m "added terraform.tfvars"`. 56 | Note that the portion of the command is your human-friendly commit message that 57 | indicates what change is made by this commit. Your result should look something like: 58 | 59 | ``` 60 | [main 74ac2b7] added terraform.tfvars 61 | 1 file changed, 5 insertions(+) 62 | create mode 100644 terraform.tfvars 63 | ``` 64 | 65 | 8. Type `git status` again. You should see: 66 | 67 | ``` 68 | On branch main 69 | Your branch is ahead of 'origin/main' by 1 commit. 70 | (use "git push" to publish your local commits) 71 | 72 | nothing to commit, working tree clean 73 | ``` 74 | 75 | **NOTE:** Going forward, it is not necessary to type `git status` unless you need 76 | to determine the status of your changes since the last commit. 77 | 78 | 9. Ok, time to push the change to Github. Type `git push origin main` and then provide 79 | you your Github username and password. You should see something like: 80 | 81 | ``` 82 | Enumerating objects: 4, done. 83 | Counting objects: 100% (4/4), done. 84 | Delta compression using up to 2 threads 85 | Compressing objects: 100% (3/3), done. 86 | Writing objects: 100% (3/3), 389 bytes | 389.00 KiB/s, done. 87 | Total 3 (delta 0), reused 0 (delta 0) 88 | To https://github.com/Resistor52/tech-tue-cloud-workstation-0015.git 89 | b2a5237..74ac2b7 main -> main 90 | ``` 91 | 92 | 10. Lastly, switch to the browser tab that has your Github repository loaded and 93 | hit refresh. You should see the terraform.tfvars file along with your commit message. 94 | 95 | **[Watch the Video](https://youtu.be/wrTooPCBXsk)** 96 | -------------------------------------------------------------------------------- /workbook/Step_08.md: -------------------------------------------------------------------------------- 1 | # Step 8 - main.tf - Add Variables, Provider & Data Blocks 2 | 3 | In this step, we will create a new file called `main.tf` and add in the blocks for 4 | the variables, the aws provider, and the data for our script. 5 | 6 | 1. In the CloudShell, make sure you are in the directory of the local git repo. Run 7 | `cd ~/tech` and then press TAB, then ENTER. 8 | 2. Create the file by running `vim main.tf` 9 | 3. Enter insert mode by pressing the "i" key. 10 | 4. Now, lets add in the variable blocks. You will note that there is one block for 11 | each of the parameters set in the `terraform.tfvars` file. Copy and paste the 12 | following code into the terminal: 13 | 14 | ``` 15 | variable "aws_region" { 16 | description = "The AWS region to deploy the resources to" 17 | } 18 | 19 | variable "aws_creds_file" { 20 | description = "The full path to the .aws/credentials file" 21 | } 22 | 23 | variable "aws_profile" { 24 | description = "The profile in the credentials file to use" 25 | } 26 | 27 | variable "aws_pem" { 28 | description = "The PEM file to use for SSH. This is outputted with the IP for convenience" 29 | } 30 | 31 | ``` 32 | 33 | 5. Next, specify the provider, which in our case is "aws". Providers are a Terraform 34 | plugin to manage a specific infrastructure platform. [You can read all about providers 35 | in the Terraform documentation](https://www.terraform.io/docs/language/providers/index.html). 36 | As you can see below, some providers require configuration. Copy and paste the following block 37 | into your terminal to add it to the `main.tf` file: 38 | 39 | ``` 40 | provider "aws" { 41 | region = var.aws_region 42 | shared_credentials_file = var.aws_creds_file 43 | profile = var.aws_profile 44 | } 45 | 46 | ``` 47 | 48 | Did you notice how this block uses the three of the variables defined earlier? And 49 | that the variables are set by reading the `terraform.tfvars` file? 50 | 51 | **INFO:** According to the 52 | [Terraform documentation](https://www.terraform.io/docs/language/data-sources/index.html), 53 | _"Data sources allow data to be fetched or computed for use elsewhere in Terraform 54 | configuration. Use of data sources allows a Terraform configuration to make use 55 | of information defined outside of Terraform, or defined by another separate 56 | Terraform configuration."_ 57 | 58 | 59 | 6. Add the following data block to the bottom of the script: 60 | 61 | ``` 62 | data "http" "myip" { 63 | url = "https://api.ipify.org" 64 | } 65 | 66 | ``` 67 | 68 | This block creates a data structure that contains the public IP address of system 69 | that runs the Terraform script. This will be used to define a security group later 70 | in the code that follows. 71 | 72 | 7. Add a data block to determine the all of the AWS availability zones. This block 73 | can only be used with the aws provider: 74 | 75 | ``` 76 | data "aws_availability_zones" "all" {} 77 | 78 | ``` 79 | 80 | NOTE: Reference the relevant documentation for the above data block 81 | [here](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/availability_zones). 82 | 83 | 8. Add a data block to get the latest Ubuntu server AMI: 84 | 85 | ``` 86 | data "aws_ami" "ubuntu" { 87 | most_recent = true 88 | 89 | filter { 90 | name = "name" 91 | values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"] 92 | } 93 | 94 | filter { 95 | name = "virtualization-type" 96 | values = ["hvm"] 97 | } 98 | 99 | owners = ["099720109477"] 100 | } 101 | 102 | ``` 103 | 104 | The above data block applies some filters to select the desired image. Note that the 105 | "owners" attribute specifies the AWS account (controlled by Amazon) that publishes 106 | the AMI. [Here](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ami) 107 | is the reference for the "aws_ami" data source. 108 | 109 | 9. Finally, close the Vim editing session by hitting ESC and typing `:wq` 110 | 111 | 112 | **[Watch the Video](https://youtu.be/DrRZX1FfUdY)** 113 | -------------------------------------------------------------------------------- /workbook/Step_09.md: -------------------------------------------------------------------------------- 1 | # Step 9 - Terraform Init and .gitignore 2 | 3 | 1. In the CloudShell, confirm you are in the "tech-tuesday-cloud-workstation" 4 | directory. Then type `ls -als` to see all files, including the hidden ones. You 5 | should see something like: 6 | 7 | ``` 8 | 4 drwxrwxr-x 4 cloudshell-user cloudshell-user 4096 Mar 2 21:09 . 9 | 4 drwxr-xr-x 7 cloudshell-user cloudshell-user 4096 Mar 2 21:09 .. 10 | 4 drwxrwxr-x 8 cloudshell-user cloudshell-user 4096 Mar 2 21:10 .git 11 | 4 -rw-rw-r-- 1 cloudshell-user cloudshell-user 901 Mar 2 21:05 main.tf 12 | 4 -rw-rw-r-- 1 cloudshell-user cloudshell-user 86 Mar 2 20:59 README.md 13 | 4 -rw-rw-r-- 1 cloudshell-user cloudshell-user 128 Mar 2 21:00 terraform.tfvars 14 | ``` 15 | 16 | 2. Now run `terraform init` and observer the output. It should look like this: 17 | 18 | ``` 19 | 20 | Initializing the backend... 21 | 22 | Initializing provider plugins... 23 | - Finding latest version of hashicorp/http... 24 | - Finding latest version of hashicorp/aws... 25 | - Installing hashicorp/http v2.0.0... 26 | - Installed hashicorp/http v2.0.0 (signed by HashiCorp) 27 | - Installing hashicorp/aws v3.26.0... 28 | - Installed hashicorp/aws v3.26.0 (signed by HashiCorp) 29 | 30 | Terraform has created a lock file .terraform.lock.hcl to record the provider 31 | selections it made above. Include this file in your version control repository 32 | so that Terraform can guarantee to make the same selections by default when 33 | you run "terraform init" in the future. 34 | 35 | Terraform has been successfully initialized! 36 | 37 | You may now begin working with Terraform. Try running "terraform plan" to see 38 | any changes that are required for your infrastructure. All Terraform commands 39 | should now work. 40 | 41 | If you ever set or change modules or backend configuration for Terraform, 42 | rerun this command to reinitialize your working directory. If you forget, other 43 | commands will detect it and remind you to do so if necessary. 44 | 45 | ``` 46 | 47 | **NOTE:** If the output does not look like this, check both `terraform.tfvars` and 48 | `main.tf` for typos and rerun `terraform init`. 49 | 50 | 3. Next, type `ls -als` to see if any new files were added. You should notice two 51 | new additions: 52 | 53 | ``` 54 | 4 drwxr-xr-x 3 cloudshell-user cloudshell-user 4096 Mar 2 21:07 .terraform 55 | 4 -rw-r--r-- 1 cloudshell-user cloudshell-user 1897 Mar 2 21:07 .terraform.lock.hcl 56 | 57 | ``` 58 | 59 | **NOTE:** Recall that the period in front of a file name or directory makes it hidden 60 | 61 | **INFO:** Read more about the `terraform init` command 62 | [here](https://www.terraform.io/docs/cli/commands/init.html) 63 | 64 | 4. Now run `terraform plan` as suggested in the output of the previous terraform 65 | command. It reads: 66 | 67 | ``` 68 | No changes. Infrastructure is up-to-date. 69 | 70 | This means that Terraform did not detect any differences between your 71 | configuration and real physical resources that exist. As a result, no 72 | actions need to be performed. 73 | 74 | ``` 75 | 76 | **INFO:** Read more about the `terraform plan` command 77 | [here](https://www.terraform.io/docs/cli/commands/plan.html) 78 | 79 | That really should not be a surprise, because we have not added any resources to 80 | deploy to the script yet. We will do that the upcoming steps. 81 | 82 | 5. First, let's do a `git status`. This command reports three untracked items: 83 | * terraform.lock.hcl 84 | * .terraform/ 85 | * main.tf 86 | 87 | 6. Add the two files: 88 | 89 | ``` 90 | git add main.tf 91 | git add .terraform.lock.hcl 92 | 93 | ``` 94 | 95 | 7. Rather than adding the `.terraform/` directory to our git repo, we will exclude 96 | it using a `.gitignore` file. Type `vim .gitignore` to create the file and start to 97 | edit it. Press "i" to enter insert mode, type `.terraform/` and then hit ESC to exit 98 | insert mode. Then type ":wq" to exit vim. 99 | 100 | 8. Now, if you run `git status` you will see our two tracked files, but not the 101 | `.terraform/` folder. However, now there is the `.gitignore` showing as an untracked 102 | file. Add it using `git add .gitignore`. 103 | 104 | **INFO:** Read more about `.gitignore` [here](https://git-scm.com/docs/gitignore). 105 | 106 | 9. Do one last `git status` to see that all looks well. Now we can commit the changes. 107 | Run `git commit -m "partial of main.tf"` 108 | 109 | 10. Push the code to Github, as before using `git push origin main` providing your 110 | credentials as prompted. 111 | 112 | 11. Lastly, check out the updates on Github by selecting the appropriate browser tab 113 | and refreshing the page. 114 | 115 | 116 | **[Watch the Video](https://youtu.be/CW9YNyfPJx8)** 117 | -------------------------------------------------------------------------------- /workbook/Step_10.md: -------------------------------------------------------------------------------- 1 | # Step 10 - main.tf - Add VPC resources 2 | 3 | In this step, we will be adding resources to the `main.tf` to create a VPC. We will 4 | test it by having Terraform deploy the resources and then we will verify the deployed 5 | resources via the AWS Web Console. 6 | 7 | 1. In the CloudShell, make sure you are in the directory of the local git repo. Run 8 | `cd ~/tech` and then press TAB, then ENTER. 9 | 2. Open the `main.tf` file by running `vim main.tf` 10 | 3. Enter insert mode by pressing the "i" key and cursor down to the bottom of the file. 11 | 4. Next copy and paste the following lines into the terminal: 12 | 13 | ``` 14 | resource "aws_vpc" "work-vpc" { 15 | cidr_block = "10.0.0.0/16" 16 | enable_dns_support = "true" #gives you an internal domain name 17 | enable_dns_hostnames = "true" #gives you an internal host name 18 | enable_classiclink = "false" 19 | instance_tenancy = "default" 20 | 21 | tags = { 22 | Name = "work-vpc" 23 | } 24 | } 25 | 26 | resource "aws_subnet" "work-subnet" { 27 | vpc_id = aws_vpc.work-vpc.id 28 | cidr_block = "10.0.1.0/24" 29 | map_public_ip_on_launch = "true" #it makes this a public subnet 30 | availability_zone = "${var.aws_region}a" 31 | tags = { 32 | Name = "work-subnet" 33 | } 34 | } 35 | 36 | resource "aws_internet_gateway" "work-igw" { 37 | vpc_id = aws_vpc.work-vpc.id 38 | tags = { 39 | Name = "work-igw" 40 | } 41 | } 42 | 43 | resource "aws_route_table" "work-rtble" { 44 | vpc_id = aws_vpc.work-vpc.id 45 | 46 | route { 47 | //associated subnet can reach everywhere 48 | cidr_block = "0.0.0.0/0" 49 | //CRT uses this IGW to reach internet 50 | gateway_id = aws_internet_gateway.work-igw.id 51 | } 52 | 53 | tags = { 54 | Name = "work-rtble" 55 | } 56 | } 57 | 58 | resource "aws_route_table_association" "work-rta" { 59 | subnet_id = aws_subnet.work-subnet.id 60 | route_table_id = aws_route_table.work-rtble.id 61 | } 62 | 63 | ``` 64 | 65 | 5. Close the Vim editing session by hitting ESC and typing `:wq` 66 | 67 | What resources does this code snippet create? In order, it creates: 68 | * A VPC named "work-vpc" with a network address of 10.0.0.0/16 69 | * A Subnet named "work-subnet" that is associated with the new VPC 70 | * An Internet Gateway that is associated with the new VPC named "work-igw" 71 | * A Route Table named "work-rtble" with a route to the Internet Gateway 72 | * A Route Table Association to link the new Route Table to the new Subnet 73 | 74 | **INFO:** To locate the documentation on any of these resource, perform an Internet 75 | search. For example, a search for "terraform resource aws_vpc" results in 76 | https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc. 77 | 78 | 6. Next, run `terraform validate` to make sure that our edits are good. The output 79 | should read "Success! The configuration is valid." 80 | 81 | **INFO:** Read more about the `terraform validate` command 82 | [here](https://www.terraform.io/docs/cli/commands/validate.html) 83 | 84 | **NOTE:** We don't need to initialize the directory again by running `terraform init` 85 | again, but it won't harm anything because the command is 86 | [idempotent](https://stackoverflow.com/questions/1077412/what-is-an-idempotent-operation). 87 | When we ran init last time, it also validated the Terraform code while it was at it. 88 | 89 | 7. Great, let's deploy these resources to AWS. Run `terraform apply` and then type 90 | "yes" when prompted. If all goes well, you should see several messages with the last 91 | line in green text that reads: 92 | ``` 93 | Apply complete! Resources: 5 added, 0 changed, 0 destroyed. 94 | ``` 95 | 96 | 8. Now, navigate to the AWS VPC Service in the Web Console and look for the 97 | following resources: 98 | > - [ ] A VPC named "work-vpc" with a network address of 10.0.0.0/16 99 | > - [ ] A Subnet named "work-subnet" that is associated with the new VPC 100 | > - [ ] An Internet Gateway that is associated with the new VPC named "work-igw" 101 | > - [ ] A Route Table named "work-rtble" with a route to the Internet Gateway 102 | > - [ ] A Route Table Association to link the new Route Table to the new Subnet 103 | 104 | 9. Now run `git status` and note that besides the `main.tf` file we expected to 105 | show as modified, there are two new files: 106 | 107 | * terraform.tfstate 108 | * terraform.tfstate.backup 109 | 110 | 10. Add these two files to .gitignore HINT: vim .gitignore 111 | 112 | **NOTE:** The tfstate files keep track of what Terraform has deployed to _your_ 113 | environment, and although they should be backed up in production, they do not 114 | belong in a code repository, lest they confuse others who use your code. 115 | 116 | 11. Run `git status` again. Type `git add *` and then `git status`. Hmm, notice 117 | that the "*" didn't pick up the `.gitignore` file because it is hidden. So add it 118 | by typing `git add .gitignore` 119 | 120 | 12. Ok, time to commit it. Type `git commit -m "added VPC to main.tf"` 121 | 122 | 13. Push it to Github: `git push origin main` 123 | 124 | 14. Check it out on your browser tab that has your Github repo open. Notice the 125 | change once you hit refresh. 126 | 127 | 15. Just for fun, lets deprovision these resources. Run `terraform destroy` and 128 | then type "yes" when prompted. Look again in the Web Console. The resources are 129 | no longer present. 130 | 131 | **[Watch the Video](https://youtu.be/e_SpOR4az2M)** 132 | -------------------------------------------------------------------------------- /workbook/Step_11.md: -------------------------------------------------------------------------------- 1 | # Step 11 - main.tf - Add Security Group 2 | 3 | 1. As before, edit `main.tf` and add in the following lines to the bottom of the 4 | file: 5 | 6 | ``` 7 | resource "aws_security_group" "work-sg" { 8 | name = "work-sg" 9 | vpc_id = aws_vpc.work-vpc.id 10 | } 11 | 12 | resource "aws_security_group_rule" "sg-ssh" { 13 | type = "ingress" 14 | from_port = 22 15 | to_port = 22 16 | protocol = "tcp" 17 | cidr_blocks = ["${chomp(data.http.myip.body)}/32"] 18 | security_group_id = aws_security_group.work-sg.id 19 | } 20 | 21 | resource "aws_security_group_rule" "sg-rdp" { 22 | type = "ingress" 23 | from_port = 3389 24 | to_port = 3389 25 | protocol = "tcp" 26 | cidr_blocks = ["${chomp(data.http.myip.body)}/32"] 27 | security_group_id = aws_security_group.work-sg.id 28 | } 29 | 30 | resource "aws_security_group_rule" "allow_all" { 31 | type = "egress" 32 | from_port = 0 33 | to_port = 0 34 | protocol = "-1" 35 | cidr_blocks = ["0.0.0.0/0"] 36 | security_group_id = aws_security_group.work-sg.id 37 | } 38 | 39 | 40 | ``` 41 | 42 | Note how the Security Group is associated with the VPC and the Security Group Rules 43 | are associated with the Security Group. Notice anything interesting about the cidr_block 44 | attribute on the first two Security Group Rules? 45 | [Chomp](https://www.terraform.io/docs/language/functions/chomp.html) is used to remove 46 | the newline characters of the string returned by the body property of the "myip" object. 47 | Recall defining that block at the beggining of our script? 48 | 49 | 2. Run `terraform validate` to ensure that the code looks good. 50 | 3. Run `terraform apply` to provision the resources to AWS. This time we will not 51 | tear down the resources, so DO NOT run `terraform destroy` 52 | 4. Visually verify the resources via your AWS Web Console. 53 | 5. Type `git add *` and then `git commit -m "Added Security Group to main.tf"` 54 | 6. Push it to Github: `git push origin main` 55 | 56 | **INFO:** Learn about other Terraform functions, like chomp, 57 | [here](https://www.terraform.io/docs/language/functions/index.html) 58 | 59 | **[Watch the Video](https://youtu.be/Kr_YAMbrnWU)** 60 | -------------------------------------------------------------------------------- /workbook/Step_12.md: -------------------------------------------------------------------------------- 1 | # Step 12 - main.tf - Add Ubuntu Instance 2 | 3 | 1. As before, edit `main.tf` and add in the following lines to the bottom of the 4 | file: 5 | 6 | ``` 7 | resource "aws_instance" "workstation1" { 8 | ami = data.aws_ami.ubuntu.id 9 | instance_type = "t3a.xlarge" 10 | key_name = var.ssh_key 11 | vpc_security_group_ids = [aws_security_group.work-sg.id] 12 | subnet_id = aws_subnet.work-subnet.id 13 | user_data = file("workstation1.sh") 14 | tags = { 15 | Name = "workstation1" 16 | } 17 | } 18 | 19 | output "ssh_connection_string" { 20 | value = "ssh -i ${var.aws_pem} ubuntu@${aws_instance.workstation1.public_ip}" 21 | } 22 | 23 | output "RDP_address" { 24 | value = aws_instance.workstation1.public_ip 25 | } 26 | 27 | output "RDP_UserName" { 28 | value = "student" 29 | } 30 | 31 | output "RDP_Password" { 32 | value = "ChangeMe" 33 | } 34 | 35 | ``` 36 | 37 | **IMPORTANT:** Did you notice that this script will launch a t3.xlarge? This will cost 38 | $0.1664 per Hour according to the 39 | [EC2 On Demand Pricing Page](https://aws.amazon.com/ec2/pricing/on-demand/). Feel 40 | Free to change it to another [valid instance type](https://aws.amazon.com/ec2/instance-types/). 41 | 42 | **NOTE:** We added a new block type, an output block. This defines information to be 43 | displayed to the terminal when the script runs. It can display: 44 | * the value of strings, such as the RDP_UserName; 45 | * object properties, such as RDP_address; and even 46 | * strings concatenated with data, as in the ssh_connection_string 47 | 48 | **INFO:** Read more about the terraform output block 49 | [here](https://www.terraform.io/docs/language/values/outputs.html) 50 | 51 | 2. Save the file and exit vim. Run `terraform validate` to ensure that the code 52 | looks good. Does it? 53 | 54 | No, we get the following results: 55 | 56 | ``` 57 | Error: Reference to undeclared input variable 58 | 59 | on main.tf line 130, in resource "aws_instance" "workstation1": 60 | 130: key_name = var.ssh_key 61 | 62 | An input variable with the name "ssh_key" has not been declared. This variable 63 | can be declared with a variable "ssh_key" {} block. 64 | ``` 65 | 66 | It looks like we just added a variable `var.ssh_key` that was not defined when we 67 | declared the others. Let's add it to the code. 68 | 69 | 3. Open `main.tf` with Vim and add the following line after the other variables: 70 | 71 | ``` 72 | variable "ssh_key" { 73 | description = "The AWS Key Pair to use for SSH" 74 | } 75 | ``` 76 | 77 | 4. Let's add it to the bottom of the `terraform.tfvars` file while we are at it. 78 | 79 | ``` 80 | ssh_key = "cloud_workstation" 81 | ``` 82 | 83 | 5. Run `terraform validate` again. You should see another error: 84 | 85 | ``` 86 | Error: Invalid function argument 87 | 88 | on main.tf line 137, in resource "aws_instance" "workstation1": 89 | 137: user_data = file("workstation1.sh") 90 | 91 | Invalid value for "path" parameter: no file exists at workstation1.sh; this 92 | function works only with files that are distributed as part of the 93 | configuration source code, so if this file will be created by a resource in 94 | this configuration you must instead obtain this result from an attribute of 95 | that resource. 96 | ``` 97 | 98 | The terraform script is also expecting user data to pass to the EC2 instance in 99 | the form of a script named `workstation1.sh` but this script does not exist yet. 100 | Let's create it. 101 | 102 | 6. Run the following commands to make a placeholder script that we can 103 | improve in subsequent steps: 104 | 105 | ``` 106 | echo '#!/bin/bash' > workstation1.sh 107 | echo 'date' >> workstation1.sh 108 | 109 | ``` 110 | 111 | 7. OPTIONAL: Examine the file you just created. Type `cat workstation1.sh` 112 | 8. Run `terraform validate` one more. Now you should see the success message. 113 | 9. Run `terraform apply` to provision the resources to AWS. 114 | 115 | **NOTE:** Did you notice that terraform only needed to deploy one additional resource 116 | because the other ones were already deployed in the previous step? Vey cool! 117 | 118 | 10. Visually verify the EC2 Instance via your AWS Web Console. 119 | 11. Let's log into it. Copy the contents of the ssh_connection_string value (excluding 120 | the quotation marks). Then click on the "Actions" drop down field, in the upper right 121 | corner of the CloudShell. Select "New tab." Paste the clipboard contents into the 122 | terminal and hit ENTER. Select "yes" when prompted if you want to connect. 123 | 12. Type "exit" in the SSH session terminal and close that CloudShell tab. 124 | **Imortant:** Leave the other CloudShell tab open! 125 | 126 | **NOTE:** You will not be able to RDP yet. We add that capability in the next step. 127 | 128 | 13. Type `git add *` and then `git commit -m "Added EC2 Instance to main.tf"` 129 | 14. Push it to Github: `git push origin main` 130 | 15. Check it out on Github by refreshing that browser tab. 131 | 132 | 133 | **[Watch the Video](https://youtu.be/vxrtcXOcPBE)** 134 | -------------------------------------------------------------------------------- /workbook/Step_13.md: -------------------------------------------------------------------------------- 1 | # Step 13 - Customize the Ubuntu Instance 2 | 3 | Now that we have an EC2 Instance that can be deployed via Terraform, lets customize 4 | it. We will do so by making modifications to the `workstation1.sh` script that is 5 | passed to the Ubuntu instance as it boots. But first, let explore the 'user data' 6 | capability. 7 | 8 | ## Explore User Data (OPTIONAL) 9 | According to the [cloud-init Documentation](https://cloudinit.readthedocs.io/en/latest/), 10 | _Cloud-init is the industry standard multi-distribution method for cross-platform cloud 11 | instance initialization. It is supported across all major public cloud providers, 12 | provisioning systems for private cloud infrastructure, and bare-metal installations._ 13 | User data is passed to the instance at the time of launch via the AWS API. In our case 14 | Terraform calls the AWS API. The 15 | [Amazon EC2 User Guide for Linux Instances](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/user-data.html) 16 | details how this in implemented in AWS. 17 | 18 | Now that we have launched an Ubuntu instance, let's poke around and see what we can learn. 19 | 20 | 1. Make sure you are in the project directory of CloudShell (Hint: `cd ~/tech` TAB) 21 | 2. Type `clear` to clear the terminal. 22 | 3. Type `terraform output` to see the output displayed the last time Terraform ran. 23 | 24 | **INFO:** Learn more about the Terraform output command 25 | [here](https://www.terraform.io/docs/cli/commands/output.html) 26 | 27 | 4. Open a new CloudShell tab and SSH into the instance as performed in the last lab. 28 | Use the displayed ssh_connection_string. 29 | 5. At the SSH prompt, run `sudo cat /var/lib/cloud/instance/user-data.txt`. Look familiar? 30 | This is where the user-data gets stored when it is passed to the system. 31 | 6. There is also a trick to fetch the user data script from the 32 | [EC2 Metadataservice](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html) 33 | Run this rather long command from within the SSH session: 34 | 35 | ``` 36 | TOKEN=`curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600"` && curl -H "X-aws-ec2-metadata-token: $TOKEN" -v http://169.254.169.254/latest/user-data 37 | ``` 38 | 39 | 7. Exit the SSH session and close the CloudShell tab. 40 | 41 | ## Enhance workstation1.sh 42 | 43 | 1. Let's improve the `workstation.sh` script. Open it in Vim, but do not enter insert 44 | mode. Curser to the last line of the file and then press "dd" to delete the entire line. 45 | 2. Now enter insert mode (press "i") and cursor to the bottom of the file. Copy and 46 | paste the following code and save it: 47 | 48 | ``` 49 | /bin/echo "Script Start Time: "$(/usr/bin/date) > /tmp/first-boot.log 50 | /bin/echo "*****UPDATE*****" >> /tmp/first-boot.log 51 | /usr/bin/apt update -y >> /tmp/first-boot.log 2>&1 52 | /bin/echo "*****UPGRADE*****" >> /tmp/first-boot.log 53 | DEBIAN_FRONTEND=noninteractive /usr/bin/apt upgrade -y >> /tmp/first-boot.log 2>&1 54 | /bin/echo "***** INSTALL MATE 1 *****" >> /tmp/first-boot.log 55 | /usr/bin/apt-get install -y --no-install-recommends ubuntu-mate-core >> /tmp/first-boot.log 2>&1 56 | /bin/echo "***** INSTALL MATE 2 *****" >> /tmp/first-boot.log 57 | /usr/bin/apt-get install -y --no-install-recommends ubuntu-mate-desktop >> /tmp/first-boot.log 2>&1 58 | /bin/echo "***** INSTALL MATE 3 *****" >> /tmp/first-boot.log 59 | /usr/bin/apt-get install -y mate-core >> /tmp/first-boot.log 2>&1 60 | /bin/echo "***** INSTALL MATE 4 *****" >> /tmp/first-boot.log 61 | /usr/bin/apt-get install -y mate-desktop-environment >> /tmp/first-boot.log 2>&1 62 | /bin/echo "***** INSTALL MATE 5 *****" >> /tmp/first-boot.log 63 | /usr/bin/apt-get install -y mate-notification-daemon >> /tmp/first-boot.log 2>&1 64 | /bin/echo "***** INSTALL xrdp *****" >> /tmp/first-boot.log 65 | /usr/bin/apt-get install -y xrdp >> /tmp/first-boot.log 2>&1 66 | /usr/bin/systemctl enable xrdp >> /tmp/first-boot.log 2>&1 67 | /bin/echo "***** Fix Broken Packages *****" >> /tmp/first-boot.log 68 | /usr/bin/apt --fix-broken install -y >> /tmp/first-boot.log 2>&1 69 | /bin/echo "***** INSTALL Firefox *****" >> /tmp/first-boot.log 70 | /usr/bin/apt-get install -y firefox >> /tmp/first-boot.log 2>&1 71 | /bin/echo "***** INSTALL chrome *****" >> /tmp/first-boot.log 72 | /usr/bin/apt-get install -y gdebi-core >> /tmp/first-boot.log 2>&1 73 | /usr/bin/wget -q https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb >> /tmp/first-boot.log 2>&1 74 | /usr/bin/gdebi --n --q google-chrome-stable_current_amd64.deb >> /tmp/first-boot.log 2>&1 75 | /bin/echo "***** INSTALL Python *****" >> /tmp/first-boot.log 76 | /usr/bin/apt-get install -y python >> /tmp/first-boot.log 2>&1 77 | /usr/bin/apt-get install -y python3-pip >> /tmp/first-boot.log 2>&1 78 | /bin/echo "***** Create New User *****" >> /tmp/first-boot.log 79 | /usr/sbin/useradd -m student >> /tmp/first-boot.log 2>&1 80 | /usr/sbin/usermod -aG sudo student 81 | /usr/bin/mkdir /home/student/Desktop >> /tmp/first-boot.log 2>&1 82 | /usr/bin/chown student:student /home/student/Desktop >> /tmp/first-boot.log 2>&1 83 | /usr/bin/ls -als /home/student/Desktop >> /tmp/first-boot.log 2>&1 84 | /bin/echo 'student:ChangeMe' | /usr/sbin/chpasswd 85 | /bin/echo "***** Create Firefox Launcher *****" >> /tmp/first-boot.log 86 | /usr/bin/cat << EOF > /home/student/Desktop/Firefox.desktop 87 | #!/usr/bin/env xdg-open 88 | [Desktop Entry] 89 | Version=1.0 90 | Type=Application 91 | Terminal=false 92 | Icon=firefox 93 | Icon[C]=firefox 94 | Name[C]=Firefox 95 | Exec=firefox 96 | Name=Firefox 97 | EOF 98 | /usr/bin/cat /home/student/Desktop/Firefox.desktop >> /tmp/first-boot.log 99 | /usr/bin/chown student:student /home/student/Desktop/Firefox.desktop >> /tmp/first-boot.log 2>&1 100 | /usr/bin/chmod 775 /home/student/Desktop/Firefox.desktop >> /tmp/first-boot.log 2>&1 101 | /bin/echo "***** Create Firefox Launcher *****" >> /tmp/first-boot.log 102 | /usr/bin/cat << EOF > /home/student/Desktop/Chrome.desktop 103 | #!/usr/bin/env xdg-open 104 | [Desktop Entry] 105 | Version=1.0 106 | Type=Application 107 | Terminal=false 108 | Icon=google-chrome 109 | Icon[C]=google-chrome 110 | Name[C]=Chrome 111 | Exec=google-chrome 112 | Name=Chrome 113 | EOF 114 | /usr/bin/cat /home/student/Desktop/Chrome.desktop >> /tmp/first-boot.log 115 | /usr/bin/chown student:student /home/student/Desktop/Chrome.desktop >> /tmp/first-boot.log 2>&1 116 | /usr/bin/chmod 775 /home/student/Desktop/Chrome.desktop >> /tmp/first-boot.log 2>&1 117 | /bin/echo "***** Create Terminal Launcher *****" >> /tmp/first-boot.log 118 | /usr/bin/cat << EOF > /home/student/Desktop/Terminal.desktop 119 | #!/usr/bin/env xdg-open 120 | [Desktop Entry] 121 | Version=1.0 122 | Type=Application 123 | Terminal=true 124 | Icon=mate-panel-launcher 125 | Icon[C]=mate-panel-launcher 126 | Name[C]=Terminal 127 | Exec=bash 128 | Name=Terminal 129 | EOF 130 | /usr/bin/cat /home/student/Desktop/Terminal.desktop >> /tmp/first-boot.log 131 | /usr/bin/chown student:student /home/student/Desktop/Terminal.desktop >> /tmp/first-boot.log 2>&1 132 | /usr/bin/chmod 775 /home/student/Desktop/Terminal.desktop >> /tmp/first-boot.log 2>&1 133 | /bin/echo "*****DONE*****" >> /tmp/first-boot.log 134 | /bin/echo "Script Stop Time: "$(/usr/bin/date) >> /tmp/first-boot.log 135 | /usr/bin/wall "NOTICE: First Boot Setup Has Completed" 136 | 137 | 138 | ``` 139 | 140 | **NOTE:** Each line redirects the output into a custom log file (`/tmp/first-boot.log`) 141 | for troubleshooting purposes, but this is not strictly necessary, just a nice to have. 142 | There are more elegant ways to achieve this, but this is quick and dirty. 143 | 144 | **TROUBLESHOOTING TIP:** Make sure the first line of `workstation1.sh` still has 145 | `#!/bin/bash` 146 | 147 | 3. Run `terraform validate` and then `terraform apply` to provision the change to AWS. 148 | 4. Once Terraform has finished executing, note the IP Address. Is it the same as the 149 | one you just SSHed into? Nope, because Terraform had to destroy the old instance and 150 | launch a new one with the new user data. 151 | 5. Open a new CloudShell tab and SSH into the instance as performed earlier. Use the 152 | displayed ssh_connection_string. 153 | 6. In the SSH session, run `tail -f /tmp/first-boot.log` to monitor the progress of the 154 | setup script. If the script has not finished before you SSH in, you should get a message that reads 155 | "NOTICE: First Boot Setup Has Completed" when it does finish. 156 | 7. OPTIONAL: To review the log, use `less /tmp/first-boot.log` and press "q" to quit the 157 | less pager. 158 | 159 | ## RDP Into Cloud Workstation 160 | 1. After confirming that the start-up sript has completed, we will RDP into our cloud 161 | workstation. On a Windows system, click the Windows Key and then type "mstsc" to 162 | launch the **M**icro**s**oft **t**erminal **s**ervices **c**lient 163 | (RDP client). Macintosh users will need to have set up the MacOs client, per 164 | [these instrictions](https://docs.microsoft.com/en-us/windows-server/remote/remote-desktop-services/clients/remote-desktop-mac). Enter the RDP_address (without quotes) as displayed in the CloudShell. 165 | Click "Connect," and try to connect. 166 | 167 | We cannot. Why? Think about our security group rules. We are allowing port 3389 in from only the 168 | IP address from where the Terraform script is run. Oops. Let's add an additional rule and variable 169 | for the rule. 170 | 171 | 2. Open a new tab on your web browser and navigate to https://api.ipify.org and copy 172 | the IP address it displays. This is your public IP address. 173 | 174 | 3. Run `vim terraform.tfvars` enter insert mode, move to the bottom of the file and 175 | add `alt_rdp_source_ip = "127.0.0.1/32"` But be sure to replace `127.0.0.1` with the address 176 | obtained in the previous step. 177 | 178 | 4. Run `vim main.tf` and add the following line to the end of the variable blocks: 179 | ``` 180 | variable "alt_rdp_source_ip" { 181 | description = "An additional IP address from which to RDP" 182 | } 183 | 184 | ``` 185 | 186 | 5. While still in vim insert mode, add the following block just below the block for the 187 | "sg-rdp" Security Group Rule: 188 | 189 | ``` 190 | resource "aws_security_group_rule" "sg-rdp2" { 191 | type = "ingress" 192 | from_port = 3389 193 | to_port = 3389 194 | protocol = "tcp" 195 | cidr_blocks = [var.alt_rdp_source_ip] 196 | security_group_id = aws_security_group.work-sg.id 197 | } 198 | 199 | ``` 200 | 6. Save your changes and then run `terraform validate` 201 | 7. Deploy the changes using `terraform apply` 202 | 8. This time Terraform did not need to destroy and then relaunch another virtual 203 | machine. Once the script has completed, you will now be able to RDP using the IP 204 | address and credentials output by Terraform. Try it out. 205 | 10. Type `git add *` and then `git commit -m "Added content to workstation1.sh"` 206 | 11. Push it to Github: `git push origin main` 207 | 12. Check it out on Github by refreshing that browser tab. 208 | 209 | 210 | **[Watch the Video](https://youtu.be/9JMb2ir5jC0)** 211 | -------------------------------------------------------------------------------- /workbook/Step_14.md: -------------------------------------------------------------------------------- 1 | # Step 14 - Finish the Documentation for the Project 2 | 3 | No project is complete without the documentation. In this step, we will create a README 4 | to inform others how to use our Terraform script. 5 | 6 | 1. confirm you are in the "tech-tuesday-cloud-workstation" directory and then copy 7 | a template for the README.md from the Internet: 8 | 9 | ``` 10 | wget https://raw.githubusercontent.com/Resistor52/terraform-cloud-workstation/main/README-template.md -O README.md 11 | ``` 12 | 13 | 2. Take a look at the top of the new README.md file using `head -n19 README.md` and you 14 | will see lines that contain the following: 15 | 16 | ``` 17 | git clone XXXXXXXXXX 18 | ``` 19 | and then 20 | 21 | ``` 22 | cd ZZZZZZZZZZ 23 | ``` 24 | 25 | 3. Next, we will use some command line kung-fu to modify these lines to your git repo name: 26 | 27 | ``` 28 | REPO=$(grep url .git/config | cut -d'=' -f2) 29 | sed -i "s|XXXXXXXXXX|$REPO|" README.md 30 | DIR=$(pwd | cut -d'/' -f4) 31 | sed -i "s|ZZZZZZZZZZ|$DIR|" README.md 32 | ``` 33 | 34 | 4. Run the head command to see the resultant changes: 35 | 36 | ``` 37 | head -n19 README.md 38 | ``` 39 | 40 | 5. Type `git add README*` and then `git commit -m "Updated README"` 41 | 6. Push it to Github: `git push origin main` 42 | 7. Check it out on Github by refreshing that browser tab. 43 | 44 | 45 | **[Watch the Video](https://youtu.be/CY5uZ2sbZI0)** 46 | -------------------------------------------------------------------------------- /workbook/Step_15.md: -------------------------------------------------------------------------------- 1 | # Step 15 - Teardown and Test 2 | 3 | The last step in the process is to tear down our Cloud Workstation, destroy our CloudShell 4 | environment and test everything. 5 | 6 | 1. Tear down the Cloud Workstation: 7 | 8 | ``` 9 | terraform destroy 10 | ``` 11 | 12 | 2. Click on the **Actions** dropdown field in the upper right corner of the CloudShell 13 | and select "Delete AWS CloudShell home directory" and then confirm the deletion. 14 | 15 | 3. When a new CloudShell environment is ready, install Terraform as per Step 1: 16 | 17 | ``` 18 | wget https://releases.hashicorp.com/terraform/0.14.7/terraform_0.14.7_linux_amd64.zip 19 | unzip terraform*.zip 20 | mkdir ~/bin 21 | mv terraform ~/bin/ 22 | rm terraform*.zip 23 | ``` 24 | 25 | 4. Navigate to the Github repository that you created during this workshop and follow 26 | the instructions in the DEPLOYMENT section. 27 | 28 | 5. Make a copy of the PEM file on CloudShell from the backup you made in Step 5. 29 | 30 | 6. Open a new CloudShell tab and SSH into the cloud workstation using this new PEM file. 31 | 32 | 7. When the boot up script has completed, RDP into the system and verify the presence of 33 | the expected functionality. 34 | 35 | 8. Destroy the cloud workstation when you are finished with it, using `terraform destroy` 36 | 37 | 38 | **[Watch the Video](https://youtu.be/CFBc_rCU2z0)** 39 | --------------------------------------------------------------------------------