├── .gitattributes ├── .gitignore ├── LICENSE ├── bastdeploy.cfg ├── bastion.tf ├── database.tf ├── diagram.png ├── diagram.svg ├── guacdeploy.cfg ├── guacsrv.tf ├── initdb.sql ├── loadbalancer.tf ├── network.tf ├── provider.tf ├── readme.md ├── terraform.tfvars.sample └── variables.tf /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | *.tfvars 3 | infra/.terraform/plugins/windows_amd64/lock.json 4 | *.exe 5 | *.tfstate 6 | *.backup 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /bastdeploy.cfg: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | 3 | # Bastion cloud config file 4 | 5 | output: 6 | init: 7 | output: "> /var/log/cloud-init.out" 8 | error: "> /var/log/cloud-init.err" 9 | config: "tee -a /var/log/cloud-config.log" 10 | final: 11 | - ">> /var/log/cloud-final.out" 12 | - "/var/log/cloud-final.err" 13 | 14 | 15 | 16 | package_update: true 17 | 18 | package_upgrade: true 19 | 20 | packages: 21 | - mysql-client 22 | 23 | runcmd: 24 | - wget https://raw.githubusercontent.com/aries-strato/guacamole-aws/master/initdb.sql 25 | - mysql -h${db_ip} -u${db_user} -p${db_password} guacamole_db < initdb.sql 26 | -------------------------------------------------------------------------------- /bastion.tf: -------------------------------------------------------------------------------- 1 | # Deploy bastion 2 | # The bastion also has to pull the db configuration script and run it the first time it's spawned. 3 | 4 | 5 | data "template_file" "bastdeploy"{ 6 | template = "${file("./bastdeploy.cfg")}" 7 | 8 | vars { 9 | db_ip = "${aws_db_instance.guacdb.address}" 10 | db_user = "${var.db_user}" 11 | db_password = "${var.db_password}" 12 | } 13 | } 14 | 15 | data "template_cloudinit_config" "bastdeploy_config" { 16 | gzip = false 17 | base64_encode = false 18 | 19 | part { 20 | filename = "bastdeploy.cfg" 21 | content_type = "text/cloud-config" 22 | content = "${data.template_file.bastdeploy.rendered}" 23 | } 24 | } 25 | 26 | # Bastion server 1 27 | # We'll add another one later for more redundancy 28 | 29 | resource "aws_instance" "bastion-server1" { 30 | ami = "${var.bastion_ami}" 31 | vpc_security_group_ids = ["${aws_security_group.bastion-sec.id}", "${aws_security_group.allout.id}"] 32 | instance_type = "${var.bastion_instance_type}" 33 | subnet_id = "${aws_subnet.pub_subnet1.id}" 34 | key_name = "${var.user_keyname}" 35 | 36 | tags { 37 | Name = "Guac Bastion 1" 38 | } 39 | depends_on = ["aws_db_instance.guacdb"] 40 | user_data = "${data.template_cloudinit_config.bastdeploy_config.rendered}" 41 | } 42 | 43 | # associate EIP with bastion 1 44 | resource "aws_eip" "bastion1" { 45 | instance = "${aws_instance.bastion-server1.id}" 46 | vpc = true 47 | } 48 | 49 | resource "aws_security_group" "bastion-sec" { 50 | name = "bastion-secgroup" 51 | vpc_id = "${aws_vpc.app_vpc.id}" 52 | 53 | #ssh from anywhere (unnecessary) 54 | ingress { 55 | from_port = 22 56 | to_port = 22 57 | protocol = "tcp" 58 | cidr_blocks = ["0.0.0.0/0"] 59 | } 60 | # ping access from anywhere 61 | ingress { 62 | from_port = 8 63 | to_port = 0 64 | protocol = "icmp" 65 | cidr_blocks = ["0.0.0.0/0"] 66 | } 67 | } 68 | 69 | #public access sg 70 | 71 | # allow all egress traffic 72 | resource "aws_security_group" "allout" { 73 | name = "allout-secgroup" 74 | vpc_id = "${aws_vpc.app_vpc.id}" 75 | 76 | egress { 77 | from_port = 0 78 | to_port = 0 79 | protocol = "-1" 80 | cidr_blocks = ["0.0.0.0/0"] 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /database.tf: -------------------------------------------------------------------------------- 1 | # Create db instance 2 | 3 | 4 | #make db subnet group 5 | resource "aws_db_subnet_group" "dbsubnet" { 6 | name = "main" 7 | subnet_ids = ["${aws_subnet.priv_subnet1.id}", "${aws_subnet.priv_subnet2.id}"] 8 | } 9 | 10 | #provision the database 11 | resource "aws_db_instance" "guacdb" { 12 | identifier = "guacdb" 13 | instance_class = "db.m4.large" 14 | allocated_storage = 50 15 | engine = "mysql" 16 | name = "guacamole_db" 17 | password = "${var.db_password}" 18 | username = "${var.db_user}" 19 | 20 | # If using Symphony, use 5.7.00, otherwise us AWS reccomended version 21 | #engine_version = "5.7.00" 22 | 23 | engine_version = "5.7.21" 24 | skip_final_snapshot = true 25 | db_subnet_group_name = "${aws_db_subnet_group.dbsubnet.name}" 26 | vpc_security_group_ids = ["${aws_security_group.db.id}"] 27 | 28 | } 29 | # Security group definition for database 30 | 31 | resource "aws_security_group" "db" { 32 | name = "db-secgroup" 33 | vpc_id = "${aws_vpc.app_vpc.id}" 34 | 35 | ingress { 36 | from_port = 3306 37 | to_port = 3306 38 | protocol = "tcp" 39 | cidr_blocks = ["192.168.0.0/16"] 40 | } 41 | 42 | egress { 43 | from_port = 0 44 | to_port = 0 45 | protocol = "-1" 46 | cidr_blocks = ["0.0.0.0/0"] 47 | } 48 | } -------------------------------------------------------------------------------- /diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ariesyous/guacamole-aws/b91e650b7e5a827b3eb57a70c0acfcea93fe05fb/diagram.png -------------------------------------------------------------------------------- /diagram.svg: -------------------------------------------------------------------------------- 1 | 2 |
Bastion Server

Bastion Server<br><br>
RDP/VNC/SSH
RDP/VNC/SSH
TCP 3306
TCP 3306
Guacamole Server
Guacamole Server
All External traffic from Guac server and VDI Desktops go through NAT GW
All External traffic from Guac server and VDI Desktops go through NAT GW
VDI Desktop (Linux)
VDI Desktop (Linux)
NAT Gateway
NAT Gateway
HTTPS 443
HTTPS 443
SSH 22
SSH 22
IGW
IGW
HTTP 8080
HTTP 8080
ALB

[Not supported by viewer]
Guacamole VPC
Guacamole VPC
MySQL RDS

MySQL RDS<br><br>
Internet
Internet
VDI User

VDI User<br><br>
-------------------------------------------------------------------------------- /guacdeploy.cfg: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | 3 | # Guacamole server cloud config 4 | 5 | output: 6 | init: 7 | output: "> /var/log/cloud-init.out" 8 | error: "> /var/log/cloud-init.err" 9 | config: "tee -a /var/log/cloud-config.log" 10 | final: 11 | - ">> /var/log/cloud-final.out" 12 | - "/var/log/cloud-final.err" 13 | 14 | package_update: true 15 | package_upgrade: true 16 | 17 | packages: 18 | - docker.io 19 | 20 | runcmd: 21 | - docker pull guacamole/guacamole 22 | - docker pull guacamole/guacd 23 | - docker run --name stratoguacd -d guacamole/guacd 24 | - docker run --name stratoguacsrv --link stratoguacd:guacd -e MYSQL_HOSTNAME=${db_ip} -e MYSQL_PORT=3306 -e MYSQL_DATABASE=guacamole_db -e MYSQL_USER=${db_user} -e MYSQL_PASSWORD=${db_password} -d -p 8080:8080 guacamole/guacamole 25 | 26 | -------------------------------------------------------------------------------- /guacsrv.tf: -------------------------------------------------------------------------------- 1 | # Guacamole server containing docker images. 2 | 3 | data "template_file" "guacdeploy"{ 4 | template = "${file("./guacdeploy.cfg")}" 5 | 6 | vars { 7 | db_ip = "${aws_db_instance.guacdb.address}" 8 | db_user = "${var.db_user}" 9 | db_password = "${var.db_password}" 10 | } 11 | } 12 | 13 | data "template_cloudinit_config" "guacdeploy_config" { 14 | gzip = false 15 | base64_encode = false 16 | 17 | part { 18 | filename = "guacconfig.cfg" 19 | content_type = "text/cloud-config" 20 | content = "${data.template_file.guacdeploy.rendered}" 21 | } 22 | } 23 | 24 | # Just going to deploy 1 guacamole server for now into one subnet, will add another soon 25 | 26 | resource "aws_instance" "guac-server1" { 27 | ami = "${var.guacsrv_ami}" 28 | vpc_security_group_ids = ["${aws_security_group.guac-sec.id}", "${aws_security_group.allout.id}"] 29 | instance_type = "${var.guacsrv_instance_type}" 30 | subnet_id = "${aws_subnet.priv_subnet1.id}" 31 | key_name = "${var.user_keyname}" 32 | tags { 33 | Name = "Guacamole Server 1" 34 | } 35 | # Needs the bastion server to exist since it runs the mysql init script before it can connect to the db 36 | depends_on = ["aws_instance.bastion-server1"] 37 | user_data = "${data.template_cloudinit_config.guacdeploy_config.rendered}" 38 | } 39 | 40 | # Security group definition 41 | resource "aws_security_group" "guac-sec" { 42 | name = "guacserver-secgroup" 43 | vpc_id = "${aws_vpc.app_vpc.id}" 44 | 45 | # Guac listens on 8080 46 | ingress { 47 | from_port = 8080 48 | to_port = 8080 49 | protocol = "tcp" 50 | cidr_blocks = ["192.168.0.0/16"] 51 | } 52 | # SSH from within VPC (bastion connectivity) 53 | ingress { 54 | from_port = 22 55 | to_port = 22 56 | protocol = "tcp" 57 | cidr_blocks = ["192.168.0.0/16"] 58 | } 59 | # ping access 60 | ingress { 61 | from_port = 8 62 | to_port = 0 63 | protocol = "icmp" 64 | cidr_blocks = ["192.168.0.0/16"] 65 | } 66 | } 67 | 68 | /* Commenting out because it already exists 69 | #public access sg 70 | 71 | # allow all egress traffic (needed for server to download packages) 72 | resource "aws_security_group" "allout" { 73 | name = "allout-secgroup" 74 | vpc_id = "${aws_vpc.app_vpc.id}" 75 | 76 | egress { 77 | from_port = 0 78 | to_port = 0 79 | protocol = "-1" 80 | cidr_blocks = ["0.0.0.0/0"] 81 | } 82 | } 83 | */ -------------------------------------------------------------------------------- /initdb.sql: -------------------------------------------------------------------------------- 1 | -- 2 | -- Licensed to the Apache Software Foundation (ASF) under one 3 | -- or more contributor license agreements. See the NOTICE file 4 | -- distributed with this work for additional information 5 | -- regarding copyright ownership. The ASF licenses this file 6 | -- to you under the Apache License, Version 2.0 (the 7 | -- "License"); you may not use this file except in compliance 8 | -- with the License. You may obtain a copy of the License at 9 | -- 10 | -- http://www.apache.org/licenses/LICENSE-2.0 11 | -- 12 | -- Unless required by applicable law or agreed to in writing, 13 | -- software distributed under the License is distributed on an 14 | -- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | -- KIND, either express or implied. See the License for the 16 | -- specific language governing permissions and limitations 17 | -- under the License. 18 | -- 19 | 20 | -- 21 | -- Table of connection groups. Each connection group has a name. 22 | -- 23 | 24 | CREATE TABLE `guacamole_connection_group` ( 25 | 26 | `connection_group_id` int(11) NOT NULL AUTO_INCREMENT, 27 | `parent_id` int(11), 28 | `connection_group_name` varchar(128) NOT NULL, 29 | `type` enum('ORGANIZATIONAL', 30 | 'BALANCING') NOT NULL DEFAULT 'ORGANIZATIONAL', 31 | 32 | -- Concurrency limits 33 | `max_connections` int(11), 34 | `max_connections_per_user` int(11), 35 | `enable_session_affinity` boolean NOT NULL DEFAULT 0, 36 | 37 | PRIMARY KEY (`connection_group_id`), 38 | UNIQUE KEY `connection_group_name_parent` (`connection_group_name`, `parent_id`), 39 | 40 | CONSTRAINT `guacamole_connection_group_ibfk_1` 41 | FOREIGN KEY (`parent_id`) 42 | REFERENCES `guacamole_connection_group` (`connection_group_id`) ON DELETE CASCADE 43 | 44 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 45 | 46 | -- 47 | -- Table of connections. Each connection has a name, protocol, and 48 | -- associated set of parameters. 49 | -- A connection may belong to a connection group. 50 | -- 51 | 52 | CREATE TABLE `guacamole_connection` ( 53 | 54 | `connection_id` int(11) NOT NULL AUTO_INCREMENT, 55 | `connection_name` varchar(128) NOT NULL, 56 | `parent_id` int(11), 57 | `protocol` varchar(32) NOT NULL, 58 | 59 | -- Guacamole proxy (guacd) overrides 60 | `proxy_port` integer, 61 | `proxy_hostname` varchar(512), 62 | `proxy_encryption_method` enum('NONE', 'SSL'), 63 | 64 | -- Concurrency limits 65 | `max_connections` int(11), 66 | `max_connections_per_user` int(11), 67 | 68 | -- Load-balancing behavior 69 | `connection_weight` int(11), 70 | `failover_only` boolean NOT NULL DEFAULT 0, 71 | 72 | PRIMARY KEY (`connection_id`), 73 | UNIQUE KEY `connection_name_parent` (`connection_name`, `parent_id`), 74 | 75 | CONSTRAINT `guacamole_connection_ibfk_1` 76 | FOREIGN KEY (`parent_id`) 77 | REFERENCES `guacamole_connection_group` (`connection_group_id`) ON DELETE CASCADE 78 | 79 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 80 | 81 | -- 82 | -- Table of users. Each user has a unique username and a hashed password 83 | -- with corresponding salt. Although the authentication system will always set 84 | -- salted passwords, other systems may set unsalted passwords by simply not 85 | -- providing the salt. 86 | -- 87 | 88 | CREATE TABLE `guacamole_user` ( 89 | 90 | `user_id` int(11) NOT NULL AUTO_INCREMENT, 91 | 92 | -- Username and optionally-salted password 93 | `username` varchar(128) NOT NULL, 94 | `password_hash` binary(32) NOT NULL, 95 | `password_salt` binary(32), 96 | `password_date` datetime NOT NULL, 97 | 98 | -- Account disabled/expired status 99 | `disabled` boolean NOT NULL DEFAULT 0, 100 | `expired` boolean NOT NULL DEFAULT 0, 101 | 102 | -- Time-based access restriction 103 | `access_window_start` TIME, 104 | `access_window_end` TIME, 105 | 106 | -- Date-based access restriction 107 | `valid_from` DATE, 108 | `valid_until` DATE, 109 | 110 | -- Timezone used for all date/time comparisons and interpretation 111 | `timezone` VARCHAR(64), 112 | 113 | -- Profile information 114 | `full_name` VARCHAR(256), 115 | `email_address` VARCHAR(256), 116 | `organization` VARCHAR(256), 117 | `organizational_role` VARCHAR(256), 118 | 119 | PRIMARY KEY (`user_id`), 120 | UNIQUE KEY `username` (`username`) 121 | 122 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 123 | 124 | -- 125 | -- Table of sharing profiles. Each sharing profile has a name, associated set 126 | -- of parameters, and a primary connection. The primary connection is the 127 | -- connection that the sharing profile shares, and the parameters dictate the 128 | -- restrictions/features which apply to the user joining the connection via the 129 | -- sharing profile. 130 | -- 131 | 132 | CREATE TABLE guacamole_sharing_profile ( 133 | 134 | `sharing_profile_id` int(11) NOT NULL AUTO_INCREMENT, 135 | `sharing_profile_name` varchar(128) NOT NULL, 136 | `primary_connection_id` int(11) NOT NULL, 137 | 138 | PRIMARY KEY (`sharing_profile_id`), 139 | UNIQUE KEY `sharing_profile_name_primary` (sharing_profile_name, primary_connection_id), 140 | 141 | CONSTRAINT `guacamole_sharing_profile_ibfk_1` 142 | FOREIGN KEY (`primary_connection_id`) 143 | REFERENCES `guacamole_connection` (`connection_id`) 144 | ON DELETE CASCADE 145 | 146 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 147 | 148 | -- 149 | -- Table of connection parameters. Each parameter is simply a name/value pair 150 | -- associated with a connection. 151 | -- 152 | 153 | CREATE TABLE `guacamole_connection_parameter` ( 154 | 155 | `connection_id` int(11) NOT NULL, 156 | `parameter_name` varchar(128) NOT NULL, 157 | `parameter_value` varchar(4096) NOT NULL, 158 | 159 | PRIMARY KEY (`connection_id`,`parameter_name`), 160 | 161 | CONSTRAINT `guacamole_connection_parameter_ibfk_1` 162 | FOREIGN KEY (`connection_id`) 163 | REFERENCES `guacamole_connection` (`connection_id`) ON DELETE CASCADE 164 | 165 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 166 | 167 | -- 168 | -- Table of sharing profile parameters. Each parameter is simply 169 | -- name/value pair associated with a sharing profile. These parameters dictate 170 | -- the restrictions/features which apply to the user joining the associated 171 | -- connection via the sharing profile. 172 | -- 173 | 174 | CREATE TABLE guacamole_sharing_profile_parameter ( 175 | 176 | `sharing_profile_id` integer NOT NULL, 177 | `parameter_name` varchar(128) NOT NULL, 178 | `parameter_value` varchar(4096) NOT NULL, 179 | 180 | PRIMARY KEY (`sharing_profile_id`, `parameter_name`), 181 | 182 | CONSTRAINT `guacamole_sharing_profile_parameter_ibfk_1` 183 | FOREIGN KEY (`sharing_profile_id`) 184 | REFERENCES `guacamole_sharing_profile` (`sharing_profile_id`) ON DELETE CASCADE 185 | 186 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 187 | 188 | -- 189 | -- Table of connection permissions. Each connection permission grants a user 190 | -- specific access to a connection. 191 | -- 192 | 193 | CREATE TABLE `guacamole_connection_permission` ( 194 | 195 | `user_id` int(11) NOT NULL, 196 | `connection_id` int(11) NOT NULL, 197 | `permission` enum('READ', 198 | 'UPDATE', 199 | 'DELETE', 200 | 'ADMINISTER') NOT NULL, 201 | 202 | PRIMARY KEY (`user_id`,`connection_id`,`permission`), 203 | 204 | CONSTRAINT `guacamole_connection_permission_ibfk_1` 205 | FOREIGN KEY (`connection_id`) 206 | REFERENCES `guacamole_connection` (`connection_id`) ON DELETE CASCADE, 207 | 208 | CONSTRAINT `guacamole_connection_permission_ibfk_2` 209 | FOREIGN KEY (`user_id`) 210 | REFERENCES `guacamole_user` (`user_id`) ON DELETE CASCADE 211 | 212 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 213 | 214 | -- 215 | -- Table of connection group permissions. Each group permission grants a user 216 | -- specific access to a connection group. 217 | -- 218 | 219 | CREATE TABLE `guacamole_connection_group_permission` ( 220 | 221 | `user_id` int(11) NOT NULL, 222 | `connection_group_id` int(11) NOT NULL, 223 | `permission` enum('READ', 224 | 'UPDATE', 225 | 'DELETE', 226 | 'ADMINISTER') NOT NULL, 227 | 228 | PRIMARY KEY (`user_id`,`connection_group_id`,`permission`), 229 | 230 | CONSTRAINT `guacamole_connection_group_permission_ibfk_1` 231 | FOREIGN KEY (`connection_group_id`) 232 | REFERENCES `guacamole_connection_group` (`connection_group_id`) ON DELETE CASCADE, 233 | 234 | CONSTRAINT `guacamole_connection_group_permission_ibfk_2` 235 | FOREIGN KEY (`user_id`) 236 | REFERENCES `guacamole_user` (`user_id`) ON DELETE CASCADE 237 | 238 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 239 | 240 | -- 241 | -- Table of sharing profile permissions. Each sharing profile permission grants 242 | -- a user specific access to a sharing profile. 243 | -- 244 | 245 | CREATE TABLE guacamole_sharing_profile_permission ( 246 | 247 | `user_id` integer NOT NULL, 248 | `sharing_profile_id` integer NOT NULL, 249 | `permission` enum('READ', 250 | 'UPDATE', 251 | 'DELETE', 252 | 'ADMINISTER') NOT NULL, 253 | 254 | PRIMARY KEY (`user_id`, `sharing_profile_id`, `permission`), 255 | 256 | CONSTRAINT `guacamole_sharing_profile_permission_ibfk_1` 257 | FOREIGN KEY (`sharing_profile_id`) 258 | REFERENCES `guacamole_sharing_profile` (`sharing_profile_id`) ON DELETE CASCADE, 259 | 260 | CONSTRAINT `guacamole_sharing_profile_permission_ibfk_2` 261 | FOREIGN KEY (`user_id`) 262 | REFERENCES `guacamole_user` (`user_id`) ON DELETE CASCADE 263 | 264 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 265 | 266 | -- 267 | -- Table of system permissions. Each system permission grants a user a 268 | -- system-level privilege of some kind. 269 | -- 270 | 271 | CREATE TABLE `guacamole_system_permission` ( 272 | 273 | `user_id` int(11) NOT NULL, 274 | `permission` enum('CREATE_CONNECTION', 275 | 'CREATE_CONNECTION_GROUP', 276 | 'CREATE_SHARING_PROFILE', 277 | 'CREATE_USER', 278 | 'ADMINISTER') NOT NULL, 279 | 280 | PRIMARY KEY (`user_id`,`permission`), 281 | 282 | CONSTRAINT `guacamole_system_permission_ibfk_1` 283 | FOREIGN KEY (`user_id`) 284 | REFERENCES `guacamole_user` (`user_id`) ON DELETE CASCADE 285 | 286 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 287 | 288 | -- 289 | -- Table of user permissions. Each user permission grants a user access to 290 | -- another user (the "affected" user) for a specific type of operation. 291 | -- 292 | 293 | CREATE TABLE `guacamole_user_permission` ( 294 | 295 | `user_id` int(11) NOT NULL, 296 | `affected_user_id` int(11) NOT NULL, 297 | `permission` enum('READ', 298 | 'UPDATE', 299 | 'DELETE', 300 | 'ADMINISTER') NOT NULL, 301 | 302 | PRIMARY KEY (`user_id`,`affected_user_id`,`permission`), 303 | 304 | CONSTRAINT `guacamole_user_permission_ibfk_1` 305 | FOREIGN KEY (`affected_user_id`) 306 | REFERENCES `guacamole_user` (`user_id`) ON DELETE CASCADE, 307 | 308 | CONSTRAINT `guacamole_user_permission_ibfk_2` 309 | FOREIGN KEY (`user_id`) 310 | REFERENCES `guacamole_user` (`user_id`) ON DELETE CASCADE 311 | 312 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 313 | 314 | -- 315 | -- Table of connection history records. Each record defines a specific user's 316 | -- session, including the connection used, the start time, and the end time 317 | -- (if any). 318 | -- 319 | 320 | CREATE TABLE `guacamole_connection_history` ( 321 | 322 | `history_id` int(11) NOT NULL AUTO_INCREMENT, 323 | `user_id` int(11) DEFAULT NULL, 324 | `username` varchar(128) NOT NULL, 325 | `remote_host` varchar(256) DEFAULT NULL, 326 | `connection_id` int(11) DEFAULT NULL, 327 | `connection_name` varchar(128) NOT NULL, 328 | `sharing_profile_id` int(11) DEFAULT NULL, 329 | `sharing_profile_name` varchar(128) DEFAULT NULL, 330 | `start_date` datetime NOT NULL, 331 | `end_date` datetime DEFAULT NULL, 332 | 333 | PRIMARY KEY (`history_id`), 334 | KEY `user_id` (`user_id`), 335 | KEY `connection_id` (`connection_id`), 336 | KEY `sharing_profile_id` (`sharing_profile_id`), 337 | KEY `start_date` (`start_date`), 338 | KEY `end_date` (`end_date`), 339 | KEY `connection_start_date` (`connection_id`, `start_date`), 340 | 341 | CONSTRAINT `guacamole_connection_history_ibfk_1` 342 | FOREIGN KEY (`user_id`) 343 | REFERENCES `guacamole_user` (`user_id`) ON DELETE SET NULL, 344 | 345 | CONSTRAINT `guacamole_connection_history_ibfk_2` 346 | FOREIGN KEY (`connection_id`) 347 | REFERENCES `guacamole_connection` (`connection_id`) ON DELETE SET NULL, 348 | 349 | CONSTRAINT `guacamole_connection_history_ibfk_3` 350 | FOREIGN KEY (`sharing_profile_id`) 351 | REFERENCES `guacamole_sharing_profile` (`sharing_profile_id`) ON DELETE SET NULL 352 | 353 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 354 | 355 | -- 356 | -- User login/logout history 357 | -- 358 | 359 | CREATE TABLE guacamole_user_history ( 360 | 361 | `history_id` int(11) NOT NULL AUTO_INCREMENT, 362 | `user_id` int(11) DEFAULT NULL, 363 | `username` varchar(128) NOT NULL, 364 | `remote_host` varchar(256) DEFAULT NULL, 365 | `start_date` datetime NOT NULL, 366 | `end_date` datetime DEFAULT NULL, 367 | 368 | PRIMARY KEY (history_id), 369 | KEY `user_id` (`user_id`), 370 | KEY `start_date` (`start_date`), 371 | KEY `end_date` (`end_date`), 372 | KEY `user_start_date` (`user_id`, `start_date`), 373 | 374 | CONSTRAINT guacamole_user_history_ibfk_1 375 | FOREIGN KEY (user_id) 376 | REFERENCES guacamole_user (user_id) ON DELETE SET NULL 377 | 378 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 379 | 380 | -- 381 | -- User password history 382 | -- 383 | 384 | CREATE TABLE guacamole_user_password_history ( 385 | 386 | `password_history_id` int(11) NOT NULL AUTO_INCREMENT, 387 | `user_id` int(11) NOT NULL, 388 | 389 | -- Salted password 390 | `password_hash` binary(32) NOT NULL, 391 | `password_salt` binary(32), 392 | `password_date` datetime NOT NULL, 393 | 394 | PRIMARY KEY (`password_history_id`), 395 | KEY `user_id` (`user_id`), 396 | 397 | CONSTRAINT `guacamole_user_password_history_ibfk_1` 398 | FOREIGN KEY (`user_id`) 399 | REFERENCES `guacamole_user` (`user_id`) ON DELETE CASCADE 400 | 401 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 402 | -- 403 | -- Licensed to the Apache Software Foundation (ASF) under one 404 | -- or more contributor license agreements. See the NOTICE file 405 | -- distributed with this work for additional information 406 | -- regarding copyright ownership. The ASF licenses this file 407 | -- to you under the Apache License, Version 2.0 (the 408 | -- "License"); you may not use this file except in compliance 409 | -- with the License. You may obtain a copy of the License at 410 | -- 411 | -- http://www.apache.org/licenses/LICENSE-2.0 412 | -- 413 | -- Unless required by applicable law or agreed to in writing, 414 | -- software distributed under the License is distributed on an 415 | -- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 416 | -- KIND, either express or implied. See the License for the 417 | -- specific language governing permissions and limitations 418 | -- under the License. 419 | -- 420 | 421 | -- Create default user "guacadmin" with password "guacadmin" 422 | INSERT INTO guacamole_user (username, password_hash, password_salt, password_date) 423 | VALUES ('guacadmin', 424 | x'CA458A7D494E3BE824F5E1E175A1556C0F8EEF2C2D7DF3633BEC4A29C4411960', -- 'guacadmin' 425 | x'FE24ADC5E11E2B25288D1704ABE67A79E342ECC26064CE69C5B3177795A82264', 426 | NOW()); 427 | 428 | -- Grant this user all system permissions 429 | INSERT INTO guacamole_system_permission 430 | SELECT user_id, permission 431 | FROM ( 432 | SELECT 'guacadmin' AS username, 'CREATE_CONNECTION' AS permission 433 | UNION SELECT 'guacadmin' AS username, 'CREATE_CONNECTION_GROUP' AS permission 434 | UNION SELECT 'guacadmin' AS username, 'CREATE_SHARING_PROFILE' AS permission 435 | UNION SELECT 'guacadmin' AS username, 'CREATE_USER' AS permission 436 | UNION SELECT 'guacadmin' AS username, 'ADMINISTER' AS permission 437 | ) permissions 438 | JOIN guacamole_user ON permissions.username = guacamole_user.username; 439 | 440 | -- Grant admin permission to read/update/administer self 441 | INSERT INTO guacamole_user_permission 442 | SELECT guacamole_user.user_id, affected.user_id, permission 443 | FROM ( 444 | SELECT 'guacadmin' AS username, 'guacadmin' AS affected_username, 'READ' AS permission 445 | UNION SELECT 'guacadmin' AS username, 'guacadmin' AS affected_username, 'UPDATE' AS permission 446 | UNION SELECT 'guacadmin' AS username, 'guacadmin' AS affected_username, 'ADMINISTER' AS permission 447 | ) permissions 448 | JOIN guacamole_user ON permissions.username = guacamole_user.username 449 | JOIN guacamole_user affected ON permissions.affected_username = affected.username; 450 | 451 | -------------------------------------------------------------------------------- /loadbalancer.tf: -------------------------------------------------------------------------------- 1 | 2 | #Provision load balancer and associated security group 3 | 4 | ############################################### 5 | ## SECURITY GROUP DEFINITION 6 | ############################################### 7 | 8 | resource "aws_security_group" "lb-sec" { 9 | name = "lb-secgroup" 10 | vpc_id = "${aws_vpc.app_vpc.id}" 11 | 12 | 13 | # HTTPS access from anywhere 14 | ingress { 15 | from_port = 443 16 | to_port = 443 17 | protocol = "tcp" 18 | cidr_blocks = ["0.0.0.0/0"] 19 | } 20 | 21 | #ping from anywhere - can be omitted if unnecessary 22 | ingress { 23 | from_port = 8 24 | to_port = 0 25 | protocol = "icmp" 26 | cidr_blocks = ["0.0.0.0/0"] 27 | } 28 | egress { 29 | from_port = 0 30 | to_port = 0 31 | protocol = "-1" 32 | cidr_blocks = ["0.0.0.0/0"] 33 | } 34 | } 35 | 36 | 37 | 38 | ############################################### 39 | ## ALB CONFIGURATION 40 | ############################################### 41 | 42 | resource "aws_alb" "alb" { 43 | subnets = ["${aws_subnet.pub_subnet1.id}","${aws_subnet.pub_subnet2.id}"] 44 | internal = false 45 | security_groups = ["${aws_security_group.lb-sec.id}"] 46 | } 47 | 48 | resource "aws_alb_target_group" "targ" { 49 | port = 8080 50 | protocol = "HTTP" 51 | vpc_id = "${aws_vpc.app_vpc.id}" 52 | } 53 | 54 | resource "aws_alb_target_group_attachment" "attach_guac" { 55 | target_group_arn = "${aws_alb_target_group.targ.arn}" 56 | target_id = "${aws_instance.guac-server1.id}" 57 | port = 8080 58 | } 59 | 60 | resource "aws_alb_listener" "list" { 61 | "default_action" { 62 | target_group_arn = "${aws_alb_target_group.targ.arn}" 63 | type = "forward" 64 | } 65 | load_balancer_arn = "${aws_alb.alb.arn}" 66 | port = 443 67 | protocol = "HTTPS" 68 | certificate_arn = "${var.certificate_arn}" 69 | } 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /network.tf: -------------------------------------------------------------------------------- 1 | # Provision networking resources 2 | # We need 1 VPC, 2 Public subnets (for load balancer and NAT GW), 2 Internal Subnet (for Guacamole and Database) 3 | # And 2 VDI subnets (for VDI VM's) 4 | 5 | 6 | #provision vpc 7 | resource "aws_vpc" "app_vpc" { 8 | cidr_block = "192.168.0.0/16" 9 | assign_generated_ipv6_cidr_block = false 10 | enable_dns_support = true 11 | tags { 12 | Name = "Guacamole VPC" 13 | } 14 | } 15 | 16 | #create igw 17 | resource "aws_internet_gateway" "app_igw" { 18 | vpc_id = "${aws_vpc.app_vpc.id}" 19 | } 20 | 21 | # Pull data for AZs in region 22 | data "aws_availability_zones" "available" {} 23 | 24 | #provision public subnet 1 25 | resource "aws_subnet" "pub_subnet1"{ 26 | # Ensures subnet is created in it's own AZ 27 | availability_zone = "${data.aws_availability_zones.available.names[0]}" 28 | vpc_id = "${aws_vpc.app_vpc.id}" 29 | cidr_block = "192.168.10.0/24" 30 | tags { 31 | Name = "public subnet 1" 32 | } 33 | } 34 | 35 | #public subnet 2 36 | resource "aws_subnet" "pub_subnet2"{ 37 | # Ensures subnet is created in it's own AZ 38 | availability_zone = "${data.aws_availability_zones.available.names[1]}" 39 | vpc_id = "${aws_vpc.app_vpc.id}" 40 | cidr_block = "192.168.11.0/24" 41 | tags { 42 | Name = "public subnet 2" 43 | } 44 | } 45 | 46 | #provision VDI subnet 1 47 | resource "aws_subnet" "vdi_subnet1" { 48 | vpc_id = "${aws_vpc.app_vpc.id}" 49 | availability_zone = "${data.aws_availability_zones.available.names[0]}" 50 | cidr_block = "192.168.20.0/24" 51 | tags { 52 | Name = "VDI subnet 1" 53 | } 54 | } 55 | 56 | #provision VDI subnet 2 57 | resource "aws_subnet" "vdi_subnet2" { 58 | vpc_id = "${aws_vpc.app_vpc.id}" 59 | availability_zone = "${data.aws_availability_zones.available.names[1]}" 60 | cidr_block = "192.168.21.0/24" 61 | tags { 62 | Name = "VDI subnet 2" 63 | } 64 | } 65 | 66 | 67 | #provision private subnet #1 68 | resource "aws_subnet" "priv_subnet1" { 69 | vpc_id = "${aws_vpc.app_vpc.id}" 70 | availability_zone = "${data.aws_availability_zones.available.names[0]}" 71 | cidr_block = "192.168.30.0/24" 72 | tags { 73 | Name = "private subnet 1" 74 | } 75 | } 76 | 77 | #provision private subnet #2 78 | resource "aws_subnet" "priv_subnet2" { 79 | vpc_id = "${aws_vpc.app_vpc.id}" 80 | availability_zone = "${data.aws_availability_zones.available.names[1]}" 81 | cidr_block = "192.168.31.0/24" 82 | tags { 83 | Name = "private subnet 2" 84 | } 85 | } 86 | 87 | #new default route table 88 | resource "aws_default_route_table" "default" { 89 | default_route_table_id = "${aws_vpc.app_vpc.default_route_table_id}" 90 | 91 | route { 92 | cidr_block = "0.0.0.0/0" 93 | gateway_id = "${aws_internet_gateway.app_igw.id}" 94 | } 95 | } 96 | 97 | # provision EIP for nat gateway 1 98 | resource "aws_eip" "gwip1" { 99 | } 100 | 101 | # provision EIP for nat gateway 1 102 | resource "aws_eip" "gwip2" { 103 | } 104 | 105 | # NAT Gateway 1 106 | resource "aws_nat_gateway" "gw1" { 107 | allocation_id = "${aws_eip.gwip1.id}" 108 | subnet_id = "${aws_subnet.pub_subnet1.id}" 109 | tags { 110 | Name = "NAT Gateway 1" 111 | } 112 | } 113 | 114 | # NAT Gateway 2 115 | resource "aws_nat_gateway" "gw2" { 116 | allocation_id = "${aws_eip.gwip2.id}" 117 | subnet_id = "${aws_subnet.pub_subnet2.id}" 118 | tags { 119 | Name = "NAT Gateway 2" 120 | } 121 | } 122 | 123 | # Add route table for NAT GW 124 | # private and VDI subnets respective to each AZ is routed to a NAT GW 125 | 126 | resource "aws_route_table" "natroute1" { 127 | vpc_id = "${aws_vpc.app_vpc.id}" 128 | route { 129 | cidr_block = "0.0.0.0/0" 130 | gateway_id = "${aws_nat_gateway.gw1.id}" 131 | } 132 | } 133 | resource "aws_route_table" "natroute2" { 134 | vpc_id = "${aws_vpc.app_vpc.id}" 135 | route { 136 | cidr_block = "0.0.0.0/0" 137 | gateway_id = "${aws_nat_gateway.gw2.id}" 138 | } 139 | } 140 | # Associate VDI Subnets with AWS Route table for the NAT Gateway 141 | resource "aws_route_table_association" "vdi1" { 142 | subnet_id = "${aws_subnet.vdi_subnet1.id}" 143 | route_table_id = "${aws_route_table.natroute1.id}" 144 | } 145 | resource "aws_route_table_association" "vdi2" { 146 | subnet_id = "${aws_subnet.vdi_subnet2.id}" 147 | route_table_id = "${aws_route_table.natroute2.id}" 148 | } 149 | # Associate private subnets with NAT gw route tables 150 | resource "aws_route_table_association" "int1" { 151 | subnet_id = "${aws_subnet.priv_subnet1.id}" 152 | route_table_id = "${aws_route_table.natroute1.id}" 153 | } 154 | resource "aws_route_table_association" "int2" { 155 | subnet_id = "${aws_subnet.priv_subnet2.id}" 156 | route_table_id = "${aws_route_table.natroute2.id}" 157 | } -------------------------------------------------------------------------------- /provider.tf: -------------------------------------------------------------------------------- 1 | 2 | # AWS endpoint provider 3 | 4 | provider "aws" { 5 | 6 | access_key = "${var.aws_access_key}" 7 | secret_key = "${var.aws_secret_key}" 8 | region = "eu-west-1" 9 | } 10 | 11 | /* Only required for Stratoscale Symphony deployments 12 | 13 | provider "aws" { 14 | access_key = "${var.symp_access_key}" 15 | secret_key = "${var.symp_secret_key}" 16 | 17 | endpoints { 18 | ec2 = "https://${var.symphony_ip}/api/v2/aws/ec2" 19 | elb = "https://${var.symphony_ip}/api/v2/aws/elb" 20 | rds = "https://${var.symphony_ip}/api/v2/aws/rds" 21 | } 22 | 23 | insecure = "true" 24 | skip_metadata_api_check = true 25 | skip_credentials_validation = true 26 | 27 | # No importance for this value currently 28 | region = "us-east-2" 29 | version = "1.28" 30 | } 31 | 32 | */ -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | 2 | # Apache Guacamole Deployment for AWS using Terraform and Cloudinit 3 | 4 | ## Overview 5 | 6 | This Terraform script will deploy Apache Guacamole environment into your AWS region of choice. 7 | 8 | ## What's Guacamole? 9 | 10 | From https://guacamole.apache.org/ - 11 | 12 | "Apache Guacamole is a **clientless remote desktop gateway**. It supports standard protocols like VNC, RDP, and SSH. We call it _clientless_ because no plugins or client software are required. Thanks to HTML5, once Guacamole is installed on a server, all you need to access your desktops is a web browser." 13 | 14 | ## Architecture 15 | 16 | ![Overview](https://raw.githubusercontent.com/aries-strato/guacamole-aws/master/diagram.png) 17 | 18 | The infra folder contains scripts to deploy Guacamole infrastructure. The vdi folder contains scripts to create the optional Linux VDI VM's based on Ubuntu. 19 | 20 | ## What we use 21 | 22 | 1 RDS instance (MySQL), 1 VPC, 6 subnets (2 public, 2 private, 2 for VDI), 2 NAT gateways, 1 ALB. 23 | 24 | Terraform, Ubuntu Linux 16.04 LTS cloud image to run the Guacamole containers is all that's needed to deploy it. 25 | 26 | ## terraform.tfvars 27 | 28 | Populate the file (terraform.tfvars.sample, and omit the .sample extension) with your respective input. You need to upload or generate a certificate via ACM or IAM for the ALB. 29 | 30 | Also include the name of the keypair you would like to use for the bastion host. The same keypair will be injected into the Guacamole server as well (in case you ever need to SSH into it). 31 | 32 | The cloudinit scripts are written for Ubuntu Xenial (16.04 LTS) so use a cloud-enabled image for those. 33 | 34 | ## Getting VDI images connected 35 | It's your responsibility to create VDI images (whether they are Linux or Windows based) and spawn them in the relevant subnets that get created in their respective AZ. There's no automation (yet) to connect and automatically add the images to the Guacamole server, so it's a manual process - but the good news is the configuration state is always stored in the MySQL database so as long as this is backed up and kept up to date - this configuration can be done once and kept persistent. You can refer to the Guacamole documentation, specifically the API documentation to take this concept further - https://guacamole.apache.org/api-documentation/. 36 | 37 | ## Network connectivity 38 | 39 | By default - the Guacamole servers have connectivity to the VDI images since they should all be in the proper subnets and routing using the IGW for the VPC that gets created. Security groups should prevent any other VM's from accessing them. 40 | 41 | ## Some additional notes / next steps / things to improve 42 | 43 | I'd like to configure elastic load balancing and auto scaling to work together and spawn more guacd/guacamole servers as needed. Another option is using something like AWS Fargate or Kubernetes to manage the containers, rather then just deploying them into single EC2 instances per AZ in a region. 44 | 45 | I've only configured the script to use the first two AZ's available in a region, but this can be extended to 3 or more, depending on the region - and would require some additional coding. Another MySQL read-replica can be set up to run in another region as well - or rather then using an on-demand MySQL instance one can use Aurora Serverless. 46 | 47 | The bastion server is also single instance - so if an AZ goes down with your bastion it kind of puts you out of comission until that AZ comes back up. To fix this, just deploy another bastion into the other public subnet and give it an EIP. 48 | 49 | -------------------------------------------------------------------------------- /terraform.tfvars.sample: -------------------------------------------------------------------------------- 1 | # Sample tfvars file 2 | # omit .sample from extension before applying 3 | 4 | 5 | # AWS Credentials 6 | 7 | aws_access_key = "" 8 | aws_secret_key = "" 9 | 10 | # Database information 11 | 12 | db_user = "stratoadmin" 13 | db_password = "Stratoscale2018!" 14 | 15 | 16 | # AMI Values and instance types 17 | # Use Public Ubuntu Server 16.04 LTS (Xenial) cloud image ami 18 | # For list of official AMI's see: https://cloud-images.ubuntu.com/locator/ec2/ 19 | # 20 | # Quick reference: 21 | # us-east-1: ami-a4dc46db 22 | # us-east-2: ami-6a003c0f 23 | # eu-west-1: ami-58d7e821 24 | 25 | guacsrv_ami = "" 26 | guacsrv_instance_type = "t2.medium" 27 | # For Guacamole server EC2 instance type, it's reccommended to have at least 1GB of RAM 28 | 29 | bastion_ami = "" 30 | bastion_instance_type = "t2.medium" 31 | 32 | # Key pair name for SSH 33 | user_keyname = "" 34 | 35 | # SSL Certificate arn (either IAM or ACM managed) 36 | certificate_arn = "" 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /variables.tf: -------------------------------------------------------------------------------- 1 | # Global TF Variables 2 | 3 | variable "db_password" {} 4 | variable "db_user" {} 5 | 6 | variable "user_keyname" {} 7 | 8 | variable "aws_secret_key" {} 9 | variable "aws_access_key" {} 10 | 11 | variable "guacsrv_ami" {} 12 | variable "guacsrv_instance_type" {} 13 | variable "bastion_ami" {} 14 | variable "bastion_instance_type" {} 15 | 16 | variable "certificate_arn" {} 17 | 18 | 19 | 20 | 21 | 22 | --------------------------------------------------------------------------------