4 | ## Requirements
5 |
6 | | Name | Version |
7 | |------|---------|
8 | | [terraform](#requirement\_terraform) | ~> 1.0 |
9 | | [digitalocean](#requirement\_digitalocean) | ~> 2.0 |
10 |
11 | ## Providers
12 |
13 | | Name | Version |
14 | |------|---------|
15 | | [digitalocean](#provider\_digitalocean) | 2.34.1 |
16 |
17 | ## Modules
18 |
19 | No modules.
20 |
21 | ## Resources
22 |
23 | | Name | Type |
24 | |------|------|
25 | | [digitalocean_certificate.cert](https://registry.terraform.io/providers/digitalocean/digitalocean/latest/docs/resources/certificate) | resource |
26 | | [digitalocean_domain.default](https://registry.terraform.io/providers/digitalocean/digitalocean/latest/docs/resources/domain) | resource |
27 | | [digitalocean_droplet.kasm-server](https://registry.terraform.io/providers/digitalocean/digitalocean/latest/docs/resources/droplet) | resource |
28 | | [digitalocean_firewall.workspaces-fw](https://registry.terraform.io/providers/digitalocean/digitalocean/latest/docs/resources/firewall) | resource |
29 | | [digitalocean_loadbalancer.www-lb](https://registry.terraform.io/providers/digitalocean/digitalocean/latest/docs/resources/loadbalancer) | resource |
30 | | [digitalocean_project.project](https://registry.terraform.io/providers/digitalocean/digitalocean/latest/docs/resources/project) | resource |
31 | | [digitalocean_record.static](https://registry.terraform.io/providers/digitalocean/digitalocean/latest/docs/resources/record) | resource |
32 | | [digitalocean_tag.project](https://registry.terraform.io/providers/digitalocean/digitalocean/latest/docs/resources/tag) | resource |
33 | | [digitalocean_vpc.kasm_vpc](https://registry.terraform.io/providers/digitalocean/digitalocean/latest/docs/resources/vpc) | resource |
34 | | [digitalocean_certificate.data-cert](https://registry.terraform.io/providers/digitalocean/digitalocean/latest/docs/data-sources/certificate) | data source |
35 | | [digitalocean_domain.data-default](https://registry.terraform.io/providers/digitalocean/digitalocean/latest/docs/data-sources/domain) | data source |
36 | | [digitalocean_droplet.data-kasm_server](https://registry.terraform.io/providers/digitalocean/digitalocean/latest/docs/data-sources/droplet) | data source |
37 | | [digitalocean_tag.data-project](https://registry.terraform.io/providers/digitalocean/digitalocean/latest/docs/data-sources/tag) | data source |
38 | | [digitalocean_vpc.data-kasm_vpc](https://registry.terraform.io/providers/digitalocean/digitalocean/latest/docs/data-sources/vpc) | data source |
39 |
40 | ## Inputs
41 |
42 | | Name | Description | Type | Default | Required |
43 | |------|-------------|------|---------|:--------:|
44 | | [admin\_password](#input\_admin\_password) | The default password to be used for the default admin@kasm.local account. Only use alphanumeric characters | `string` | n/a | yes |
45 | | [allow\_kasm\_web\_cidrs](#input\_allow\_kasm\_web\_cidrs) | CIDR notation of the bastion host allowed to SSH in to the machines | `list(string)` | n/a | yes |
46 | | [allow\_ssh\_cidrs](#input\_allow\_ssh\_cidrs) | List of Subnets in CIDR notation for hosts allowed to SSH | `list(string)` | n/a | yes |
47 | | [anywhere](#input\_anywhere) | Anywhere route subnet | `list(string)` | [
"0.0.0.0/0",
"::/0"
]
| no |
48 | | [digital\_ocean\_droplet\_slug](#input\_digital\_ocean\_droplet\_slug) | The Default Digital Ocean Droplet Slug: https://slugs.do-api.dev/ | `string` | n/a | yes |
49 | | [digital\_ocean\_image](#input\_digital\_ocean\_image) | Default Image for Ubuntu LTS | `string` | n/a | yes |
50 | | [digital\_ocean\_region](#input\_digital\_ocean\_region) | The Default Digital Ocean Region Slug: https://docs.digitalocean.com/products/platform/availability-matrix/ | `string` | n/a | yes |
51 | | [do\_domain\_name](#input\_do\_domain\_name) | The domain name that users will use to access kasm | `string` | n/a | yes |
52 | | [kasm\_build\_url](#input\_kasm\_build\_url) | The Kasm build file to install | `string` | n/a | yes |
53 | | [project\_name](#input\_project\_name) | The name of the project/deployment/company eg (acme). Lower case all one word as this will be used in a domain name | `string` | n/a | yes |
54 | | [ssh\_key\_fingerprints](#input\_ssh\_key\_fingerprints) | Keys used for sshing into kasm hosts | `list(string)` | n/a | yes |
55 | | [swap\_size](#input\_swap\_size) | The amount of swap (in MB) to configure inside the compute instances | `number` | n/a | yes |
56 | | [user\_password](#input\_user\_password) | The default password to be used for the default user@kasm.local account. Only use alphanumeric characters | `string` | n/a | yes |
57 | | [vpc\_subnet\_cidr](#input\_vpc\_subnet\_cidr) | VPC Subnet CIDR to deploy Kasm | `string` | n/a | yes |
58 |
59 | ## Outputs
60 |
61 | | Name | Description |
62 | |------|-------------|
63 | | [kasm\_server\_ip](#output\_kasm\_server\_ip) | n/a |
64 |
65 |
--------------------------------------------------------------------------------
/digitalocean/single_server/module/dns.tf:
--------------------------------------------------------------------------------
1 | resource "digitalocean_domain" "default" {
2 | name = var.do_domain_name
3 | }
4 |
5 | data "digitalocean_domain" "data-default" {
6 | name = digitalocean_domain.default.name
7 | }
8 |
9 | resource "digitalocean_record" "static" {
10 | domain = digitalocean_domain.default.name
11 | type = "A"
12 | name = "static"
13 | value = digitalocean_loadbalancer.www-lb.ip
14 | }
15 |
16 | resource "digitalocean_certificate" "cert" {
17 | name = "${var.project_name}-cert"
18 | type = "lets_encrypt"
19 | domains = [digitalocean_domain.default.name]
20 |
21 | lifecycle {
22 | create_before_destroy = true
23 | }
24 | }
25 |
26 | data "digitalocean_certificate" "data-cert" {
27 | name = digitalocean_certificate.cert.name
28 | }
29 |
--------------------------------------------------------------------------------
/digitalocean/single_server/module/firewall.tf:
--------------------------------------------------------------------------------
1 | resource "digitalocean_firewall" "workspaces-fw" {
2 | name = "${var.project_name}-fw"
3 |
4 | tags = [digitalocean_tag.project.id]
5 |
6 | inbound_rule {
7 | protocol = "tcp"
8 | port_range = "22"
9 | source_addresses = var.allow_ssh_cidrs
10 | }
11 |
12 | inbound_rule {
13 | protocol = "tcp"
14 | port_range = "443"
15 | source_addresses = var.allow_kasm_web_cidrs
16 | }
17 |
18 | inbound_rule {
19 | protocol = "tcp"
20 | port_range = "80"
21 | source_addresses = var.allow_kasm_web_cidrs
22 | }
23 |
24 | outbound_rule {
25 | protocol = "tcp"
26 | port_range = "1-65535"
27 | destination_addresses = var.anywhere
28 | }
29 |
30 | outbound_rule {
31 | protocol = "udp"
32 | port_range = "1-65535"
33 | destination_addresses = var.anywhere
34 | }
35 |
36 | outbound_rule {
37 | protocol = "icmp"
38 | destination_addresses = var.anywhere
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/digitalocean/single_server/module/load_balancer.tf:
--------------------------------------------------------------------------------
1 | resource "digitalocean_loadbalancer" "www-lb" {
2 | name = "${var.project_name}-lb"
3 | region = var.digital_ocean_region
4 | vpc_uuid = data.digitalocean_vpc.data-kasm_vpc.id
5 | redirect_http_to_https = true
6 |
7 | forwarding_rule {
8 | entry_port = 443
9 | entry_protocol = "https"
10 |
11 | target_port = 443
12 | target_protocol = "https"
13 |
14 | certificate_name = data.digitalocean_certificate.data-cert.id
15 | }
16 |
17 | healthcheck {
18 | port = 443
19 | protocol = "https"
20 | path = "/"
21 | }
22 |
23 | droplet_ids = [data.digitalocean_droplet.data-kasm_server.id]
24 | }
25 |
--------------------------------------------------------------------------------
/digitalocean/single_server/module/output.tf:
--------------------------------------------------------------------------------
1 | output "kasm_server_ip" {
2 | value = digitalocean_droplet.kasm-server.ipv4_address
3 | }
4 |
--------------------------------------------------------------------------------
/digitalocean/single_server/module/project.tf:
--------------------------------------------------------------------------------
1 | resource "digitalocean_project" "project" {
2 | name = var.project_name
3 | description = "Deployment for ${var.project_name}"
4 | purpose = "Kasm Workspaces"
5 | environment = "Development"
6 | resources = [
7 | data.digitalocean_droplet.data-kasm_server.urn,
8 | data.digitalocean_domain.data-default.urn
9 | ]
10 | }
11 |
12 | resource "digitalocean_tag" "project" {
13 | name = var.project_name
14 | }
15 |
16 | data "digitalocean_tag" "data-project" {
17 | name = digitalocean_tag.project.name
18 | }
19 |
--------------------------------------------------------------------------------
/digitalocean/single_server/module/provider.tf:
--------------------------------------------------------------------------------
1 | terraform {
2 | required_version = "~> 1.0"
3 |
4 | required_providers {
5 | digitalocean = {
6 | source = "digitalocean/digitalocean"
7 | version = "~> 2.0"
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/digitalocean/single_server/module/server.tf:
--------------------------------------------------------------------------------
1 | resource "digitalocean_droplet" "kasm-server" {
2 | ssh_keys = var.ssh_key_fingerprints
3 | image = var.digital_ocean_image
4 | region = var.digital_ocean_region
5 | size = var.digital_ocean_droplet_slug
6 | vpc_uuid = data.digitalocean_vpc.data-kasm_vpc.id
7 | backups = false
8 | ipv6 = false
9 | name = "${var.project_name}-workspaces"
10 | tags = [data.digitalocean_tag.data-project.id]
11 | user_data = templatefile("${path.module}/userdata/kasm_server_init.sh",
12 | {
13 | kasm_build_url = var.kasm_build_url
14 | user_password = var.user_password
15 | admin_password = var.admin_password
16 | swap_size = var.swap_size
17 | }
18 | )
19 | }
20 |
21 | data "digitalocean_droplet" "data-kasm_server" {
22 | id = digitalocean_droplet.kasm-server.id
23 | }
24 |
--------------------------------------------------------------------------------
/digitalocean/single_server/module/userdata/kasm_server_init.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -ex
3 | echo "Starting Kasm Workspaces Install"
4 |
5 | ## Create Swap partition
6 | fallocate -l "${swap_size}"g /var/kasm.swap
7 | chmod 600 /var/kasm.swap
8 | mkswap /var/kasm.swap
9 | swapon /var/kasm.swap
10 | echo '/var/kasm.swap swap swap defaults 0 0' | tee -a /etc/fstab
11 |
12 | cd /tmp
13 |
14 | PRIVATE_IP=(`hostname -I | cut -d' ' -f1 | tr -d '\\n'`)
15 |
16 | wget ${kasm_build_url} -O kasm_workspaces.tar.gz
17 | tar -xf kasm_workspaces.tar.gz
18 | bash kasm_release/install.sh -e -U ${user_password} -P ${admin_password} -p $PRIVATE_IP -m $PRIVATE_IP
19 |
20 | echo "Done
21 |
--------------------------------------------------------------------------------
/digitalocean/single_server/module/variables.tf:
--------------------------------------------------------------------------------
1 | variable "project_name" {
2 | description = "The name of the project/deployment/company eg (acme). Lower case all one word as this will be used in a domain name"
3 | type = string
4 | }
5 |
6 | variable "digital_ocean_region" {
7 | description = "The Default Digital Ocean Region Slug: https://docs.digitalocean.com/products/platform/availability-matrix/"
8 | type = string
9 | }
10 |
11 | variable "digital_ocean_droplet_slug" {
12 | description = "The Default Digital Ocean Droplet Slug: https://slugs.do-api.dev/"
13 | type = string
14 | }
15 |
16 | variable "digital_ocean_image" {
17 | description = "Default Image for Ubuntu LTS"
18 | type = string
19 | }
20 |
21 | variable "vpc_subnet_cidr" {
22 | description = "VPC Subnet CIDR to deploy Kasm"
23 | type = string
24 | }
25 |
26 | variable "kasm_build_url" {
27 | description = "The Kasm build file to install"
28 | type = string
29 | }
30 |
31 | variable "user_password" {
32 | description = "The default password to be used for the default user@kasm.local account. Only use alphanumeric characters"
33 | type = string
34 | sensitive = true
35 | }
36 |
37 | variable "admin_password" {
38 | description = "The default password to be used for the default admin@kasm.local account. Only use alphanumeric characters"
39 | type = string
40 | sensitive = true
41 | }
42 |
43 | variable "allow_ssh_cidrs" {
44 | description = "List of Subnets in CIDR notation for hosts allowed to SSH"
45 | type = list(string)
46 | }
47 |
48 | variable "allow_kasm_web_cidrs" {
49 | description = "CIDR notation of the bastion host allowed to SSH in to the machines"
50 | type = list(string)
51 | }
52 |
53 | variable "do_domain_name" {
54 | description = "The domain name that users will use to access kasm"
55 | type = string
56 | }
57 |
58 | variable "ssh_key_fingerprints" {
59 | # The ssh key fingerprints from uploaded keys can be obtained at https://cloud.digitalocean.com/account/security
60 | description = "Keys used for sshing into kasm hosts"
61 | type = list(string)
62 | }
63 |
64 | variable "swap_size" {
65 | description = "The amount of swap (in GB) to configure inside the compute instances"
66 | type = number
67 |
68 | validation {
69 | condition = var.swap_size >= 1 && var.swap_size <= 8 && floor(var.swap_size) == var.swap_size
70 | error_message = "Swap size is the amount of disk space to use for Kasm in GB and must be an integer between 1 and 8."
71 | }
72 | }
73 |
74 | variable "anywhere" {
75 | description = "Anywhere route subnet"
76 | type = list(string)
77 | default = ["0.0.0.0/0", "::/0"]
78 |
79 | validation {
80 | condition = can([for subnet in var.anywhere : cidrhost(subnet, 0)])
81 | error_message = "Anywhere variable must be valid IPv4 CIDR - usually 0.0.0.0/0 for all default routes and default Security Group access."
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/digitalocean/single_server/module/vpc.tf:
--------------------------------------------------------------------------------
1 | resource "digitalocean_vpc" "kasm_vpc" {
2 | name = "${var.project_name}-vpc"
3 | description = "Kasm deployment VPC for ${var.project_name}"
4 | region = var.digital_ocean_region
5 | ip_range = var.vpc_subnet_cidr
6 | }
7 |
8 | data "digitalocean_vpc" "data-kasm_vpc" {
9 | name = digitalocean_vpc.kasm_vpc.name
10 | }
11 |
--------------------------------------------------------------------------------
/digitalocean/single_server/provider.tf:
--------------------------------------------------------------------------------
1 | terraform {
2 | required_version = "~> 1.0"
3 |
4 | required_providers {
5 | digitalocean = {
6 | source = "digitalocean/digitalocean"
7 | version = "~> 2.0"
8 | }
9 | }
10 | }
11 |
12 | provider "digitalocean" {
13 | token = var.digital_ocean_token
14 | }
15 |
--------------------------------------------------------------------------------
/digitalocean/single_server/secrets.tfvars.example:
--------------------------------------------------------------------------------
1 | digital_ocean_token = ""
--------------------------------------------------------------------------------
/digitalocean/single_server/terraform.tfvars:
--------------------------------------------------------------------------------
1 | ## Kasm deployment settings
2 | project_name = "contoso"
3 | do_domain_name = "kasm.contoso.com"
4 | digital_ocean_region = "nyc3"
5 | vpc_subnet_cidr = "10.0.0.0/24"
6 |
7 | ## DO Authentication variables
8 | ssh_key_fingerprints = []
9 |
10 | ## VM Settings
11 | digital_ocean_image = "docker-20-04"
12 | digital_ocean_droplet_slug = "s-2vcpu-4gb-intel"
13 | swap_size = 2
14 |
15 | ## Kasm passwords
16 | user_password = "changeme"
17 | admin_password = "changeme"
18 |
19 | ## VM Access subnets
20 | allow_ssh_cidrs = ["0.0.0.0/0"]
21 | allow_kasm_web_cidrs = ["0.0.0.0/0"]
22 |
23 | ## Kasm download URL
24 | kasm_build_url = "https://kasm-static-content.s3.amazonaws.com/kasm_release_1.17.0.bbc15c.tar.gz"
--------------------------------------------------------------------------------
/digitalocean/single_server/variables.tf:
--------------------------------------------------------------------------------
1 | variable "digital_ocean_token" {
2 | description = "Authentication Token For Digital Ocean"
3 | type = string
4 | sensitive = true
5 |
6 | validation {
7 | condition = can(regex("^(dop_v1_[a-f0-9]{64})", var.digital_ocean_token))
8 | error_message = "The digital_ocean_token must be a valid API Token (https://docs.digitalocean.com/reference/api/create-personal-access-token/)."
9 | }
10 | }
11 |
12 | variable "digital_ocean_region" {
13 | description = "The Digital Ocean region where you wish to deploy Kasm"
14 | type = string
15 | default = "nyc3"
16 |
17 | validation {
18 | condition = can(regex("^[a-zA-Z]{3}\\d", var.digital_ocean_region))
19 | error_message = "The DigitalOcean region format is always 3 letters and a number (e.g. nyc3) - check out https://docs.digitalocean.com/products/platform/availability-matrix/ for available regions."
20 | }
21 | }
22 |
23 | variable "do_domain_name" {
24 | description = "The domain name that users will use to access Kasm"
25 | type = string
26 |
27 | validation {
28 | condition = can(regex("^[a-z0-9_-]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,6}", var.do_domain_name))
29 | error_message = "There are invalid characters in the do_domain_name - it must be a valid domain name."
30 | }
31 | }
32 |
33 | variable "ssh_key_fingerprints" {
34 | # The ssh key fingerprints from uploaded keys can be obtained at https://cloud.digitalocean.com/account/security
35 | description = "Keys used for sshing into kasm hosts"
36 | type = list(string)
37 |
38 | validation {
39 | condition = alltrue([for fingerprint in var.ssh_key_fingerprints : can(regex("^([a-f0-9]{2}:?){16}$", fingerprint))])
40 | error_message = "One of the SSH Key fingerprints is incorrectly formatted. It should be 16 colon-delimited hex bytes (e.g. 12:34:56:78:90:ab:cd:ef:12:34:56:78:90:ab:cd:ef)."
41 | }
42 | }
43 |
44 | variable "project_name" {
45 | description = "The name of the project/deployment/company eg (acme)."
46 | type = string
47 |
48 | validation {
49 | condition = can(regex("^[a-z]{1,15}", var.project_name))
50 | error_message = "The project_name variable can only be one word between 1 and 15 lower-case letters since it is a seed value in multiple object names."
51 | }
52 | }
53 |
54 | variable "vpc_subnet_cidr" {
55 | description = "VPC Subnet CIDR where you wish to deploy Kasm"
56 | type = string
57 | default = "10.0.0.0/24"
58 |
59 | validation {
60 | condition = can(cidrhost(var.vpc_subnet_cidr, 0))
61 | error_message = "The vpc_subnet_cidr must be a valid IPv4 Subnet in CIDR notation (e.g. 10.0.0.0/24)"
62 | }
63 | }
64 |
65 | variable "digital_ocean_droplet_slug" {
66 | description = "The Default Digital Ocean Droplet Slug: https://slugs.do-api.dev/"
67 | type = string
68 | default = "s-2vcpu-4gb-intel"
69 | }
70 |
71 | variable "digital_ocean_image" {
72 | description = "Default Image for Ubuntu 20.04 LTS with Docker"
73 | type = string
74 | default = "docker-20-04"
75 | }
76 |
77 | variable "kasm_build_url" {
78 | description = "The Kasm build file to install"
79 | type = string
80 | default = "https://kasm-static-content.s3.amazonaws.com/kasm_release_1.12.0.d4fd8a.tar.gz"
81 | }
82 |
83 | variable "user_password" {
84 | description = "The default password to be used for the default user@kasm.local account. Only use alphanumeric characters"
85 | type = string
86 | default = "changeme"
87 |
88 | validation {
89 | condition = can(regex("^[a-zA-Z0-9]{12,30}$", var.user_password))
90 | error_message = "The User Password should be a string between 12 and 30 letters or numbers with no special characters."
91 | }
92 | }
93 |
94 | variable "admin_password" {
95 | description = "The default password to be used for the default admin@kasm.local account. Only use alphanumeric characters"
96 | default = "changeme"
97 | type = string
98 |
99 | validation {
100 | condition = can(regex("^[a-zA-Z0-9]{12,30}$", var.admin_password))
101 | error_message = "The Admin Password should be a string between 12 and 30 letters or numbers with no special characters."
102 | }
103 | }
104 |
105 | variable "allow_ssh_cidrs" {
106 | description = "CIDR notation of the bastion host allowed to SSH in to the machines"
107 | type = list(string)
108 | default = ["0.0.0.0/0"]
109 |
110 | validation {
111 | condition = alltrue([for subnet in var.allow_ssh_cidrs : can(cidrhost(subnet, 0))])
112 | error_message = "One of the subnets provided in the allow_ssh_cidrs list is invalid."
113 | }
114 | }
115 |
116 | variable "allow_kasm_web_cidrs" {
117 | description = "CIDR notation of the bastion host allowed to SSH in to the machines"
118 | type = list(string)
119 | default = ["0.0.0.0/0"]
120 |
121 | validation {
122 | condition = alltrue([for subnet in var.allow_kasm_web_cidrs : can(cidrhost(subnet, 0))])
123 | error_message = "One of the subnets provided in the allow_ssh_cidrs list is invalid."
124 | }
125 | }
126 |
127 | variable "swap_size" {
128 | description = "The amount of swap (in GB) to configure inside the compute instances"
129 | type = number
130 |
131 | validation {
132 | condition = var.swap_size >= 1 && var.swap_size <= 8 && floor(var.swap_size) == var.swap_size
133 | error_message = "Swap size is the amount of disk space to use for Kasm in GB and must be an integer between 1 and 8."
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/gcp/MULTI_REGION.md:
--------------------------------------------------------------------------------
1 | # GCP Multi-Server Single Region
2 |
3 | This project will deploy Kasm Workspaces in a multi-server deployment in GCP within multiple regions of your choice. Each Kasm server role is placed in a separate subnet and you can optionally forward traffic from user sessions on the Kasm Agent through a NAT Gateway.
4 |
5 | > **NOTE:** Make sure you read and understand the [GCP requirements](./README.md) before continuing!
6 |
7 | ![Diagram][Image_Diagram]
8 |
9 | [Image_Diagram]: https://f.hubspotusercontent30.net/hubfs/5856039/terraform/diagrams/updated/gcp-multi-region.png "Diagram"
10 |
11 |
12 | # Pre-Configuration
13 |
14 | Consider creating a separate GCP Project for the Kasm deployment.
15 |
16 | ### DNS Zone
17 |
18 | There are a couple of DNS options available with this GCP Terraform. Regardless of method, Terraform will:
19 | - Add a DNS record for the load balancer
20 | - Add a private DNS zone and add records for the private load balancer used by Agents to communicate with the webapps
21 |
22 | 1. Create and verify the public DNS zone before deploying Terraform
23 | - Using this method, you will create a DNS zone or use an existing DNS zone in the same GCP Project where you deploy Kasm
24 |
25 | 2. Allow Terraform to create the public DNS zone for you
26 | - Using this method, Terraform will create a public DNS zone using the values you provide, and you must manually add the name server (NS) records to the parent DNS zone so queries are forwarded correctly
27 |
28 | ### Create Terraform service account and generate an API key
29 |
30 | Create a GCP Service Account to use with Terraform (https://cloud.google.com/iam/docs/service-accounts-create), and generate an API key. Once the API Key credential file is downloaded, copy it's contents into the `gcp_credentials.json` file in this directory, and Terraform will use these credentials to perform all operations.
31 |
32 | Recommended Service Account roles:
33 | - Compute Admin
34 | - DNS Administrator
35 | - Network Management Admin
36 | - Service Account Admin
37 |
38 | ### GCP APIs to enable before running Terraform
39 |
40 | There are several GCP service APIs that must be enabled before this Terraform can build successfully. In your GCP project, navigate to each of these and ensure they are enabled before running the Terraform configuration stage below.
41 |
42 | GCP APIs:
43 | - Cloud DNS
44 | - Cloud NAT
45 |
46 | # Terraform Configuration
47 |
48 | 1. Initialize the project
49 |
50 | terraform init
51 |
52 | 2. Open `terraform.tfvars` and update the variable values. The variable definitions, descriptions, and validation expectations can be found in the `variables.tf` file, or in the [README](./README.md).
53 |
54 | > In order to deploy this in multiple regions, simply add all additional regions in the `kasm_deployment_regions` variable in the `terraform.tfvars` file. The first region in the list is where the Database will be deployed, thus, it is recommended to put this closest to those who will be responsible for Database administration to reduce network complexity and DB latency.
55 |
56 |
57 | 3. Verify the configuration
58 |
59 | terraform plan
60 |
61 | 4. Deploy
62 |
63 | terraform apply
64 |
65 | 5. Login to the Deployment as an Admin via the domain defined e.g `https://kasm.contoso.com`
66 |
67 | > NOTE: The Load Balancer certificate can take between 15-45 min. to become active so you can access your Kasm deployment.
68 |
69 | 6. Navigate to the Agents tab, and enable each Agent after it checks in. (May take a few minutes)
70 |
71 |
72 | # Detailed Terraform Deployment Diagram
73 |
74 | ![Detailed Diagram][Detailed_Diagram]
75 |
76 | [Detailed_Diagram]: ./diagram/gcp_multi_region.png "Detailed Diagram"
77 |
--------------------------------------------------------------------------------
/gcp/MULTI_SERVER.md:
--------------------------------------------------------------------------------
1 | # GCP Multi-Server Single Region
2 | This project will deploy Kasm Workspaces in a multi-server deployment in GCP within a single region of your choice. Each Kasm server role is placed in a separate subnet and you can optionally forward traffic from user sessions on the Kasm Agent through a NAT Gateway.
3 |
4 | > **NOTE:** Make sure you read and understand the [GCP requirements](./README.md) before continuing!
5 |
6 | ![Diagram][Image_Diagram]
7 |
8 | [Image_Diagram]: https://f.hubspotusercontent30.net/hubfs/5856039/terraform/diagrams/updated/gcp-multi-server.png "Diagram"
9 |
10 |
11 | # Pre-Configuration
12 |
13 | Consider creating a separate GCP Project for the Kasm deployment.
14 |
15 | ### DNS Zone
16 |
17 | There are a couple of DNS options available with this GCP Terraform. Regardless of method, Terraform will:
18 | - Add a DNS record for the load balancer
19 | - Add a private DNS zone and add records for the private load balancer used by Agents to communicate with the webapps
20 |
21 | 1. Create and verify the public DNS zone before deploying Terraform
22 | - Using this method, you will create a DNS zone or use an existing DNS zone in the same GCP Project where you deploy Kasm
23 |
24 | 2. Allow Terraform to create the public DNS zone for you
25 | - Using this method, Terraform will create a public DNS zone using the values you provide, and you must manually add the name server (NS) records to the parent DNS zone so queries are forwarded correctly
26 |
27 | ### Create Terraform service account and generate an API key
28 |
29 | Create a GCP Service Account to use with Terraform (https://cloud.google.com/iam/docs/service-accounts-create), and generate an API key. Once the API Key credential file is downloaded, copy it's contents into the `gcp_credentials.json` file in this directory, and Terraform will use these credentials to perform all operations.
30 |
31 | Recommended Service Account roles:
32 | - Compute Admin
33 | - DNS Administrator
34 | - Network Management Admin
35 | - Service Account Admin
36 |
37 | ### GCP APIs to enable before running Terraform
38 |
39 | There are several GCP service APIs that must be enabled before this Terraform can build successfully. In your GCP project, navigate to each of these and ensure they are enabled before running the Terraform configuration stage below.
40 |
41 | GCP APIs:
42 | - Cloud DNS
43 | - Cloud NAT
44 |
45 | # Terraform Configuration
46 |
47 | 1. Initialize the project
48 |
49 | terraform init
50 |
51 | 2. Open `terraform.tfvars` and update the variable values. The variable definitions, descriptions, and validation expectations can be found in the `variables.tf` file, or in the [README](./README.md).
52 |
53 |
54 | 3. Verify the configuration
55 |
56 | terraform plan
57 |
58 | 4. Deploy
59 |
60 | terraform apply
61 |
62 | 5. Login to the Deployment as an Admin via the domain defined e.g `https://kasm.contoso.com`
63 |
64 | > NOTE: The Load Balancer certificate can take between 15-45 min. to become active so you can access your Kasm deployment.
65 |
66 | 6. Navigate to the Agents tab, and enable each Agent after it checks in. (May take a few minutes)
67 |
68 |
69 | # Detailed Terraform Deployment Diagram
70 |
71 | ![Detailed Diagram][Detailed_Diagram]
72 |
73 | [Detailed_Diagram]: ./diagram/gcp_multi_server.png "Detailed Diagram"
74 |
--------------------------------------------------------------------------------
/gcp/diagram/gcp_multi_region.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kasmtech/terraform/f8618bc382790d4684127b68e3319319d3afefb0/gcp/diagram/gcp_multi_region.png
--------------------------------------------------------------------------------
/gcp/diagram/gcp_multi_server.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kasmtech/terraform/f8618bc382790d4684127b68e3319319d3afefb0/gcp/diagram/gcp_multi_server.png
--------------------------------------------------------------------------------
/gcp/gcp_credentials.json:
--------------------------------------------------------------------------------
1 | replaceme
--------------------------------------------------------------------------------
/gcp/modules/certificate_manager/README.md:
--------------------------------------------------------------------------------
1 |
2 | ## Requirements
3 |
4 | | Name | Version |
5 | |------|---------|
6 | | [terraform](#requirement\_terraform) | ~> 1.0 |
7 | | [google](#requirement\_google) | ~> 4.0 |
8 |
9 | ## Providers
10 |
11 | | Name | Version |
12 | |------|---------|
13 | | [google](#provider\_google) | 4.81.0 |
14 |
15 | ## Modules
16 |
17 | No modules.
18 |
19 | ## Resources
20 |
21 | | Name | Type |
22 | |------|------|
23 | | [google_certificate_manager_certificate.cert](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/certificate_manager_certificate) | resource |
24 | | [google_certificate_manager_certificate_map.cert_map](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/certificate_manager_certificate_map) | resource |
25 | | [google_certificate_manager_certificate_map_entry.cert_map_entry](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/certificate_manager_certificate_map_entry) | resource |
26 | | [google_certificate_manager_dns_authorization.cert_auth](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/certificate_manager_dns_authorization) | resource |
27 | | [google_dns_record_set.cert_dns_record](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/dns_record_set) | resource |
28 |
29 | ## Inputs
30 |
31 | | Name | Description | Type | Default | Required |
32 | |------|-------------|------|---------|:--------:|
33 | | [certificate\_description](#input\_certificate\_description) | Certificate description | `string` | `"Global Load Balancer SSL Certificate"` | no |
34 | | [certificate\_dns\_authorization\_description](#input\_certificate\_dns\_authorization\_description) | Description of the DNS Authorization job in Certificate Manager | `string` | `"Global Load Balancer certificate DNS authorization"` | no |
35 | | [certificate\_dns\_authorization\_name](#input\_certificate\_dns\_authorization\_name) | The name of the DNS Authorization job in Certificate Manager | `string` | n/a | yes |
36 | | [certificate\_map\_description](#input\_certificate\_map\_description) | Description of the certificate map | `string` | `"Global HTTPS Load Balancer Certificate Map"` | no |
37 | | [certificate\_map\_name](#input\_certificate\_map\_name) | Certificate map name | `string` | n/a | yes |
38 | | [certificate\_name](#input\_certificate\_name) | Certificate name in Certificate manager. Must be globally unique. | `string` | n/a | yes |
39 | | [certificate\_scope](#input\_certificate\_scope) | GCP Certificate scope | `string` | `"DEFAULT"` | no |
40 | | [create\_wildcard](#input\_create\_wildcard) | Add wildcard domain to certificate | `bool` | `true` | no |
41 | | [dns\_managed\_zone\_name](#input\_dns\_managed\_zone\_name) | The name of the GCP DNS zone | `string` | n/a | yes |
42 | | [domain\_name](#input\_domain\_name) | Kasm deployment domain name | `string` | n/a | yes |
43 | | [resource\_labels](#input\_resource\_labels) | Labels to add to all created resources in this project | `map(any)` | `{}` | no |
44 |
45 | ## Outputs
46 |
47 | | Name | Description |
48 | |------|-------------|
49 | | [certificate\_map\_id](#output\_certificate\_map\_id) | The value of the generated certificate map for use with the external load balancer |
50 |
51 |
--------------------------------------------------------------------------------
/gcp/modules/certificate_manager/certificate_manager.tf:
--------------------------------------------------------------------------------
1 | resource "google_certificate_manager_dns_authorization" "cert_auth" {
2 | name = var.certificate_dns_authorization_name
3 | description = var.certificate_dns_authorization_description
4 | domain = var.domain_name
5 | labels = var.resource_labels
6 | }
7 |
8 | resource "google_dns_record_set" "cert_dns_record" {
9 | name = google_certificate_manager_dns_authorization.cert_auth.dns_resource_record[0].name
10 | type = "CNAME"
11 | ttl = 30
12 | managed_zone = var.dns_managed_zone_name
13 | rrdatas = [google_certificate_manager_dns_authorization.cert_auth.dns_resource_record[0].data]
14 | }
15 |
16 | resource "google_certificate_manager_certificate" "cert" {
17 | name = var.certificate_name
18 | description = var.certificate_description
19 | scope = var.certificate_scope
20 | labels = var.resource_labels
21 |
22 | managed {
23 | domains = compact([
24 | google_certificate_manager_dns_authorization.cert_auth.domain,
25 | var.create_wildcard ? "*.${google_certificate_manager_dns_authorization.cert_auth.domain}" : ""
26 | ])
27 | dns_authorizations = [
28 | google_certificate_manager_dns_authorization.cert_auth.id
29 | ]
30 | }
31 |
32 | depends_on = [google_dns_record_set.cert_dns_record]
33 | }
34 |
35 | resource "google_certificate_manager_certificate_map" "cert_map" {
36 | name = var.certificate_map_name
37 | description = var.certificate_map_description
38 | labels = var.resource_labels
39 | }
40 |
41 | resource "google_certificate_manager_certificate_map_entry" "cert_map_entry" {
42 | name = "${var.certificate_map_name}-entry"
43 | map = google_certificate_manager_certificate_map.cert_map.name
44 | certificates = [google_certificate_manager_certificate.cert.id]
45 | matcher = "PRIMARY"
46 | labels = var.resource_labels
47 | }
48 |
--------------------------------------------------------------------------------
/gcp/modules/certificate_manager/outputs.tf:
--------------------------------------------------------------------------------
1 | output "certificate_map_id" {
2 | description = "The value of the generated certificate map for use with the external load balancer"
3 | value = google_certificate_manager_certificate_map.cert_map.id
4 | }
5 |
--------------------------------------------------------------------------------
/gcp/modules/certificate_manager/provider.tf:
--------------------------------------------------------------------------------
1 | terraform {
2 | required_version = "~> 1.0"
3 | required_providers {
4 | google = {
5 | source = "hashicorp/google"
6 | version = "~> 5.0"
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/gcp/modules/certificate_manager/variables.tf:
--------------------------------------------------------------------------------
1 | variable "domain_name" {
2 | description = "Kasm deployment domain name"
3 | type = string
4 | }
5 |
6 | variable "dns_managed_zone_name" {
7 | description = "The name of the GCP DNS zone"
8 | type = string
9 | }
10 |
11 | variable "certificate_name" {
12 | description = "Certificate name in Certificate manager. Must be globally unique."
13 | type = string
14 |
15 | validation {
16 | condition = can(regex("^[a-z0-9-]{5,63}", var.certificate_name))
17 | error_message = "The certificate_name must be globally unique, and can only be between 5-63 characters consisting of only lower case letters, number, and dash (-)."
18 | }
19 | }
20 |
21 | variable "certificate_dns_authorization_name" {
22 | description = "The name of the DNS Authorization job in Certificate Manager"
23 | type = string
24 |
25 | validation {
26 | condition = can(regex("^[a-z0-9-]{5,63}", var.certificate_dns_authorization_name))
27 | error_message = "The certificate_dns_authorization_name must be globally unique, and can only be between 5-63 characters consisting of only lower case letters, number, and dash (-)."
28 | }
29 | }
30 |
31 | variable "certificate_map_name" {
32 | description = "Certificate map name"
33 | type = string
34 |
35 | validation {
36 | condition = can(regex("^[a-z0-9-]{5,63}", var.certificate_map_name))
37 | error_message = "The certificate_map_name must be globally unique, and can only be between 5-63 characters consisting of only lower case letters, number, and dash (-)."
38 | }
39 | }
40 |
41 | ## Pre-set values
42 | variable "certificate_description" {
43 | description = "Certificate description"
44 | type = string
45 | default = "Global Load Balancer SSL Certificate"
46 | }
47 |
48 | variable "certificate_dns_authorization_description" {
49 | description = "Description of the DNS Authorization job in Certificate Manager"
50 | type = string
51 | default = "Global Load Balancer certificate DNS authorization"
52 | }
53 |
54 | variable "certificate_map_description" {
55 | description = "Description of the certificate map"
56 | type = string
57 | default = "Global HTTPS Load Balancer Certificate Map"
58 | }
59 |
60 | variable "certificate_scope" {
61 | description = "GCP Certificate scope"
62 | type = string
63 | default = "DEFAULT"
64 |
65 | validation {
66 | condition = contains(["DEFAULT", "EDGE_CACHE"], var.certificate_scope)
67 | error_message = "The certificate_scope variable can only be one of: DEFAULT or EDGE_CACHE."
68 | }
69 | }
70 |
71 | variable "create_wildcard" {
72 | description = "Add wildcard domain to certificate"
73 | type = bool
74 | default = true
75 | }
76 |
77 | variable "resource_labels" {
78 | description = "Labels to add to all created resources in this project"
79 | type = map(any)
80 | default = {}
81 | }
82 |
--------------------------------------------------------------------------------
/gcp/modules/compute_instance/compute.tf:
--------------------------------------------------------------------------------
1 | data "google_compute_image" "kasm_image" {
2 | project = var.source_image[0].project
3 | family = var.source_image[0].family
4 | name = var.source_image[0].source_image
5 | }
6 |
7 | data "google_compute_zones" "available" {
8 | region = var.kasm_region
9 | }
10 |
11 | resource "google_compute_instance" "kasm_instance" {
12 | count = var.number_of_instances
13 | name = var.instance_details.name == "" ? substr("${var.kasm_region}-${var.instance_details.name_prefix}-0${count.index + 1}", 0, 63) : var.instance_details.name
14 | description = var.instance_details.description
15 | machine_type = var.instance_details.machine_type
16 | deletion_protection = var.instance_details.instance_role == "agent" ? false : var.instance_delete_protection
17 | tags = var.security_tags
18 | labels = var.resource_labels
19 | allow_stopping_for_update = var.allow_stopping_for_update
20 | metadata_startup_script = var.cloud_init_script[count.index]
21 | zone = data.google_compute_zones.available.names[count.index]
22 |
23 | boot_disk {
24 | auto_delete = var.instance_details.disk_auto_delete
25 | initialize_params {
26 | size = var.instance_details.disk_size_gb
27 | type = var.instance_details.disk_type
28 | image = data.google_compute_image.kasm_image.self_link
29 | labels = var.resource_labels
30 | }
31 | }
32 |
33 | network_interface {
34 | network = var.instance_network
35 | subnetwork = var.instance_subnetwork
36 | nic_type = var.instance_nic_type
37 | stack_type = var.instance_nic_stack_type
38 |
39 | dynamic "access_config" {
40 | for_each = var.public_access_config
41 | content {
42 | nat_ip = lookup(access_config.value, "nat_ip", null)
43 | public_ptr_domain_name = lookup(access_config.value, "public_ptr_domain_name", null)
44 | network_tier = lookup(access_config.value, "network_tier", null)
45 | }
46 | }
47 | }
48 |
49 | shielded_instance_config {
50 | enable_secure_boot = var.enable_secure_boot
51 | enable_vtpm = var.enable_instance_vtpm
52 | enable_integrity_monitoring = var.enable_integrity_monitoring
53 | }
54 |
55 | dynamic "service_account" {
56 | for_each = var.service_account
57 | content {
58 | email = lookup(service_account.value, "email", null)
59 | scopes = lookup(service_account.value, "scopes", null)
60 | }
61 | }
62 |
63 | lifecycle {
64 | ignore_changes = [
65 | metadata["ssh-keys"]
66 | ]
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/gcp/modules/compute_instance/outputs.tf:
--------------------------------------------------------------------------------
1 | ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ##
2 | ## Deploy Ksam Database
3 | ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ##
4 | output "private_ip" {
5 | description = "Instance private IP address."
6 | value = google_compute_instance.kasm_instance[*].network_interface[0].network_ip
7 | }
8 |
9 | output "public_ip" {
10 | description = "Instance public IP address (if applicable)"
11 | value = google_compute_instance.kasm_instance[*].network_interface[0].access_config
12 | }
13 |
--------------------------------------------------------------------------------
/gcp/modules/compute_instance/provider.tf:
--------------------------------------------------------------------------------
1 | terraform {
2 | required_version = "~> 1.0"
3 | required_providers {
4 | google = {
5 | source = "hashicorp/google"
6 | version = "~> 5.0"
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/gcp/modules/dns_records/README.md:
--------------------------------------------------------------------------------
1 |
2 | ## Requirements
3 |
4 | | Name | Version |
5 | |------|---------|
6 | | [terraform](#requirement\_terraform) | ~> 1.0 |
7 | | [google](#requirement\_google) | ~> 4.0 |
8 |
9 | ## Providers
10 |
11 | | Name | Version |
12 | |------|---------|
13 | | [google](#provider\_google) | 4.81.0 |
14 |
15 | ## Modules
16 |
17 | No modules.
18 |
19 | ## Resources
20 |
21 | | Name | Type |
22 | |------|------|
23 | | [google_dns_record_set.records](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/dns_record_set) | resource |
24 |
25 | ## Inputs
26 |
27 | | Name | Description | Type | Default | Required |
28 | |------|-------------|------|---------|:--------:|
29 | | [domain](#input\_domain) | Zone domain, must end with a period. | `string` | n/a | yes |
30 | | [name](#input\_name) | Zone name, must be unique within the project. | `string` | n/a | yes |
31 | | [project\_id](#input\_project\_id) | Project id for the zone. | `string` | n/a | yes |
32 | | [recordsets](#input\_recordsets) | List of DNS record objects to manage, in the standard terraform dns structure. | list(object({
name = string
type = string
ttl = number
records = list(string)
}))
| `[]` | no |
33 |
34 | ## Outputs
35 |
36 | No outputs.
37 |
38 |
--------------------------------------------------------------------------------
/gcp/modules/dns_records/dns.tf:
--------------------------------------------------------------------------------
1 | resource "google_dns_record_set" "records" {
2 | project = var.project_id
3 | managed_zone = var.name
4 |
5 | for_each = { for record in var.recordsets : join("/", [record.name, record.type]) => record }
6 | name = (each.value.name != "" ? "${each.value.name}.${var.domain}." : "${var.domain}.")
7 | type = each.value.type
8 | ttl = each.value.ttl
9 |
10 | rrdatas = each.value.records
11 | }
12 |
--------------------------------------------------------------------------------
/gcp/modules/dns_records/provider.tf:
--------------------------------------------------------------------------------
1 | terraform {
2 | required_version = "~> 1.0"
3 | required_providers {
4 | google = {
5 | source = "hashicorp/google"
6 | version = "~> 5.0"
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/gcp/modules/dns_records/variables.tf:
--------------------------------------------------------------------------------
1 | variable "project_id" {
2 | description = "Project id for the zone."
3 | type = string
4 | }
5 |
6 | variable "domain" {
7 | description = "Zone domain, must end with a period."
8 | type = string
9 | }
10 |
11 | variable "name" {
12 | description = "Zone name, must be unique within the project."
13 | type = string
14 | }
15 |
16 | variable "recordsets" {
17 | type = list(object({
18 | name = string
19 | type = string
20 | ttl = number
21 | records = list(string)
22 | }))
23 | description = "List of DNS record objects to manage, in the standard terraform dns structure."
24 | default = []
25 | }
26 |
--------------------------------------------------------------------------------
/gcp/modules/private_load_balancer/README copy.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kasmtech/terraform/f8618bc382790d4684127b68e3319319d3afefb0/gcp/modules/private_load_balancer/README copy.md
--------------------------------------------------------------------------------
/gcp/modules/private_load_balancer/load_balancer.tf:
--------------------------------------------------------------------------------
1 | data "google_compute_network" "network" {
2 | name = var.network
3 | project = var.network_project == "" ? var.project : var.network_project
4 | }
5 |
6 | data "google_compute_subnetwork" "network" {
7 | name = var.subnetwork
8 | project = var.network_project == "" ? var.project : var.network_project
9 | region = var.region
10 | }
11 |
12 | resource "google_compute_forwarding_rule" "default" {
13 | project = var.project
14 | name = var.name
15 | region = var.region
16 | network = data.google_compute_network.network.self_link
17 | subnetwork = data.google_compute_subnetwork.network.self_link
18 | allow_global_access = var.global_access
19 | load_balancing_scheme = var.load_balancing_scheme
20 | network_tier = "PREMIUM"
21 | ip_protocol = var.ip_protocol
22 | port_range = var.port_range
23 | labels = var.labels
24 | target = google_compute_region_target_tcp_proxy.default.self_link
25 | }
26 |
27 | resource "google_compute_region_target_tcp_proxy" "default" {
28 | region = var.region
29 | name = "${var.name}-target-proxy"
30 | proxy_header = "NONE"
31 | backend_service = google_compute_region_backend_service.default.self_link
32 | }
33 |
34 | resource "google_compute_region_backend_service" "default" {
35 | project = var.project
36 | name = "${var.name}-backend-service"
37 | region = var.region
38 | protocol = var.ip_protocol
39 | locality_lb_policy = var.load_balancing_policy
40 | port_name = var.named_port
41 | session_affinity = var.session_affinity
42 | timeout_sec = var.backend_timeout_sec
43 | load_balancing_scheme = "INTERNAL_MANAGED"
44 | connection_draining_timeout_sec = var.connection_draining_timeout_sec
45 | dynamic "backend" {
46 | for_each = var.backends
47 | content {
48 | group = lookup(backend.value, "group", null)
49 | description = lookup(backend.value, "description", null)
50 | max_utilization = lookup(backend.value, "max_utilization", null)
51 | balancing_mode = lookup(backend.value, "balancing_mode", null)
52 | capacity_scaler = lookup(backend.value, "capacity_scaler", null)
53 | failover = lookup(backend.value, "failover", null)
54 | }
55 | }
56 | health_checks = [google_compute_region_health_check.tcp.self_link]
57 | }
58 |
59 | resource "google_compute_region_health_check" "tcp" {
60 | project = var.project
61 | name = "${var.name}-hc-tcp"
62 | region = var.region
63 | timeout_sec = var.health_check["timeout_sec"]
64 | check_interval_sec = var.health_check["check_interval_sec"]
65 | healthy_threshold = var.health_check["healthy_threshold"]
66 | unhealthy_threshold = var.health_check["unhealthy_threshold"]
67 |
68 | https_health_check {
69 | port = var.health_check["port"]
70 | port_name = var.health_check["port_name"]
71 | request_path = var.health_check["request_path"]
72 | proxy_header = var.health_check["proxy_header"]
73 | }
74 |
75 | dynamic "log_config" {
76 | for_each = var.health_check["enable_log"] ? [true] : []
77 | content {
78 | enable = true
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/gcp/modules/private_load_balancer/outputs.tf:
--------------------------------------------------------------------------------
1 | output "ip_address" {
2 | description = "The internal IP assigned to the regional forwarding rule."
3 | value = google_compute_forwarding_rule.default.ip_address
4 | }
5 |
6 | output "forwarding_rule" {
7 | description = "The forwarding rule self_link."
8 | value = google_compute_forwarding_rule.default.self_link
9 | }
10 |
11 | output "forwarding_rule_id" {
12 | description = "The forwarding rule id."
13 | value = google_compute_forwarding_rule.default.id
14 | }
15 |
--------------------------------------------------------------------------------
/gcp/modules/private_load_balancer/provider.tf:
--------------------------------------------------------------------------------
1 | terraform {
2 | required_version = "~> 1.0"
3 | required_providers {
4 | google = {
5 | source = "hashicorp/google"
6 | version = "~> 5.0"
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/gcp/modules/private_load_balancer/variables.tf:
--------------------------------------------------------------------------------
1 | variable "project" {
2 | description = "The project to deploy to, if not set the default provider project is used."
3 | type = string
4 | default = ""
5 | }
6 |
7 | variable "region" {
8 | description = "Region for cloud resources."
9 | type = string
10 | }
11 |
12 | variable "global_access" {
13 | description = "Allow all regions on the same VPC network access."
14 | type = bool
15 | default = false
16 | }
17 |
18 | variable "network" {
19 | description = "Name of the network to create resources in."
20 | type = string
21 | default = "default"
22 | }
23 |
24 | variable "subnetwork" {
25 | description = "Name of the subnetwork to create resources in."
26 | type = string
27 | default = "default"
28 | }
29 |
30 | variable "network_project" {
31 | description = "Name of the project for the network. Useful for shared VPC. Default is var.project."
32 | type = string
33 | default = ""
34 | }
35 |
36 | variable "name" {
37 | description = "Name for the forwarding rule and prefix for supporting resources."
38 | type = string
39 | }
40 |
41 | variable "backends" {
42 | description = "List of backends, should be a map of key-value pairs for each backend, must have the 'group' key."
43 | type = list(any)
44 | }
45 |
46 | variable "backend_timeout_sec" {
47 | description = "Backend timeout"
48 | type = number
49 | default = 30
50 | }
51 |
52 | variable "load_balancing_scheme" {
53 | description = "The load balancing scheme to use. The default is INTERNAL"
54 | type = string
55 | default = "INTERNAL_MANAGED"
56 | }
57 |
58 | variable "load_balancing_policy" {
59 | description = "The load balancing policy to use"
60 | type = string
61 | default = "ROUND_ROBIN"
62 | }
63 |
64 | variable "named_port" {
65 | description = "Named port to allow access to the LB or resources through the VPC FW"
66 | type = string
67 | }
68 |
69 | variable "session_affinity" {
70 | description = "The session affinity for the backends example: NONE, CLIENT_IP. Default is `NONE`."
71 | type = string
72 | default = "NONE"
73 | }
74 |
75 | variable "port_range" {
76 | description = "List of ports range to forward to backend services. Max is 5."
77 | type = string
78 | }
79 |
80 | variable "health_check" {
81 | description = "Health check to determine whether instances are responsive and able to do work"
82 | type = object({
83 | type = string
84 | check_interval_sec = number
85 | healthy_threshold = number
86 | timeout_sec = number
87 | unhealthy_threshold = number
88 | response = string
89 | proxy_header = string
90 | port = number
91 | port_name = string
92 | request = string
93 | request_path = string
94 | host = string
95 | enable_log = bool
96 | })
97 | }
98 |
99 | variable "ip_protocol" {
100 | description = "The IP protocol for the backend and frontend forwarding rule. TCP or UDP."
101 | type = string
102 | default = "TCP"
103 | }
104 |
105 | variable "connection_draining_timeout_sec" {
106 | description = "Time for which instance will be drained"
107 | default = 300
108 | type = number
109 | }
110 |
111 | variable "labels" {
112 | description = "The labels to attach to resources created by this module."
113 | default = {}
114 | type = map(string)
115 | }
116 |
--------------------------------------------------------------------------------
/gcp/modules/random/README.md:
--------------------------------------------------------------------------------
1 |
2 | ## Requirements
3 |
4 | | Name | Version |
5 | |------|---------|
6 | | [terraform](#requirement\_terraform) | ~> 1.0 |
7 | | [random](#requirement\_random) | ~> 3.0 |
8 |
9 | ## Providers
10 |
11 | | Name | Version |
12 | |------|---------|
13 | | [random](#provider\_random) | 3.5.1 |
14 |
15 | ## Modules
16 |
17 | No modules.
18 |
19 | ## Resources
20 |
21 | | Name | Type |
22 | |------|------|
23 | | [random_password.password](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password) | resource |
24 |
25 | ## Inputs
26 |
27 | | Name | Description | Type | Default | Required |
28 | |------|-------------|------|---------|:--------:|
29 | | [kasm\_version](#input\_kasm\_version) | The version of kasm installed | `string` | n/a | yes |
30 |
31 | ## Outputs
32 |
33 | | Name | Description |
34 | |------|-------------|
35 | | [password](#output\_password) | # ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## # Auto-generated passwords # ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## |
36 |
37 |
--------------------------------------------------------------------------------
/gcp/modules/random/outputs.tf:
--------------------------------------------------------------------------------
1 | ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ##
2 | ## Auto-generated passwords
3 | ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ##
4 | output "password" {
5 | value = random_password.password.result
6 | sensitive = true
7 | }
8 |
--------------------------------------------------------------------------------
/gcp/modules/random/password.tf:
--------------------------------------------------------------------------------
1 | resource "random_password" "password" {
2 | keepers = {
3 | kasm_version = var.kasm_version
4 | }
5 |
6 | length = 30
7 | special = false
8 | }
9 |
--------------------------------------------------------------------------------
/gcp/modules/random/provider.tf:
--------------------------------------------------------------------------------
1 | terraform {
2 | required_version = "~> 1.0"
3 | required_providers {
4 | random = {
5 | source = "hashicorp/random"
6 | version = "~> 3.0"
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/gcp/modules/random/variables.tf:
--------------------------------------------------------------------------------
1 | variable "kasm_version" {
2 | description = "The version of kasm installed"
3 | type = string
4 | }
5 |
--------------------------------------------------------------------------------
/gcp/modules/service_account_iam/README.md:
--------------------------------------------------------------------------------
1 |
2 | ## Requirements
3 |
4 | | Name | Version |
5 | |------|---------|
6 | | [terraform](#requirement\_terraform) | ~> 1.0 |
7 | | [google](#requirement\_google) | ~> 4.0 |
8 |
9 | ## Providers
10 |
11 | | Name | Version |
12 | |------|---------|
13 | | [google](#provider\_google) | 4.81.0 |
14 |
15 | ## Modules
16 |
17 | | Name | Source | Version |
18 | |------|--------|---------|
19 | | [dns\_zone\_admin](#module\_dns\_zone\_admin) | terraform-google-modules/iam/google//modules/dns_zones_iam | ~> 7.6 |
20 | | [service\_account\_roles](#module\_service\_account\_roles) | terraform-google-modules/iam/google//modules/member_iam | ~> 7.6 |
21 |
22 | ## Resources
23 |
24 | | Name | Type |
25 | |------|------|
26 | | [google_service_account.service_account](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/service_account) | resource |
27 | | [google_service_account_key.kasm_key](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/service_account_key) | resource |
28 |
29 | ## Inputs
30 |
31 | | Name | Description | Type | Default | Required |
32 | |------|-------------|------|---------|:--------:|
33 | | [account\_id](#input\_account\_id) | The account id used to generate the service account email address | `string` | n/a | yes |
34 | | [display\_name](#input\_display\_name) | The service account display name | `string` | `""` | no |
35 | | [dns\_public\_zone\_name](#input\_dns\_public\_zone\_name) | Friendly name of the public DNS zone to manage. Only used if Direct to Agent is enabled. | `string` | `""` | no |
36 | | [manage\_dns](#input\_manage\_dns) | Allow the service account to add/delete DNS records for direct-to-agent. | `bool` | `false` | no |
37 | | [project\_id](#input\_project\_id) | Project id for the zone. | `string` | n/a | yes |
38 |
39 | ## Outputs
40 |
41 | | Name | Description |
42 | |------|-------------|
43 | | [connect\_details](#output\_connect\_details) | Kasm autoscale service account connect JSON output. NOTE: This contains sensitive data! |
44 |
45 |
--------------------------------------------------------------------------------
/gcp/modules/service_account_iam/outputs.tf:
--------------------------------------------------------------------------------
1 | ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ##
2 | ## Service Account details
3 | ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ##
4 | output "connect_details" {
5 | description = "Kasm autoscale service account connect JSON output. NOTE: This contains sensitive data!"
6 | value = google_service_account_key.kasm_key.private_key
7 | sensitive = true
8 | }
9 |
--------------------------------------------------------------------------------
/gcp/modules/service_account_iam/provider.tf:
--------------------------------------------------------------------------------
1 | terraform {
2 | required_version = "~> 1.0"
3 | required_providers {
4 | google = {
5 | source = "hashicorp/google"
6 | version = "~> 5.0"
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/gcp/modules/service_account_iam/service_account.tf:
--------------------------------------------------------------------------------
1 | resource "google_service_account" "service_account" {
2 | project = var.project_id
3 | account_id = var.account_id
4 | display_name = var.display_name
5 | }
6 |
7 | resource "google_service_account_key" "kasm_key" {
8 | service_account_id = google_service_account.service_account.id
9 | }
10 |
11 | module "service_account_roles" {
12 | source = "terraform-google-modules/iam/google//modules/member_iam"
13 | version = "~> 7.6"
14 |
15 | service_account_address = google_service_account.service_account.email
16 | project_id = var.project_id
17 | project_roles = ["roles/compute.instanceAdmin", "roles/iam.serviceAccountUser"]
18 | prefix = "serviceAccount"
19 | }
20 |
21 | module "dns_zone_admin" {
22 | source = "terraform-google-modules/iam/google//modules/dns_zones_iam"
23 | version = "~> 7.6"
24 | count = var.manage_dns ? 1 : 0
25 |
26 | project = var.project_id
27 | managed_zones = [var.dns_public_zone_name]
28 | mode = "additive"
29 | bindings = {
30 | "roles/dns.admin" = [
31 | "serviceAccount:${google_service_account.service_account.email}"
32 | ]
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/gcp/modules/service_account_iam/variables.tf:
--------------------------------------------------------------------------------
1 | variable "project_id" {
2 | description = "Project id for the zone."
3 | type = string
4 | }
5 |
6 | variable "account_id" {
7 | description = "The account id used to generate the service account email address"
8 | type = string
9 |
10 | validation {
11 | condition = can(regex("^([a-z]([-a-z0-9]*[a-z0-9]){6,30})", var.account_id))
12 | error_message = "The account_id must be between 6-30 characters beginning with a lower case letter, and consisting of lower case letters, numbers, or dash (-)."
13 | }
14 | }
15 |
16 | ## Pre-set values
17 | variable "display_name" {
18 | description = "The service account display name"
19 | type = string
20 | default = ""
21 | }
22 |
23 | variable "dns_public_zone_name" {
24 | description = "Friendly name of the public DNS zone to manage. Only used if Direct to Agent is enabled."
25 | type = string
26 | default = ""
27 | }
28 |
29 | variable "manage_dns" {
30 | description = "Allow the service account to add/delete DNS records for direct-to-agent."
31 | type = bool
32 | default = false
33 | }
34 |
--------------------------------------------------------------------------------
/gcp/outputs.tf:
--------------------------------------------------------------------------------
1 | ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ##
2 | ## Service Account details
3 | ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ##
4 | output "kasm_sa_account" {
5 | description = "Kasm Service Account connection details"
6 | value = var.show_sa_credentials ? module.kasm_autoscale_service_account[*].connect_details : null
7 | sensitive = true
8 | }
9 |
10 | output "kasm_passwords" {
11 | description = "Kasm login passwords"
12 | value = var.show_passwords ? {
13 | kasm_admin_password = local.admin_password
14 | kasm_user_password = local.user_password
15 | kasm_database_password = local.database_password
16 | kasm_redis_password = local.redis_password
17 | kasm_service_token = local.service_token
18 | kasm_manager_token = local.manager_token
19 | } : null
20 | sensitive = true
21 | }
22 |
--------------------------------------------------------------------------------
/gcp/provider.tf:
--------------------------------------------------------------------------------
1 | terraform {
2 | required_version = "~> 1.0"
3 | required_providers {
4 | google = {
5 | source = "hashicorp/google"
6 | version = "~> 5.0"
7 | }
8 | google-beta = {
9 | source = "hashicorp/google-beta"
10 | version = ">= 4.64, < 6"
11 | }
12 | random = {
13 | source = "hashicorp/random"
14 | version = "~> 3.0"
15 | }
16 | tls = {
17 | source = "hashicorp/tls"
18 | version = "~> 2.0"
19 | }
20 | }
21 | }
22 |
23 | provider "google" {
24 | project = var.project_id
25 | credentials = local.gcp_credentials
26 | }
27 |
28 | provider "google-beta" {
29 | project = var.project_id
30 | credentials = local.gcp_credentials
31 | }
32 |
--------------------------------------------------------------------------------
/gcp/terraform.tfvars:
--------------------------------------------------------------------------------
1 | ## Connection variables
2 | project_id = ""
3 | google_credential_file_path = "./gcp_credentials.json"
4 |
5 | ## VPC and deployment environment variables
6 | vpc_name = ""
7 | kasm_vpc_subnet = "10.0.0.0/16"
8 |
9 | ## Ensure the desired Database region is the first value in the list
10 | kasm_deployment_regions = ["us-east1"] # Use only one region for Multi-Server (single-region)
11 | #kasm_deployment_regions = ["us-west2", "asia-southeast1"] # Use multiple regions for Multi-Region deployment
12 |
13 | ## DNS Zone settings
14 | create_public_dns_zone = true
15 | public_dns_friendly_name = "kasm-public-dns-zone"
16 | private_dns_friendly_name = "kasm-private-dns-zone"
17 |
18 | ## Additional Kasm services or GCP features to deploy
19 | create_kasm_autoscale_service_account = true
20 | service_account_name = "kasm-autoscale"
21 | show_passwords = true
22 |
23 | ## Kasm variables
24 | kasm_domain_name = "example.kasmweb.com"
25 | kasm_project_name = ""
26 | deployment_type = "Multi-Region" # Valid values Multi-Region or Multi-Server
27 | kasm_version = "1.17.0"
28 | kasm_download_url = "https://kasm-static-content.s3.amazonaws.com/kasm_release_1.17.0.bbc15c.tar.gz"
29 |
30 | ## Kasm VM instance configurations
31 | # Webapp
32 | webapp_vm_instance_config = {
33 | machine_type = "e2-standard-2"
34 | disk_size_gb = "50"
35 | disk_type = "pd-standard"
36 | }
37 |
38 | # Database
39 | database_vm_instance_config = {
40 | name = "kasm-database"
41 | machine_type = "e2-standard-2"
42 | disk_size_gb = 50
43 | description = "Kasm database with Terraform"
44 | disk_auto_delete = true
45 | disk_type = "pd-balanced"
46 | instance_role = "database"
47 | }
48 |
49 | # Agent
50 | number_of_agents_per_region = 1
51 | enable_agent_nat_gateway = false
52 | agent_vm_instance_config = {
53 | name_prefix = "kasm-static-agent"
54 | machine_type = "e2-standard-2"
55 | disk_size_gb = 100
56 | description = "Kasm Static agent with Terraform"
57 | disk_auto_delete = true
58 | disk_type = "pd-balanced"
59 | instance_role = "agent"
60 | }
61 |
62 | # Connection Proxy (Guac)
63 | deploy_connection_proxy = false
64 | deploy_windows_hosts = false ## NOTE: This only creates the Windows subnet and firewall rules, it does NOT deploy Windows VMs
65 | cpx_vm_instance_config = {
66 | machine_type = "e2-standard-2"
67 | disk_size_gb = "50"
68 | disk_type = "pd-standard"
69 | }
70 |
--------------------------------------------------------------------------------
/gcp/userdata/agent_bootstrap.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | ## Download Kasm
4 | cd /tmp
5 | wget ${KASM_DOWNLOAD_URL}
6 | tar xvf kasm_*.tar.gz
7 |
8 | ## Create Swap partition
9 | fallocate -l 8g /mnt/kasm.swap
10 | chmod 600 /mnt/kasm.swap
11 | mkswap /mnt/kasm.swap
12 | swapon /mnt/kasm.swap
13 | echo '/mnt/kasm.swap swap swap defaults 0 0' | tee -a /etc/fstab
14 | apt update && apt install iputils-ping dnsutils netcat -y
15 |
16 | ## Verify connectivity with the webapp private load balancer
17 | while ! (curl -k https://${PRIVATE_LB_HOSTNAME}/api/__healthcheck 2>/dev/null | grep -q true)
18 | do
19 | echo "Waiting for API server..."
20 | sleep 5
21 | done
22 |
23 | ## Get Agent Private IP for Kasm registration
24 | connect_ip="`hostname -I | cut -d' ' -f1`"
25 |
26 | ## Install Kasm
27 | ## Kasm install arguments used:
28 | ## -S = Kasm role - agent in this case
29 | ## -H = Don't check for swap (since we created it already)
30 | ## -e = accept EULA
31 | ## -p = Agent IP address or hostname
32 | ## -m = Webapp private load balancer hostname
33 | ## -M = Manager token to use to register the agent
34 | ## Useful additional arguments:
35 | ## -O = use Rolling images (ensures the most up-to-date containers are used)
36 | bash kasm_release/install.sh -S agent -H -e -p $${connect_ip} -m ${PRIVATE_LB_HOSTNAME} -M ${KASM_MANAGER_TOKEN} ${ADDITIONAL_AGENT_INSTALL_ARGS}
37 |
38 | ## Install Nvidia drivers if this is a GPU-enabled agent.
39 | if [[ "${GPU_ENABLED}" == "1" ]]
40 | then
41 | apt-get update && apt-get upgrade -y
42 | apt-get install -y gcc make linux-headers-$(uname -r) linux-aws awscli
43 | cat << EOF | tee --append /etc/modprobe.d/blacklist.conf
44 | blacklist vga16fb
45 | blacklist nouveau
46 | blacklist rivafb
47 | blacklist nvidiafb
48 | blacklist rivatv
49 | EOF
50 | echo 'GRUB_CMDLINE_LINUX="rdblacklist=nouveau"' | tee --append /etc/default/grub
51 | update-grub
52 | aws s3 cp --no-sign-request --recursive s3://ec2-linux-nvidia-drivers/latest/ .
53 | chmod +x NVIDIA-Linux-x86_64*.run
54 | /bin/sh ./NVIDIA-Linux-x86_64*.run -s
55 | curl -fsSL "https://nvidia.github.io/nvidia-docker/gpgkey" | gpg --dearmor | tee /etc/apt/trusted.gpg.d/nvidia.gpg > /dev/null
56 | curl -s -L "https://nvidia.github.io/nvidia-docker/$(source /etc/os-release;echo $ID$VERSION_ID)/nvidia-docker.list" -o /etc/apt/sources.list.d/nvidia-docker.list
57 | apt-get update
58 | apt-get install -y nvidia-docker2
59 | systemctl restart docker
60 | docker restart kasm_agent
61 | fi
62 |
--------------------------------------------------------------------------------
/gcp/userdata/cpx_bootstrap.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | ## Download Kasm
4 | cd /tmp
5 | wget ${KASM_DOWNLOAD_URL}
6 | tar xvf kasm_*.tar.gz
7 |
8 | ## Create Swap partition
9 | fallocate -l 2g /mnt/kasm.swap
10 | chmod 600 /mnt/kasm.swap
11 | mkswap /mnt/kasm.swap
12 | swapon /mnt/kasm.swap
13 | echo '/mnt/kasm.swap swap swap defaults 0 0' | tee -a /etc/fstab
14 |
15 | ## Install useful packages
16 | apt update && apt install iputils-ping dnsutils netcat -y
17 |
18 | ## Make sure the CPX node can access the private load balancer
19 | while ! (curl -k https://${PRIVATE_LB_HOSTNAME}/api/__healthcheck 2>/dev/null | grep -q true)
20 | do
21 | echo "Waiting for API server..."
22 | sleep 5
23 | done
24 |
25 | ## Get CPX Private IP for Kasm registration
26 | connect_ip="`hostname -I | cut -d' ' -f1`"
27 |
28 | ## Install Kasm
29 | ## Kasm install arguments used:
30 | ## -S = Kasm role - guac in this case
31 | ## -H = Don't check for swap (since we created it already)
32 | ## -e = accept EULA
33 | ## -n = Private Load balancer URL for Kasm webapps
34 | ## -p = CPX Node private IP so webapp can connect to CPX
35 | ## -k = Service registration token required to register the CPX with Kasm
36 | ## -z = The Zone name to register the CPX node with
37 | ## Useful additional arguments:
38 | ## -O = use Rolling images (ensures the most up-to-date containers are used)
39 | bash kasm_release/install.sh -S guac -H -e -n ${PRIVATE_LB_HOSTNAME} -p $${connect_ip} -k ${KASM_SERVICE_TOKEN} -z ${KASM_ZONE_NAME} ${ADDITIONAL_CPX_INSTALL_ARGS}
40 |
--------------------------------------------------------------------------------
/gcp/userdata/database_bootstrap.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | ## Download Kasm
4 | cd /tmp
5 | wget ${KASM_DOWNLOAD_URL}
6 | tar xvf kasm_*.tar.gz
7 |
8 | ## Create swap partition
9 | fallocate -l 8g /mnt/kasm.swap
10 | chmod 600 /mnt/kasm.swap
11 | mkswap /mnt/kasm.swap
12 | swapon /mnt/kasm.swap
13 | echo '/mnt/kasm.swap swap swap defaults 0 0' | tee -a /etc/fstab
14 |
15 | ## Install useful packages
16 | apt update && apt install iputils-ping dnsutils netcat -y
17 |
18 | ## Install Kasm
19 | ## Kasm install arguments used:
20 | ## -S = Kasm role - db in this case
21 | ## -H = Don't check for swap (since we created it already)
22 | ## -e = accept EULA
23 | ## -Q = Database password
24 | ## -R = Redis password
25 | ## -U = Password to use for user@kasm.local built-in account
26 | ## -P = Password to use for admin@kasm.local built-in admin account
27 | ## -M = Management token to use for agent registration
28 | ## -k = Service registration token to use for Connection Proxy (Guac) registration
29 | ## Useful additional arguments:
30 | ## -O = use Rolling images (ensures the most up-to-date containers are used)
31 | bash kasm_release/install.sh -S db -e -Q ${KASM_DB_PASS} -R ${KASM_REDIS_PASS} -U ${KASM_USER_PASS} -P ${KASM_ADMIN_PASS} -M ${KASM_MANAGER_TOKEN} -k ${KASM_SERVICE_TOKEN} ${ADDITIONAL_DATABASE_INSTALL_ARGS}
32 |
--------------------------------------------------------------------------------
/gcp/userdata/remote_db_init.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | ## Download Kasm
4 | cd /tmp
5 | wget ${KASM_DOWNLOAD_URL}
6 | tar xvf kasm_*.tar.gz
7 |
8 | ## Create swap partition
9 | fallocate -l 2g /mnt/kasm.swap
10 | chmod 600 /mnt/kasm.swap
11 | mkswap /mnt/kasm.swap
12 | swapon /mnt/kasm.swap
13 | echo '/mnt/kasm.swap swap swap defaults 0 0' | tee -a /etc/fstab
14 |
15 | ## Install useful packages
16 | apt update && apt install iputils-ping dnsutils netcat -y
17 |
18 | ## Ensure connection to remote database before installing
19 | while ! nc -w 1 -z ${DATABASE_IP} 5432
20 | do
21 | echo "Waiting for DB connection..."
22 | sleep 10
23 | done
24 |
25 | ## Ensure connection to remote Redis before installing
26 | while ! nc -w 1 -z ${REDIS_IP} 6379
27 | do
28 | echo "Waiting for Redis connection..."
29 | sleep 10
30 | done
31 |
32 | ## Install Kasm
33 | ## Kasm install arguments used:
34 | ## -S = Kasm role - init_remote_db in this case
35 | ## -H = Don't check for swap (since we created it already)
36 | ## -e = accept EULA
37 | ## -q = Database IP or Hostname
38 | ## -Q = Database password
39 | ## -o = Redis IP or Hostname
40 | ## -R = Redis password
41 | ## -U = Password to use for user@kasm.local built-in account
42 | ## -P = Password to use for admin@kasm.local built-in admin account
43 | ## -M = Management token to use for agent registration
44 | ## -k = Service registration token to use for Connection Proxy (Guac) registration
45 | ## Useful additional arguments:
46 | ## -O = use Rolling images (ensures the most up-to-date containers are used)
47 | bash kasm_release/install_dependencies.sh
48 | bash kasm_release/install.sh -S init_remote_db -e -H -q ${DATABASE_IP} -Q ${KASM_DB_PASS} -U ${KASM_USER_PASS} -P ${KASM_ADMIN_PASS} -o ${REDIS_IP} -R ${KASM_REDIS_PASS} -M ${KASM_SERVICE_TOKEN} -g ${DB_MASTER_USER} -G ${DB_MASTER_PASSWORD} -k ${KASM_SERVICE_TOKEN} ${ADDITIONAL_DATABASE_INSTALL_ARGS}
49 |
--------------------------------------------------------------------------------
/gcp/userdata/webapp_bootstrap.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | ## Download Kasm
4 | cd /tmp
5 | wget ${KASM_DOWNLOAD_URL}
6 | tar xvf kasm_*.tar.gz
7 |
8 | ## Create Swap partition
9 | fallocate -l 2g /mnt/kasm.swap
10 | chmod 600 /mnt/kasm.swap
11 | mkswap /mnt/kasm.swap
12 | swapon /mnt/kasm.swap
13 | echo '/mnt/kasm.swap swap swap defaults 0 0' | tee -a /etc/fstab
14 |
15 | ## Install useful packages
16 | apt update && apt install iputils-ping dnsutils netcat-openbsd -y
17 |
18 | ## Test Database connectivity before installing
19 | while ! nc -w 1 -z ${DB_PRIVATE_IP} 5432
20 | do
21 | echo "Waiting for DB connection..."
22 | sleep 5
23 | done
24 |
25 | ## Test Redis connectivity before installing
26 | while ! nc -w 1 -z ${DB_PRIVATE_IP} 6379
27 | do
28 | echo "Waiting for Redis connection..."
29 | sleep 5
30 | done
31 |
32 | ## Install Kasm
33 | ## Kasm install arguments used:
34 | ## -S = Kasm role - webapp in this case
35 | ## -H = Don't check for swap (since we created it already)
36 | ## -e = accept EULA
37 | ## -q = Database Server IP
38 | ## -Q = Database password
39 | ## -R = Redis password
40 | ## -z = The Zone name to use for the webapp
41 | ## Useful additional arguments:
42 | ## -O = use Rolling images (ensures the most up-to-date containers are used)
43 | bash kasm_release/install.sh -S app -H -e -z ${KASM_ZONE_NAME} -q ${DB_PRIVATE_IP} -Q ${KASM_DB_PASS} -R ${KASM_REDIS_PASS} ${ADDITIONAL_WEBAPP_INSTALL_ARGS}
44 |
--------------------------------------------------------------------------------
/oci/single_server/deployment.tf:
--------------------------------------------------------------------------------
1 | module "kasm" {
2 | source = "./module"
3 | oci_domain_name = var.oci_domain_name
4 | project_name = var.project_name
5 | kasm_build_url = var.kasm_build_url
6 | vcn_subnet_cidr = var.vcn_subnet_cidr
7 |
8 | ## OCI Auth information
9 | tenancy_ocid = var.tenancy_ocid
10 | compartment_ocid = var.compartment_ocid
11 | user_ocid = var.user_ocid
12 | fingerprint = var.fingerprint
13 | private_key_path = var.private_key_path
14 | region = var.region
15 |
16 | ## SSL Certificate values
17 | # Let TF generate Let's Encrypt SSL Certificates automatically
18 | letsencrypt_cert_support_email = var.letsencrypt_cert_support_email
19 | letsencrypt_server_type = var.letsencrypt_server_type
20 | # Bring your own SSL Certificates
21 | kasm_ssl_crt_path = var.kasm_ssl_crt_path
22 | kasm_ssl_key_path = var.kasm_ssl_key_path
23 |
24 | instance_image_ocid = var.instance_image_ocid
25 | instance_shape = var.instance_shape
26 | swap_size = var.swap_size
27 | kasm_server_cpus = var.kasm_server_cpus
28 | kasm_server_memory = var.kasm_server_memory
29 | kasm_server_hdd_size = var.kasm_server_hdd_size
30 | allow_ssh_cidrs = var.allow_ssh_cidrs
31 | allow_web_cidrs = var.allow_web_cidrs
32 | ssh_authorized_keys = var.ssh_authorized_keys
33 |
34 | admin_password = var.admin_password
35 | user_password = var.user_password
36 | }
37 |
38 | output "ssh_key_info" {
39 | description = "SSH Keys to use with Kasm Deployment"
40 | value = module.kasm.ssh_key_info
41 | sensitive = true
42 | }
--------------------------------------------------------------------------------
/oci/single_server/diagram/oci_single_server.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kasmtech/terraform/f8618bc382790d4684127b68e3319319d3afefb0/oci/single_server/diagram/oci_single_server.png
--------------------------------------------------------------------------------
/oci/single_server/kasm_ssl.crt:
--------------------------------------------------------------------------------
1 | replaceme
--------------------------------------------------------------------------------
/oci/single_server/kasm_ssl.key:
--------------------------------------------------------------------------------
1 | replaceme
--------------------------------------------------------------------------------
/oci/single_server/module/dns.tf:
--------------------------------------------------------------------------------
1 | data "oci_dns_zones" "kasm_dns_zone" {
2 | compartment_id = var.compartment_ocid
3 | name = var.oci_domain_name
4 | }
5 |
6 | resource "oci_dns_rrset" "kasm_a_record" {
7 | domain = var.oci_domain_name
8 | rtype = "A"
9 | zone_name_or_id = var.oci_domain_name
10 | compartment_id = var.compartment_ocid
11 | items {
12 | domain = var.oci_domain_name
13 | rdata = oci_core_instance.kasm_instance.public_ip
14 | rtype = "A"
15 | ttl = 300
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/oci/single_server/module/instance.tf:
--------------------------------------------------------------------------------
1 | resource "oci_core_instance" "kasm_instance" {
2 | availability_domain = data.oci_identity_availability_domain.ad.name
3 | compartment_id = var.compartment_ocid
4 | display_name = "${var.project_name}-Kasm-Workspaces"
5 | shape = var.instance_shape
6 |
7 | shape_config {
8 | ocpus = var.kasm_server_cpus
9 | memory_in_gbs = var.kasm_server_memory
10 | }
11 |
12 | create_vnic_details {
13 | subnet_id = oci_core_subnet.kasm_subnet.id
14 | display_name = "${var.project_name}-Primaryvnic"
15 | assign_public_ip = true
16 | assign_private_dns_record = true
17 | hostname_label = "${var.project_name}-Kasm-Workspaces"
18 | }
19 |
20 | source_details {
21 | source_type = "image"
22 | source_id = var.instance_image_ocid
23 | boot_volume_size_in_gbs = var.kasm_server_hdd_size
24 | }
25 |
26 | metadata = {
27 | ssh_authorized_keys = var.ssh_authorized_keys == "" ? tls_private_key.ssh_key[0].public_key_openssh : var.ssh_authorized_keys
28 | user_data = base64encode(templatefile("${path.module}/userdata/bootstrap.sh",
29 | {
30 | kasm_build_url = var.kasm_build_url
31 | user_password = var.user_password
32 | admin_password = var.admin_password
33 | swap_size = var.swap_size
34 | nginx_cert_in = var.letsencrypt_server_type == "" ? file(var.kasm_ssl_crt_path) : acme_certificate.certificate.certificate_pem
35 | nginx_key_in = var.letsencrypt_server_type == "" ? file(var.kasm_ssl_key_path) : tls_private_key.certificate_private_key.private_key_pem
36 | }
37 | ))
38 | }
39 | }
40 |
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/oci/single_server/module/letsencrypt.tf:
--------------------------------------------------------------------------------
1 | resource "tls_private_key" "registration_private_key" {
2 | algorithm = "RSA"
3 | }
4 |
5 | resource "tls_private_key" "certificate_private_key" {
6 | algorithm = "RSA"
7 | }
8 |
9 | resource "acme_registration" "registration" {
10 | account_key_pem = tls_private_key.registration_private_key.private_key_pem
11 | email_address = var.letsencrypt_cert_support_email
12 | }
13 |
14 | resource "tls_cert_request" "kasm_certificate_request" {
15 | private_key_pem = tls_private_key.certificate_private_key.private_key_pem
16 | dns_names = [data.oci_dns_zones.kasm_dns_zone.zones[0].name, "*.${data.oci_dns_zones.kasm_dns_zone.zones[0].name}"]
17 |
18 | subject {
19 | common_name = data.oci_dns_zones.kasm_dns_zone.zones[0].name
20 | }
21 | }
22 |
23 | resource "acme_certificate" "certificate" {
24 | account_key_pem = acme_registration.registration.account_key_pem
25 | certificate_request_pem = tls_cert_request.kasm_certificate_request.cert_request_pem
26 | recursive_nameservers = [
27 | "8.8.8.8:53",
28 | "4.4.2.2:53"
29 | ]
30 |
31 | dns_challenge {
32 | provider = "oraclecloud"
33 |
34 | config = {
35 | OCI_COMPARTMENT_OCID = var.compartment_ocid
36 | OCI_PRIVKEY_FILE = var.private_key_path
37 | OCI_TENANCY_OCID = var.tenancy_ocid
38 | OCI_REGION = var.region
39 | OCI_PUBKEY_FINGERPRINT = var.fingerprint
40 | OCI_USER_OCID = var.user_ocid
41 | OCI_PROPOGATION_TIMEOUT = 600
42 | OCI_POLLING_INTERVAL = 60
43 | OCI_TTL = 300
44 | }
45 | }
46 |
47 | depends_on = [acme_registration.registration]
48 | }
49 |
--------------------------------------------------------------------------------
/oci/single_server/module/provider.tf:
--------------------------------------------------------------------------------
1 | terraform {
2 | required_version = "~> 1.0"
3 |
4 | required_providers {
5 | oci = {
6 | source = "oracle/oci"
7 | version = "~> 5.0"
8 | }
9 | acme = {
10 | source = "vancluever/acme"
11 | version = "~> 2.0"
12 | }
13 | tls = {
14 | source = "hashicorp/tls"
15 | version = "~> 4.0"
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/oci/single_server/module/security_list.tf:
--------------------------------------------------------------------------------
1 | resource "oci_core_security_list" "allow_web" {
2 | compartment_id = var.compartment_ocid
3 | vcn_id = oci_core_vcn.kasm_vcn.id
4 | display_name = "allow_web"
5 |
6 | egress_security_rules {
7 | destination = var.anywhere
8 | protocol = "all"
9 | stateless = "false"
10 | }
11 |
12 | dynamic "ingress_security_rules" {
13 | for_each = var.allow_web_cidrs
14 | content {
15 | protocol = "6"
16 | source = ingress_security_rules.value
17 | tcp_options {
18 | max = "443"
19 | min = "443"
20 | }
21 | }
22 | }
23 | }
24 |
25 | resource "oci_core_security_list" "allow_ssh" {
26 | compartment_id = var.compartment_ocid
27 | vcn_id = oci_core_vcn.kasm_vcn.id
28 | display_name = "allow_ssh"
29 |
30 | egress_security_rules {
31 | destination = var.anywhere
32 | protocol = "all"
33 | stateless = "false"
34 | }
35 |
36 | dynamic "ingress_security_rules" {
37 | for_each = var.allow_ssh_cidrs
38 | content {
39 | protocol = "6"
40 | source = ingress_security_rules.value
41 | tcp_options {
42 | max = "22"
43 | min = "22"
44 | }
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/oci/single_server/module/ssh_keys.tf:
--------------------------------------------------------------------------------
1 | resource "tls_private_key" "ssh_key" {
2 | count = var.ssh_authorized_keys == "" ? 1 : 0
3 | algorithm = "ED25519"
4 | }
5 |
6 | output "ssh_key_info" {
7 | description = "SSH Keys for use with Kasm Deployment"
8 | value = <<-SSHKEYS
9 | SSH Keys:
10 | %{if var.ssh_authorized_keys == ""}
11 | Public Key: ${tls_private_key.ssh_key[0].public_key_openssh}
12 | Private Key:
13 | ${tls_private_key.ssh_key[0].private_key_openssh}
14 | %{endif}
15 | SSHKEYS
16 | }
--------------------------------------------------------------------------------
/oci/single_server/module/userdata/bootstrap.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -ex
3 | echo "Starting Kasm Workspaces Install"
4 |
5 | ## Create Swap partition
6 | fallocate -l "${swap_size}"g /var/kasm.swap
7 | chmod 600 /var/kasm.swap
8 | mkswap /var/kasm.swap
9 | swapon /var/kasm.swap
10 | echo '/var/kasm.swap swap swap defaults 0 0' | tee -a /etc/fstab
11 |
12 | cd /tmp
13 |
14 | PRIVATE_IP=(`hostname -I | cut -d ' ' -f1 | tr -d '\\n'`)
15 |
16 | wget ${kasm_build_url} -O kasm_workspaces.tar.gz
17 | tar -xf kasm_workspaces.tar.gz
18 |
19 | sleep 30
20 | bash kasm_release/install.sh -e -U ${user_password} -P ${admin_password} -p $PRIVATE_IP -m $PRIVATE_IP
21 |
22 | echo -e "${nginx_cert_in}" > /opt/kasm/current/certs/kasm_nginx.crt
23 | echo -e "${nginx_key_in}" > /opt/kasm/current/certs/kasm_nginx.key
24 |
25 | echo "Stopping and restarting Kasm services to apply certificates..."
26 | /opt/kasm/bin/stop
27 | docker rm $(docker ps -aq)
28 | /opt/kasm/bin/start
29 |
30 | echo "Done"
31 |
--------------------------------------------------------------------------------
/oci/single_server/module/variables.tf:
--------------------------------------------------------------------------------
1 | variable "project_name" {
2 | description = "The name of the deployment (e.g dev, staging). A short single word"
3 | type = string
4 | }
5 |
6 | variable "oci_domain_name" {
7 | description = "The public Zone used for the dns entries. This must already exist in the OCI account. (e.g kasm.contoso.com). The deployment will be accessed via this zone name via https"
8 | type = string
9 | }
10 |
11 | variable "tenancy_ocid" {
12 | description = "The Tenancy OCID."
13 | type = string
14 | }
15 |
16 | variable "compartment_ocid" {
17 | description = "The Compartment OCID"
18 | type = string
19 | }
20 |
21 | variable "region" {
22 | description = "The OCI Region eg: (us-ashburn-1)"
23 | type = string
24 | }
25 |
26 | variable "user_ocid" {
27 | description = "The User OCID."
28 | type = string
29 | }
30 |
31 | variable "fingerprint" {
32 | description = "API Key Fingerprint"
33 | type = string
34 | }
35 |
36 | variable "private_key_path" {
37 | description = "The path to the API Key PEM encoded Private Key"
38 | type = string
39 | sensitive = true
40 | }
41 |
42 | variable "letsencrypt_cert_support_email" {
43 | description = "Email address to use for Let's Encrypt SSL certificates for OCI Deployment"
44 | type = string
45 | }
46 | variable "letsencrypt_server_type" {
47 | description = "SSL Server type to generate. Valid options are staging, prod, and empty string. Prod certificates are limited to 5 per week per domain."
48 | type = string
49 | }
50 |
51 | variable "vcn_subnet_cidr" {
52 | description = "VPC Subnet CIDR where you wish to deploy Kasm"
53 | type = string
54 | }
55 |
56 | variable "ssh_authorized_keys" {
57 | description = "The SSH Public Keys to be installed on the OCI compute instance"
58 | type = string
59 | }
60 |
61 | variable "instance_image_ocid" {
62 | description = "The OCID for the instance image , such as ubuntu 20.04, to use."
63 | type = string
64 | }
65 |
66 | variable "allow_ssh_cidrs" {
67 | description = "The CIDR notation to allow SSH access to the systems."
68 | type = list(string)
69 | }
70 |
71 | variable "allow_web_cidrs" {
72 | description = "The CIDR notation to allow HTTPS access to the systems."
73 | type = list(string)
74 | }
75 |
76 | variable "kasm_ssl_crt_path" {
77 | description = "The file path to the PEM encoded SSL Certificate"
78 | type = string
79 | }
80 |
81 | variable "kasm_ssl_key_path" {
82 | description = "The file path to the PEM encoded SSL Certificate Key"
83 | type = string
84 | sensitive = true
85 | }
86 |
87 | variable "user_password" {
88 | description = "The standard (non administrator) user password. No special characters"
89 | type = string
90 | sensitive = true
91 | }
92 |
93 | variable "admin_password" {
94 | description = "The administrative user password. No special characters"
95 | type = string
96 | sensitive = true
97 | }
98 |
99 | variable "kasm_build_url" {
100 | description = "The URL for the Kasm Workspaces build"
101 | type = string
102 | }
103 |
104 | variable "swap_size" {
105 | description = "The amount of swap (in GB) to configure inside the compute instances"
106 | type = number
107 | }
108 |
109 | variable "instance_shape" {
110 | description = "The instance shape to use. Should be a Flex type."
111 | type = string
112 | }
113 |
114 | variable "kasm_server_cpus" {
115 | description = "The number of CPUs to configure for the Kasm instance"
116 | type = number
117 | }
118 |
119 | variable "kasm_server_memory" {
120 | description = "The amount of memory to configure for the Kasm instance"
121 | type = number
122 | }
123 |
124 | variable "kasm_server_hdd_size" {
125 | description = "The size in GBs of the Kasm instance HDD"
126 | type = number
127 | }
128 |
129 | ## Pre-set values
130 | variable "anywhere" {
131 | description = "Anywhere route subnet"
132 | type = string
133 | default = "0.0.0.0/0"
134 |
135 | validation {
136 | condition = can(cidrhost(var.anywhere, 0))
137 | error_message = "Anywhere variable must be valid IPv4 CIDR - usually 0.0.0.0/0 for all default routes and default Security Group access."
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/oci/single_server/module/vcn.tf:
--------------------------------------------------------------------------------
1 | locals {
2 | kasm_vcn_subnet_cidr_mask = split("/", var.vcn_subnet_cidr)[1]
3 | kasm_server_subnet_cidr_calculation = (8 - (local.kasm_vcn_subnet_cidr_mask - 16))
4 | kasm_server_subnet_cidr_size = local.kasm_server_subnet_cidr_calculation < 0 ? 0 : local.kasm_server_subnet_cidr_calculation
5 | }
6 |
7 | resource "oci_core_vcn" "kasm_vcn" {
8 | cidr_block = var.vcn_subnet_cidr
9 | compartment_id = var.compartment_ocid
10 | display_name = "${var.project_name}-VCN"
11 | dns_label = "${var.project_name}vcn"
12 | }
13 |
14 | resource "oci_core_internet_gateway" "kasm_internet_gateway" {
15 | compartment_id = var.compartment_ocid
16 | display_name = "${var.project_name}-Gateway"
17 | vcn_id = oci_core_vcn.kasm_vcn.id
18 | }
19 |
20 | resource "oci_core_default_route_table" "default_route_table" {
21 | manage_default_resource_id = oci_core_vcn.kasm_vcn.default_route_table_id
22 | display_name = "DefaultRouteTable"
23 |
24 | route_rules {
25 | destination = var.anywhere
26 | destination_type = "CIDR_BLOCK"
27 | network_entity_id = oci_core_internet_gateway.kasm_internet_gateway.id
28 | }
29 | }
30 |
31 | resource "oci_core_subnet" "kasm_subnet" {
32 | availability_domain = data.oci_identity_availability_domain.ad.name
33 | cidr_block = cidrsubnet(var.vcn_subnet_cidr, local.kasm_server_subnet_cidr_size, 0)
34 | display_name = "${var.project_name}-Subnet"
35 | dns_label = "${var.project_name}subnet"
36 | security_list_ids = [oci_core_security_list.allow_web.id, oci_core_security_list.allow_ssh.id]
37 | compartment_id = var.compartment_ocid
38 | vcn_id = oci_core_vcn.kasm_vcn.id
39 | route_table_id = oci_core_vcn.kasm_vcn.default_route_table_id
40 | dhcp_options_id = oci_core_vcn.kasm_vcn.default_dhcp_options_id
41 | }
42 |
43 |
44 | data "oci_identity_availability_domain" "ad" {
45 | compartment_id = var.tenancy_ocid
46 | ad_number = 1
47 | }
48 |
--------------------------------------------------------------------------------
/oci/single_server/oci-private-key.pem:
--------------------------------------------------------------------------------
1 | replaceme
--------------------------------------------------------------------------------
/oci/single_server/provider.tf:
--------------------------------------------------------------------------------
1 | terraform {
2 | required_version = "~> 1.0"
3 |
4 | required_providers {
5 | oci = {
6 | source = "oracle/oci"
7 | version = "~> 5.0"
8 | }
9 | acme = {
10 | source = "vancluever/acme"
11 | version = "~> 2.0"
12 | }
13 | tls = {
14 | source = "hashicorp/tls"
15 | version = "~> 4.0"
16 | }
17 | }
18 | }
19 |
20 | provider "oci" {
21 | tenancy_ocid = var.tenancy_ocid
22 | user_ocid = var.user_ocid
23 | fingerprint = var.fingerprint
24 | private_key_path = var.private_key_path
25 | region = var.region
26 | }
27 |
28 | provider "acme" {
29 | server_url = local.letsencrypt_server_url
30 | }
31 |
--------------------------------------------------------------------------------
/oci/single_server/terraform.tfvars:
--------------------------------------------------------------------------------
1 | ## Kasm deployment settings
2 | oci_domain_name = "kasm.contoso.com"
3 | project_name = "contoso"
4 | vcn_subnet_cidr = "10.0.0.0/16"
5 |
6 | ## OCI Authentication variables
7 | tenancy_ocid = "changeme"
8 | user_ocid = "changeme"
9 | compartment_ocid = "changeme"
10 | fingerprint = "changeme"
11 | private_key_path = "./oci-private-key.pem"
12 | region = "us-ashburn-1"
13 |
14 | ## Load Balancer SSL Keys
15 | # Terraform auto-generated Let's Encrypt keys
16 | letsencrypt_cert_support_email = ""
17 | letsencrypt_server_type = ""
18 |
19 | # Bring your own - Load Balancer SSL Keys
20 | kasm_ssl_crt_path = ""
21 | kasm_ssl_key_path = ""
22 |
23 | ## VM Access subnets
24 | allow_ssh_cidrs = ["0.0.0.0/0"]
25 | allow_web_cidrs = ["0.0.0.0/0"]
26 |
27 | ## Kasm passwords
28 | admin_password = "changeme"
29 | user_password = "changeme"
30 |
31 | ## SSH Public Keys
32 | ssh_authorized_keys = "changeme"
33 |
34 | ## OCI VM Settings
35 | instance_image_ocid = ""
36 | instance_shape = "VM.Standard.E4.Flex"
37 | swap_size = 2
38 | kasm_server_cpus = 2
39 | kasm_server_memory = 2
40 | kasm_server_hdd_size = 120
41 |
42 | ## Kasm download URL
43 | kasm_build_url = "https://kasm-static-content.s3.amazonaws.com/kasm_release_1.17.0.bbc15c.tar.gz"
44 |
--------------------------------------------------------------------------------
/oci/standard/deployment.tf:
--------------------------------------------------------------------------------
1 | module "kasm" {
2 | source = "./module"
3 | oci_domain_name = var.oci_domain_name
4 | project_name = var.project_name
5 | kasm_build_url = var.kasm_build_url
6 | vcn_subnet_cidr = var.vcn_subnet_cidr
7 |
8 | ## OCI Auth information
9 | tenancy_ocid = var.tenancy_ocid
10 | compartment_ocid = var.compartment_ocid
11 | user_ocid = var.user_ocid
12 | fingerprint = var.fingerprint
13 | private_key_path = var.private_key_path
14 | region = var.region
15 | ssh_authorized_keys = var.ssh_authorized_keys
16 |
17 | ## SSL Certificate values
18 | # Let TF generate Let's Encrypt SSL Certificates automatically
19 | letsencrypt_cert_support_email = var.letsencrypt_cert_support_email
20 | letsencrypt_server_type = var.letsencrypt_server_type
21 |
22 | # Bring your own SSL Certificates
23 | kasm_ssl_crt_path = var.kasm_ssl_crt_path
24 | kasm_ssl_key_path = var.kasm_ssl_key_path
25 |
26 | instance_image_ocid = var.instance_image_ocid
27 | instance_shape = var.instance_shape
28 | num_agents = var.num_agents
29 | num_webapps = var.num_webapps
30 | num_cpx_nodes = var.num_cpx_nodes
31 | kasm_agent_vm_settings = var.kasm_agent_vm_settings
32 | kasm_database_vm_settings = var.kasm_database_vm_settings
33 | kasm_webapp_vm_settings = var.kasm_webapp_vm_settings
34 | kasm_cpx_vm_settings = var.kasm_cpx_vm_settings
35 | allow_ssh_cidrs = var.allow_ssh_cidrs
36 | allow_web_cidrs = var.allow_web_cidrs
37 | swap_size = var.swap_size
38 | bastion_vm_settings = var.bastion_vm_settings
39 |
40 | manager_token = var.manager_token
41 | admin_password = var.admin_password
42 | user_password = var.user_password
43 | redis_password = var.redis_password
44 | database_password = var.database_password
45 | service_registration_token = var.service_registration_token
46 | }
47 |
48 | output "ssh_key_info" {
49 | description = "SSH Keys to use with Kasm Deployment"
50 | value = module.kasm.ssh_key_info
51 | sensitive = true
52 | }
53 |
--------------------------------------------------------------------------------
/oci/standard/diagram/oci_multi_server.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kasmtech/terraform/f8618bc382790d4684127b68e3319319d3afefb0/oci/standard/diagram/oci_multi_server.png
--------------------------------------------------------------------------------
/oci/standard/kasm_ssl.crt:
--------------------------------------------------------------------------------
1 | replaceme
--------------------------------------------------------------------------------
/oci/standard/kasm_ssl.key:
--------------------------------------------------------------------------------
1 | replaceme
--------------------------------------------------------------------------------
/oci/standard/module/agent.tf:
--------------------------------------------------------------------------------
1 | resource "oci_core_instance" "agent" {
2 | count = var.num_agents
3 |
4 | availability_domain = local.availability_domains[0].name
5 | compartment_id = var.compartment_ocid
6 | display_name = "${var.project_name}-Kasm-Agent-${count.index}"
7 | shape = var.instance_shape
8 |
9 | shape_config {
10 | ocpus = var.kasm_agent_vm_settings.cpus
11 | memory_in_gbs = var.kasm_agent_vm_settings.memory
12 | }
13 |
14 | create_vnic_details {
15 | subnet_id = oci_core_subnet.agent.id
16 | display_name = "${var.project_name}-Agent-Primaryvnic-${count.index}"
17 | assign_public_ip = true
18 | assign_private_dns_record = true
19 | hostname_label = "${var.project_name}-Kasm-Agent-${count.index}"
20 | }
21 |
22 | source_details {
23 | source_type = "image"
24 | source_id = var.instance_image_ocid
25 | boot_volume_size_in_gbs = var.kasm_agent_vm_settings.hdd_size_gb
26 | }
27 |
28 |
29 | metadata = {
30 | ssh_authorized_keys = var.ssh_authorized_keys == "" ? tls_private_key.ssh_key[0].public_key_openssh : var.ssh_authorized_keys
31 | user_data = base64encode(templatefile("${path.module}/userdata/agent_bootstrap.sh",
32 | {
33 | kasm_build_url = var.kasm_build_url
34 | swap_size = var.swap_size
35 | manager_address = var.oci_domain_name
36 | manager_token = var.manager_token
37 | }
38 | ))
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/oci/standard/module/bastion.tf:
--------------------------------------------------------------------------------
1 | resource "oci_core_instance" "bastion" {
2 | availability_domain = local.availability_domains[0].name
3 | compartment_id = var.compartment_ocid
4 | display_name = "${var.project_name}-Kasm-SSH-Bastion"
5 | shape = var.instance_shape
6 |
7 | shape_config {
8 | baseline_ocpu_utilization = var.bastion_vm_utilization
9 | ocpus = var.bastion_vm_settings.cpus
10 | memory_in_gbs = var.bastion_vm_settings.memory
11 | }
12 |
13 | create_vnic_details {
14 | subnet_id = oci_core_subnet.lb.id
15 | display_name = "${var.project_name}-Bastion-Primaryvnic"
16 | assign_public_ip = true
17 | assign_private_dns_record = true
18 | hostname_label = "${var.project_name}-Kasm-Bastion"
19 | }
20 |
21 | source_details {
22 | source_type = "image"
23 | source_id = var.instance_image_ocid
24 | boot_volume_size_in_gbs = var.bastion_vm_settings.hdd_size_gb
25 | }
26 |
27 | metadata = {
28 | ssh_authorized_keys = var.ssh_authorized_keys == "" ? tls_private_key.ssh_key[0].public_key_openssh : var.ssh_authorized_keys
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/oci/standard/module/cpx.tf:
--------------------------------------------------------------------------------
1 | resource "oci_core_instance" "cpx" {
2 | count = var.num_cpx_nodes
3 |
4 | availability_domain = length(local.availability_domains) > 1 ? local.availability_domains[(count.index)].name : local.availability_domains[0].name
5 | compartment_id = var.compartment_ocid
6 | display_name = "${var.project_name}-Kasm-cpx-${count.index}"
7 | shape = var.instance_shape
8 |
9 | shape_config {
10 | ocpus = var.kasm_cpx_vm_settings.cpus
11 | memory_in_gbs = var.kasm_cpx_vm_settings.memory
12 | }
13 |
14 | create_vnic_details {
15 | subnet_id = one(oci_core_subnet.cpx[*].id)
16 | display_name = "${var.project_name}-CPX-Primaryvnic-${count.index}"
17 | assign_public_ip = true
18 | assign_private_dns_record = true
19 | hostname_label = "${var.project_name}-Kasm-cpx-${count.index}"
20 | }
21 |
22 | source_details {
23 | source_type = "image"
24 | source_id = var.instance_image_ocid
25 | boot_volume_size_in_gbs = var.kasm_cpx_vm_settings.hdd_size_gb
26 | }
27 |
28 |
29 | metadata = {
30 | ssh_authorized_keys = var.ssh_authorized_keys == "" ? tls_private_key.ssh_key[0].public_key_openssh : var.ssh_authorized_keys
31 | user_data = base64encode(templatefile("${path.module}/userdata/cpx_bootstrap.sh",
32 | {
33 | kasm_build_url = var.kasm_build_url
34 | swap_size = var.swap_size
35 | manager_address = var.oci_domain_name
36 | service_registration_token = var.service_registration_token
37 | }
38 | ))
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/oci/standard/module/db.tf:
--------------------------------------------------------------------------------
1 | resource "oci_core_instance" "db" {
2 | availability_domain = local.availability_domains[0].name
3 | compartment_id = var.compartment_ocid
4 | display_name = "${var.project_name}-Kasm-DB"
5 | shape = var.instance_shape
6 |
7 | shape_config {
8 | ocpus = var.kasm_database_vm_settings.cpus
9 | memory_in_gbs = var.kasm_database_vm_settings.memory
10 | }
11 |
12 | create_vnic_details {
13 | subnet_id = oci_core_subnet.db.id
14 | display_name = "${var.project_name}-DB-Primaryvnic"
15 | assign_public_ip = true
16 | assign_private_dns_record = true
17 | hostname_label = "${var.project_name}-Kasm-DB"
18 | }
19 |
20 | source_details {
21 | source_type = "image"
22 | source_id = var.instance_image_ocid
23 | boot_volume_size_in_gbs = var.kasm_database_vm_settings.hdd_size_gb
24 | }
25 |
26 | metadata = {
27 | ssh_authorized_keys = var.ssh_authorized_keys == "" ? tls_private_key.ssh_key[0].public_key_openssh : var.ssh_authorized_keys
28 | user_data = base64encode(templatefile("${path.module}/userdata/db_bootstrap.sh",
29 | {
30 | kasm_build_url = var.kasm_build_url
31 | user_password = var.user_password
32 | admin_password = var.admin_password
33 | redis_password = var.redis_password
34 | database_password = var.database_password
35 | service_registration_token = var.service_registration_token
36 | manager_token = var.manager_token
37 | swap_size = var.swap_size
38 | }
39 | ))
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/oci/standard/module/dependencies.tf:
--------------------------------------------------------------------------------
1 | locals {
2 | kasm_vcn_subnet_cidr_mask = split("/", var.vcn_subnet_cidr)[1]
3 | kasm_server_subnet_cidr_calculation = (8 - (local.kasm_vcn_subnet_cidr_mask - 16))
4 | kasm_server_subnet_cidr_size = local.kasm_server_subnet_cidr_calculation < 3 ? 3 : local.kasm_server_subnet_cidr_calculation
5 |
6 | availability_domains = data.oci_identity_availability_domains.kasm_ads.availability_domains
7 | }
8 |
9 | data "oci_dns_zones" "this" {
10 | compartment_id = var.compartment_ocid
11 | name = var.oci_domain_name
12 | }
13 |
14 | data "oci_identity_availability_domains" "kasm_ads" {
15 | compartment_id = var.compartment_ocid
16 | }
17 |
--------------------------------------------------------------------------------
/oci/standard/module/dns.tf:
--------------------------------------------------------------------------------
1 | resource "oci_dns_rrset" "kasm_a_record" {
2 | compartment_id = var.compartment_ocid
3 | domain = var.oci_domain_name
4 | zone_name_or_id = data.oci_dns_zones.this.zones[0].name
5 | rtype = "A"
6 |
7 | items {
8 | domain = var.oci_domain_name
9 | rdata = oci_load_balancer.public.ip_address_details[0].ip_address
10 | rtype = "A"
11 | ttl = 300
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/oci/standard/module/letsencrypt.tf:
--------------------------------------------------------------------------------
1 | resource "tls_private_key" "registration" {
2 | algorithm = "RSA"
3 | }
4 |
5 | resource "tls_private_key" "certificate" {
6 | algorithm = "RSA"
7 | }
8 |
9 | resource "acme_registration" "this" {
10 | account_key_pem = tls_private_key.registration.private_key_pem
11 | email_address = var.letsencrypt_cert_support_email
12 | }
13 |
14 | resource "tls_cert_request" "this" {
15 | private_key_pem = tls_private_key.certificate.private_key_pem
16 |
17 | dns_names = [
18 | var.oci_domain_name,
19 | "*.${var.oci_domain_name}"
20 | ]
21 |
22 | subject {
23 | common_name = var.oci_domain_name
24 | }
25 | }
26 |
27 | resource "acme_certificate" "this" {
28 | account_key_pem = acme_registration.this.account_key_pem
29 | certificate_request_pem = tls_cert_request.this.cert_request_pem
30 |
31 | recursive_nameservers = [
32 | "8.8.8.8:53",
33 | "4.4.2.2:53"
34 | ]
35 |
36 | dns_challenge {
37 | provider = "oraclecloud"
38 |
39 | config = {
40 | OCI_COMPARTMENT_OCID = var.compartment_ocid
41 | OCI_PRIVKEY_FILE = var.private_key_path
42 | OCI_TENANCY_OCID = var.tenancy_ocid
43 | OCI_REGION = var.region
44 | OCI_PUBKEY_FINGERPRINT = var.fingerprint
45 | OCI_USER_OCID = var.user_ocid
46 | OCI_PROPOGATION_TIMEOUT = 600
47 | OCI_POLLING_INTERVAL = 60
48 | OCI_TTL = 300
49 | }
50 | }
51 |
52 | depends_on = [acme_registration.this]
53 | }
54 |
--------------------------------------------------------------------------------
/oci/standard/module/load_balancer.tf:
--------------------------------------------------------------------------------
1 | resource "oci_load_balancer" "public" {
2 | shape = "flexible"
3 | compartment_id = var.compartment_ocid
4 | subnet_ids = [oci_core_subnet.lb.id]
5 |
6 | shape_details {
7 | minimum_bandwidth_in_mbps = 10
8 | maximum_bandwidth_in_mbps = 1000
9 | }
10 |
11 | display_name = "${var.project_name}-kasm-load_balancer"
12 | }
13 |
14 | resource "oci_load_balancer_certificate" "public" {
15 | certificate_name = "${var.project_name}-kasm-cert"
16 | load_balancer_id = oci_load_balancer.public.id
17 |
18 | ca_certificate = var.letsencrypt_server_type == "" ? file(var.kasm_ssl_crt_path) : acme_certificate.this.certificate_pem
19 | public_certificate = var.letsencrypt_server_type == "" ? file(var.kasm_ssl_crt_path) : acme_certificate.this.certificate_pem
20 | private_key = var.letsencrypt_server_type == "" ? file(var.kasm_ssl_key_path) : tls_private_key.certificate.private_key_pem
21 |
22 | lifecycle {
23 | create_before_destroy = true
24 | }
25 | }
26 |
27 | resource "oci_load_balancer_backend_set" "public" {
28 | name = "${var.project_name}-kasm-backend_set"
29 | load_balancer_id = oci_load_balancer.public.id
30 | policy = "ROUND_ROBIN"
31 |
32 | health_checker {
33 | port = 443
34 | protocol = "HTTP"
35 | response_body_regex = ""
36 | retries = 3
37 | return_code = 200
38 | timeout_in_millis = 3000
39 | interval_ms = 10000
40 | url_path = "/api/__healthcheck"
41 | }
42 |
43 | ssl_configuration {
44 | protocols = [
45 | "TLSv1.2"
46 | ]
47 | cipher_suite_name = data.oci_load_balancer_ssl_cipher_suite.this.name
48 | certificate_name = oci_load_balancer_certificate.public.certificate_name
49 | verify_peer_certificate = false
50 | }
51 | }
52 |
53 | resource "oci_load_balancer_backend" "public" {
54 | count = var.num_webapps
55 |
56 | backendset_name = oci_load_balancer_backend_set.public.name
57 | backup = false
58 | drain = false
59 | load_balancer_id = oci_load_balancer.public.id
60 | ip_address = oci_core_instance.webapp[(count.index)].private_ip
61 | offline = false
62 | port = 443
63 | weight = 1
64 | }
65 |
66 | resource "oci_load_balancer_listener" "kasm_https_ssl_listener" {
67 | name = "${var.project_name}-https-ssl-listener"
68 | load_balancer_id = oci_load_balancer.public.id
69 | default_backend_set_name = oci_load_balancer_backend_set.public.name
70 | port = "443"
71 | protocol = "HTTP"
72 |
73 | ssl_configuration {
74 | protocols = [
75 | "TLSv1.2"
76 | ]
77 | server_order_preference = "ENABLED"
78 | verify_peer_certificate = false
79 | cipher_suite_name = data.oci_load_balancer_ssl_cipher_suite.this.name
80 | certificate_name = oci_load_balancer_certificate.public.certificate_name
81 | }
82 | }
83 |
84 | data "oci_load_balancer_ssl_cipher_suite" "this" {
85 | name = "oci-default-ssl-cipher-suite-v1"
86 | load_balancer_id = oci_load_balancer.public.id
87 | }
88 |
--------------------------------------------------------------------------------
/oci/standard/module/provider.tf:
--------------------------------------------------------------------------------
1 | terraform {
2 | required_version = "~> 1.0"
3 |
4 | required_providers {
5 | oci = {
6 | source = "oracle/oci"
7 | version = "~> 5.0"
8 | }
9 | acme = {
10 | source = "vancluever/acme"
11 | version = "~> 2.0"
12 | }
13 | tls = {
14 | source = "hashicorp/tls"
15 | version = "~> 4.0"
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/oci/standard/module/security_lists.tf:
--------------------------------------------------------------------------------
1 | resource "oci_core_security_list" "allow_web" {
2 | compartment_id = var.compartment_ocid
3 | vcn_id = oci_core_vcn.this.id
4 | display_name = "allow_web"
5 |
6 | dynamic "egress_security_rules" {
7 | for_each = var.anywhere
8 |
9 | content {
10 | destination = egress_security_rules.value
11 | protocol = "all"
12 | stateless = "false"
13 | }
14 | }
15 |
16 | dynamic "ingress_security_rules" {
17 | for_each = var.allow_web_cidrs
18 | content {
19 | protocol = "6"
20 | source = ingress_security_rules.value
21 | tcp_options {
22 | max = "443"
23 | min = "443"
24 | }
25 | }
26 | }
27 | }
28 |
29 | resource "oci_core_security_list" "allow_public_ssh" {
30 | compartment_id = var.compartment_ocid
31 | vcn_id = oci_core_vcn.this.id
32 | display_name = "allow_public_ssh"
33 |
34 | dynamic "egress_security_rules" {
35 | for_each = var.anywhere
36 |
37 | content {
38 | destination = egress_security_rules.value
39 | protocol = "all"
40 | stateless = "false"
41 | }
42 | }
43 |
44 | dynamic "ingress_security_rules" {
45 | for_each = var.allow_ssh_cidrs
46 | content {
47 | protocol = "6"
48 | source = ingress_security_rules.value
49 | tcp_options {
50 | max = "22"
51 | min = "22"
52 | }
53 | }
54 | }
55 | }
56 |
57 | resource "oci_core_security_list" "allow_bastion_ssh" {
58 | compartment_id = var.compartment_ocid
59 | vcn_id = oci_core_vcn.this.id
60 | display_name = "allow_bastion_ssh"
61 |
62 | ingress_security_rules {
63 | protocol = "6"
64 | source = "${oci_core_instance.bastion.private_ip}/32"
65 | tcp_options {
66 | max = "22"
67 | min = "22"
68 | }
69 | }
70 | }
71 |
72 | resource "oci_core_security_list" "allow_db_redis" {
73 | compartment_id = var.compartment_ocid
74 | vcn_id = oci_core_vcn.this.id
75 | display_name = "allow_db_redis"
76 |
77 | dynamic "egress_security_rules" {
78 | for_each = var.anywhere
79 |
80 | content {
81 | destination = egress_security_rules.value
82 | protocol = "all"
83 | stateless = "false"
84 | }
85 | }
86 |
87 | ingress_security_rules {
88 | protocol = "6"
89 | source = oci_core_subnet.webapp.cidr_block
90 | tcp_options {
91 | max = "5432"
92 | min = "5432"
93 | }
94 | }
95 |
96 | ingress_security_rules {
97 | protocol = "6"
98 | source = oci_core_subnet.webapp.cidr_block
99 | tcp_options {
100 | max = "6379"
101 | min = "6379"
102 | }
103 | }
104 | }
105 |
106 | resource "oci_core_security_list" "allow_web_from_lb" {
107 | compartment_id = var.compartment_ocid
108 | vcn_id = oci_core_vcn.this.id
109 | display_name = "allow_web_from_webapp"
110 |
111 | dynamic "egress_security_rules" {
112 | for_each = var.anywhere
113 |
114 | content {
115 | destination = egress_security_rules.value
116 | protocol = "all"
117 | stateless = "false"
118 | }
119 | }
120 |
121 | ingress_security_rules {
122 | protocol = "6"
123 | source = oci_core_subnet.lb.cidr_block
124 | tcp_options {
125 | max = "443"
126 | min = "443"
127 | }
128 | }
129 | }
130 |
131 | resource "oci_core_security_list" "allow_web_from_webapp" {
132 | compartment_id = var.compartment_ocid
133 | vcn_id = oci_core_vcn.this.id
134 | display_name = "allow_web_from_webapp"
135 |
136 | dynamic "egress_security_rules" {
137 | for_each = var.anywhere
138 |
139 | content {
140 | destination = egress_security_rules.value
141 | protocol = "all"
142 | stateless = "false"
143 | }
144 | }
145 |
146 | ingress_security_rules {
147 | protocol = "6"
148 | source = oci_core_subnet.webapp.cidr_block
149 | tcp_options {
150 | max = "443"
151 | min = "443"
152 | }
153 | }
154 | }
155 |
156 | resource "oci_core_security_list" "allow_rdp_to_windows" {
157 | count = var.num_cpx_nodes > 0 ? 1 : 0
158 |
159 | compartment_id = var.compartment_ocid
160 | vcn_id = oci_core_vcn.this.id
161 | display_name = "allow_rdp_for_windows"
162 |
163 | dynamic "egress_security_rules" {
164 | for_each = var.anywhere
165 |
166 | content {
167 | destination = egress_security_rules.value
168 | protocol = "all"
169 | stateless = "false"
170 | }
171 | }
172 |
173 | ingress_security_rules {
174 | protocol = "6"
175 | source = oci_core_subnet.webapp.cidr_block
176 | tcp_options {
177 | max = "4902"
178 | min = "4902"
179 | }
180 | }
181 |
182 | ingress_security_rules {
183 | protocol = "6"
184 | source = one(oci_core_subnet.cpx[*].cidr_block)
185 | tcp_options {
186 | max = "3389"
187 | min = "3389"
188 | }
189 | }
190 |
191 | ingress_security_rules {
192 | protocol = "6"
193 | source = one(oci_core_subnet.cpx[*].cidr_block)
194 | tcp_options {
195 | max = "4902"
196 | min = "4902"
197 | }
198 | }
199 | }
200 |
--------------------------------------------------------------------------------
/oci/standard/module/ssh_keys.tf:
--------------------------------------------------------------------------------
1 | resource "tls_private_key" "ssh_key" {
2 | count = var.ssh_authorized_keys == "" ? 1 : 0
3 | algorithm = "ED25519"
4 | }
5 |
6 | output "ssh_key_info" {
7 | description = "SSH Keys for use with Kasm Deployment"
8 | value = <<-SSHKEYS
9 | SSH Keys:
10 | %{if var.ssh_authorized_keys == ""}
11 | Public Key: ${tls_private_key.ssh_key[0].public_key_openssh}
12 | Private Key:
13 | ${tls_private_key.ssh_key[0].private_key_openssh}
14 | %{endif}
15 | SSHKEYS
16 | }
--------------------------------------------------------------------------------
/oci/standard/module/subnets.tf:
--------------------------------------------------------------------------------
1 | ## Will create WebApp subnets x.x.0.x/24 (assuming a VPC Subnet CIDR between x.x.0.0/16 and x.x.0.0/21)
2 | resource "oci_core_subnet" "lb" {
3 | compartment_id = var.compartment_ocid
4 | vcn_id = oci_core_vcn.this.id
5 | route_table_id = oci_core_route_table.internet_gateway.id
6 | dhcp_options_id = oci_core_vcn.this.default_dhcp_options_id
7 | cidr_block = cidrsubnet(var.vcn_subnet_cidr, local.kasm_server_subnet_cidr_size, 0)
8 | display_name = "${var.project_name}-public-lb-subnet"
9 | dns_label = "${var.project_name}lb"
10 | security_list_ids = [
11 | oci_core_security_list.allow_web.id,
12 | oci_core_security_list.allow_public_ssh.id
13 | ]
14 | }
15 |
16 | ## Will create WebApp subnets x.x.1.x/24 (assuming a VPC Subnet CIDR between x.x.0.0/16 and x.x.0.0/21)
17 | resource "oci_core_subnet" "webapp" {
18 | compartment_id = var.compartment_ocid
19 | vcn_id = oci_core_vcn.this.id
20 | route_table_id = oci_core_route_table.nat_gateway.id
21 | dhcp_options_id = oci_core_vcn.this.default_dhcp_options_id
22 | cidr_block = cidrsubnet(var.vcn_subnet_cidr, local.kasm_server_subnet_cidr_size, 1)
23 | display_name = "${var.project_name}-webapp-subnet"
24 | dns_label = "${var.project_name}webapp"
25 | security_list_ids = [
26 | oci_core_security_list.allow_web_from_lb.id,
27 | oci_core_security_list.allow_bastion_ssh.id
28 | ]
29 | }
30 |
31 | ## Will create Agent subnet x.x.2.x/24 (assuming a VPC Subnet CIDR between x.x.0.0/16 and x.x.0.0/21)
32 | resource "oci_core_subnet" "db" {
33 | compartment_id = var.compartment_ocid
34 | vcn_id = oci_core_vcn.this.id
35 | route_table_id = oci_core_route_table.nat_gateway.id
36 | dhcp_options_id = oci_core_vcn.this.default_dhcp_options_id
37 | cidr_block = cidrsubnet(var.vcn_subnet_cidr, local.kasm_server_subnet_cidr_size, 2)
38 | display_name = "${var.project_name}-db-subnet"
39 | dns_label = "${var.project_name}db"
40 | security_list_ids = [
41 | oci_core_security_list.allow_db_redis.id,
42 | oci_core_security_list.allow_bastion_ssh.id
43 | ]
44 | }
45 |
46 | ## Will create Agent subnet x.x.3.x/24 (assuming a VPC Subnet CIDR between x.x.0.0/16 and x.x.0.0/21)
47 | resource "oci_core_subnet" "agent" {
48 | compartment_id = var.compartment_ocid
49 | vcn_id = oci_core_vcn.this.id
50 | route_table_id = oci_core_route_table.internet_gateway.id
51 | dhcp_options_id = oci_core_vcn.this.default_dhcp_options_id
52 | cidr_block = cidrsubnet(var.vcn_subnet_cidr, local.kasm_server_subnet_cidr_size, 3)
53 | display_name = "${var.project_name}-agent-subnet"
54 | dns_label = "${var.project_name}agent"
55 | security_list_ids = [
56 | oci_core_security_list.allow_web_from_webapp.id,
57 | oci_core_security_list.allow_bastion_ssh.id
58 | ]
59 | }
60 |
61 | ## Will create Guac subnet x.x.4.x/24 (assuming a VPC Subnet CIDR between x.x.0.0/16 and x.x.0.0/21)
62 | resource "oci_core_subnet" "cpx" {
63 | count = var.num_cpx_nodes > 0 ? 1 : 0
64 |
65 | compartment_id = var.compartment_ocid
66 | vcn_id = oci_core_vcn.this.id
67 | route_table_id = oci_core_route_table.nat_gateway.id
68 | dhcp_options_id = oci_core_vcn.this.default_dhcp_options_id
69 | cidr_block = cidrsubnet(var.vcn_subnet_cidr, local.kasm_server_subnet_cidr_size, 4)
70 | display_name = "${var.project_name}-cpx-subnet"
71 | dns_label = "${var.project_name}cpx"
72 | security_list_ids = [
73 | oci_core_security_list.allow_web_from_webapp.id,
74 | oci_core_security_list.allow_bastion_ssh.id
75 | ]
76 | }
77 |
78 | ## Will create Guac subnet x.x.5.x/24 (assuming a VPC Subnet CIDR between x.x.0.0/16 and x.x.0.0/21)
79 | resource "oci_core_subnet" "windows" {
80 | count = var.num_cpx_nodes > 0 ? 1 : 0
81 |
82 | compartment_id = var.compartment_ocid
83 | vcn_id = oci_core_vcn.this.id
84 | route_table_id = oci_core_route_table.internet_gateway.id
85 | dhcp_options_id = oci_core_vcn.this.default_dhcp_options_id
86 | cidr_block = cidrsubnet(var.vcn_subnet_cidr, local.kasm_server_subnet_cidr_size, 5)
87 | display_name = "${var.project_name}-windows-subnet"
88 | dns_label = "${var.project_name}win"
89 | security_list_ids = oci_core_security_list.allow_rdp_to_windows[*].id
90 | }
91 |
--------------------------------------------------------------------------------
/oci/standard/module/userdata/agent_bootstrap.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -ex
3 | echo "Starting Kasm Workspaces Agent Install"
4 |
5 | ## Create Swap partition
6 | fallocate -l "${swap_size}"g /var/kasm.swap
7 | chmod 600 /var/kasm.swap
8 | mkswap /var/kasm.swap
9 | swapon /var/kasm.swap
10 | echo '/var/kasm.swap swap swap defaults 0 0' | tee -a /etc/fstab
11 |
12 | cd /tmp
13 |
14 | PRIVATE_IP=(`hostname -I | cut -d' ' -f1 | tr -d '\\n'`)
15 |
16 | wget ${kasm_build_url} -O kasm_workspaces.tar.gz
17 | tar -xf kasm_workspaces.tar.gz
18 |
19 | echo "Waiting for Kasm WebApp availability..."
20 | while ! (curl -k https://${manager_address}/api/__healthcheck 2>/dev/null | grep -q true)
21 | do
22 | echo "Waiting for API server..."
23 | sleep 5
24 | done
25 | echo "WebApp is alive"
26 |
27 | bash kasm_release/install.sh -S agent -e -p $PRIVATE_IP -m ${manager_address} -M ${manager_token}
28 |
29 | echo "Done"
30 |
--------------------------------------------------------------------------------
/oci/standard/module/userdata/cpx_bootstrap.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -ex
3 | echo "Starting Kasm Workspaces Agent Install"
4 |
5 | ## Create Swap partition
6 | fallocate -l "${swap_size}"g /var/kasm.swap
7 | chmod 600 /var/kasm.swap
8 | mkswap /var/kasm.swap
9 | swapon /var/kasm.swap
10 | echo '/var/kasm.swap swap swap defaults 0 0' | tee -a /etc/fstab
11 |
12 | cd /tmp
13 |
14 | PRIVATE_IP=(`hostname -I | cut -d ' ' -f1 | tr -d '\\n'`)
15 |
16 | wget ${kasm_build_url} -O kasm_workspaces.tar.gz
17 | tar -xf kasm_workspaces.tar.gz
18 |
19 | echo "Waiting for Kasm WebApp availability..."
20 | while ! (curl -k https://${manager_address}/api/__healthcheck 2>/dev/null | grep -q true)
21 | do
22 | echo "Waiting for API server..."
23 | sleep 5
24 | done
25 | echo "WebApp is alive"
26 |
27 | bash kasm_release/install.sh -S guac -e -p $PRIVATE_IP -n ${manager_address} -k ${service_registration_token}
28 |
29 | echo "Done"
30 |
--------------------------------------------------------------------------------
/oci/standard/module/userdata/db_bootstrap.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -ex
3 | echo "Starting Kasm Workspaces Install"
4 |
5 | ## Create Swap partition
6 | fallocate -l "${swap_size}"g /var/kasm.swap
7 | chmod 600 /var/kasm.swap
8 | mkswap /var/kasm.swap
9 | swapon /var/kasm.swap
10 | echo '/var/kasm.swap swap swap defaults 0 0' | tee -a /etc/fstab
11 |
12 | cd /tmp
13 |
14 | PRIVATE_IP=(`hostname -I | cut -d ' ' -f1 | tr -d '\\n'`)
15 |
16 | wget ${kasm_build_url} -O kasm_workspaces.tar.gz
17 | tar -xf kasm_workspaces.tar.gz
18 |
19 | sleep 30
20 | bash kasm_release/install.sh -S db -e -Q ${database_password} -R ${redis_password} -U ${user_password} -P ${admin_password} -M ${manager_token} -k ${service_registration_token}
21 |
22 | echo "Done"
23 |
--------------------------------------------------------------------------------
/oci/standard/module/userdata/webapp_bootstrap.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -ex
3 | echo "Starting Kasm Workspaces Install"
4 |
5 | ## Create Swap partition
6 | fallocate -l "${swap_size}"g /var/kasm.swap
7 | chmod 600 /var/kasm.swap
8 | mkswap /var/kasm.swap
9 | swapon /var/kasm.swap
10 | echo '/var/kasm.swap swap swap defaults 0 0' | tee -a /etc/fstab
11 |
12 | cd /tmp
13 |
14 | PRIVATE_IP=(`hostname -I | cut -d ' ' -f1 | tr -d '\\n'`)
15 |
16 | wget ${kasm_build_url} -O kasm_workspaces.tar.gz
17 | tar -xf kasm_workspaces.tar.gz
18 |
19 | echo "Checking for Kasm DB and Redis..."
20 | apt-get update && apt-get install -y netcat-openbsd
21 | while ! nc -w 1 -z ${db_ip} 5432; do
22 | echo "Database not ready..."
23 | sleep 5
24 | done
25 | echo "DB is alive"
26 |
27 | while ! nc -w 1 -z ${db_ip} 6379; do
28 | echo "Redis not ready..."
29 | sleep 5
30 | done
31 | echo "Redis is alive"
32 |
33 | sleep 30
34 | bash kasm_release/install.sh -S app -e -z ${zone_name} -q "${db_ip}" -Q ${database_password} -R ${redis_password}
35 |
36 | echo "Done"
37 |
--------------------------------------------------------------------------------
/oci/standard/module/vcn.tf:
--------------------------------------------------------------------------------
1 | resource "oci_core_vcn" "this" {
2 | cidr_block = "10.0.0.0/16"
3 | compartment_id = var.compartment_ocid
4 | display_name = "${var.project_name}-VCN"
5 | dns_label = "${var.project_name}vcn"
6 | }
7 |
8 | resource "oci_core_internet_gateway" "this" {
9 | compartment_id = var.compartment_ocid
10 | display_name = "${var.project_name}-Internet-Gateway"
11 | vcn_id = oci_core_vcn.this.id
12 | }
13 |
14 | resource "oci_core_nat_gateway" "this" {
15 | compartment_id = var.compartment_ocid
16 | display_name = "${var.project_name}-NAT-Gateway"
17 | vcn_id = oci_core_vcn.this.id
18 | }
19 |
20 | resource "oci_core_route_table" "internet_gateway" {
21 | compartment_id = var.compartment_ocid
22 | vcn_id = oci_core_vcn.this.id
23 | display_name = "Kasm-IG-RouteTable"
24 |
25 | route_rules {
26 | destination = var.anywhere[0]
27 | destination_type = "CIDR_BLOCK"
28 | network_entity_id = oci_core_internet_gateway.this.id
29 | }
30 | }
31 |
32 | resource "oci_core_route_table" "nat_gateway" {
33 | compartment_id = var.compartment_ocid
34 | vcn_id = oci_core_vcn.this.id
35 | display_name = "Kasm-NAT-RouteTable"
36 |
37 | route_rules {
38 | destination = var.anywhere[0]
39 | destination_type = "CIDR_BLOCK"
40 | network_entity_id = oci_core_nat_gateway.this.id
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/oci/standard/module/webapp.tf:
--------------------------------------------------------------------------------
1 | resource "oci_core_instance" "webapp" {
2 | count = var.num_webapps
3 |
4 | availability_domain = length(local.availability_domains) > 1 ? local.availability_domains[(count.index)].name : local.availability_domains[0].name
5 | compartment_id = var.compartment_ocid
6 | display_name = "${var.project_name}-Kasm-Webapp-${count.index}"
7 | shape = var.instance_shape
8 |
9 | shape_config {
10 | ocpus = var.kasm_webapp_vm_settings.cpus
11 | memory_in_gbs = var.kasm_webapp_vm_settings.memory
12 | }
13 |
14 | create_vnic_details {
15 | subnet_id = oci_core_subnet.webapp.id
16 | display_name = "${var.project_name}-WebApp-Primaryvnic"
17 | assign_public_ip = true
18 | assign_private_dns_record = true
19 | hostname_label = "${var.project_name}-Kasm-Webapp-${count.index}"
20 | }
21 |
22 | source_details {
23 | source_type = "image"
24 | source_id = var.instance_image_ocid
25 | boot_volume_size_in_gbs = var.kasm_webapp_vm_settings.hdd_size_gb
26 | }
27 |
28 | metadata = {
29 | ssh_authorized_keys = var.ssh_authorized_keys == "" ? tls_private_key.ssh_key[0].public_key_openssh : var.ssh_authorized_keys
30 | user_data = base64encode(templatefile("${path.module}/userdata/webapp_bootstrap.sh",
31 | {
32 | kasm_build_url = var.kasm_build_url
33 | db_ip = oci_core_instance.db.private_ip
34 | database_password = var.database_password
35 | redis_password = var.redis_password
36 | swap_size = var.swap_size
37 | zone_name = "default"
38 | }
39 | ))
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/oci/standard/oci-private-key.pem:
--------------------------------------------------------------------------------
1 | replaceme
--------------------------------------------------------------------------------
/oci/standard/provider.tf:
--------------------------------------------------------------------------------
1 | terraform {
2 | required_version = "~> 1.0"
3 |
4 | required_providers {
5 | oci = {
6 | source = "oracle/oci"
7 | version = "~> 5.0"
8 | }
9 | acme = {
10 | source = "vancluever/acme"
11 | version = "~> 2.0"
12 | }
13 | tls = {
14 | source = "hashicorp/tls"
15 | version = "~> 4.0"
16 | }
17 | }
18 | }
19 |
20 | provider "oci" {
21 | tenancy_ocid = var.tenancy_ocid
22 | user_ocid = var.user_ocid
23 | fingerprint = var.fingerprint
24 | private_key_path = var.private_key_path
25 | region = var.region
26 | }
27 |
28 | provider "acme" {
29 | server_url = local.letsencrypt_server_url
30 | }
31 |
--------------------------------------------------------------------------------
/oci/standard/terraform.tfvars:
--------------------------------------------------------------------------------
1 | ## Kasm deployment settings
2 | oci_domain_name = "kasm.contoso.com"
3 | project_name = "contoso"
4 | vcn_subnet_cidr = "10.0.0.0/16"
5 | kasm_build_url = "https://kasm-static-content.s3.amazonaws.com/kasm_release_1.17.0.bbc15c.tar.gz"
6 |
7 | ## OCI Authentication variables
8 | tenancy_ocid = ""
9 | user_ocid = ""
10 | compartment_ocid = ""
11 | fingerprint = ""
12 | region = "us-ashburn-1"
13 | private_key_path = "./oci-private-key.pem"
14 |
15 | ## Load Balancer SSL Keys
16 | # Terraform auto-generated Let's Encrypt keys
17 | letsencrypt_cert_support_email = ""
18 | letsencrypt_server_type = ""
19 |
20 | # Bring your own - Load Balancer SSL Keys
21 | kasm_ssl_crt_path = ""
22 | kasm_ssl_key_path = ""
23 |
24 | ## VM Access subnets
25 | allow_ssh_cidrs = ["0.0.0.0/0"]
26 | allow_web_cidrs = ["0.0.0.0/0"]
27 |
28 | ## Kasm passwords
29 | manager_token = "changeme"
30 | admin_password = "changeme"
31 | user_password = "changeme"
32 | redis_password = "changeme"
33 | database_password = "changeme"
34 | service_registration_token = "changeme"
35 |
36 | ## SSH Public Key
37 | ssh_authorized_keys = "changeme"
38 |
39 | ## OCI VM Settings
40 | instance_image_ocid = ""
41 | instance_shape = "VM.Standard.E4.Flex"
42 | swap_size = 2
43 | num_webapps = 2
44 | num_agents = 2
45 | num_cpx_nodes = 1
46 |
47 | kasm_webapp_vm_settings = {
48 | cpus = 2
49 | memory = 2
50 | hdd_size_gb = 50
51 | }
52 |
53 | kasm_database_vm_settings = {
54 | cpus = 2
55 | memory = 2
56 | hdd_size_gb = 50
57 | }
58 |
59 | kasm_agent_vm_settings = {
60 | cpus = 4
61 | memory = 8
62 | hdd_size_gb = 150
63 | }
64 |
65 | kasm_cpx_vm_settings = {
66 | cpus = 4
67 | memory = 4
68 | hdd_size_gb = 50
69 | }
70 |
71 | bastion_vm_settings = {
72 | cpus = 1
73 | memory = 2
74 | hdd_size_gb = 50
75 | }
76 |
--------------------------------------------------------------------------------