├── .gitignore ├── .pre-commit-config.yaml ├── README.md ├── aws ├── multi_region │ ├── README.md │ ├── agents │ │ ├── README.md │ │ ├── agent.tf │ │ ├── cert.tf │ │ ├── cpx.tf │ │ ├── dependencies.tf │ │ ├── elb.tf │ │ ├── provider.tf │ │ ├── proxy.tf │ │ ├── routes.tf │ │ ├── security_group.tf │ │ ├── subnet.tf │ │ ├── variables.tf │ │ └── vpc.tf │ ├── aws_key_pairs │ │ ├── README.md │ │ ├── main.tf │ │ ├── outputs.tf │ │ ├── providers.tf │ │ └── variables.tf │ ├── deployment.tf │ ├── diagram │ │ ├── aws_multi_region.drawio │ │ └── aws_multi_region.png │ ├── outputs.tf │ ├── primary │ │ ├── README.md │ │ ├── cert.tf │ │ ├── db.tf │ │ ├── dependencies.tf │ │ ├── lb_s3_log_bucket.tf │ │ ├── output.tf │ │ ├── provider.tf │ │ ├── routes.tf │ │ ├── security_group.tf │ │ ├── ssm.tf │ │ ├── subnet.tf │ │ ├── variables.tf │ │ └── vpc.tf │ ├── provider.tf │ ├── secrets.tfvars.example │ ├── ssh_keys │ │ ├── README.md │ │ ├── main.tf │ │ ├── outputs.tf │ │ ├── providers.tf │ │ └── variables.tf │ ├── terraform.tfvars │ ├── userdata │ │ ├── agent_bootstrap.sh │ │ ├── cpx_bootstrap.sh │ │ ├── db_bootstrap.sh │ │ ├── proxy_bootstrap.sh │ │ └── webapp_bootstrap.sh │ ├── variables.tf │ └── webapps │ │ ├── README.md │ │ ├── agent.tf │ │ ├── cpx.tf │ │ ├── dependencies.tf │ │ ├── elb.tf │ │ ├── outputs.tf │ │ ├── provider.tf │ │ ├── variables.tf │ │ └── webapp.tf └── standard │ ├── README.md │ ├── deployment.tf │ ├── diagram │ ├── aws_multi_server.drawio │ └── aws_multi_server.png │ ├── module │ ├── README.md │ ├── agent.tf │ ├── cert.tf │ ├── db.tf │ ├── dependencies.tf │ ├── elb_logs_s3_bucket.tf │ ├── guac_rdp.tf │ ├── kms.tf │ ├── private_alb.tf │ ├── provider.tf │ ├── public_alb.tf │ ├── routes.tf │ ├── security_group.tf │ ├── ssh_keys.tf │ ├── ssm.tf │ ├── subnet.tf │ ├── userdata │ │ ├── agent_bootstrap.sh │ │ ├── cpx_bootstrap.sh │ │ ├── db_bootstrap.sh │ │ └── webapp_bootstrap.sh │ ├── variables.tf │ ├── vpc.tf │ └── webapp.tf │ ├── output.tf │ ├── provider.tf │ ├── secrets.tfvars.example │ ├── terraform.tfvars │ └── variables.tf ├── digitalocean └── single_server │ ├── README.md │ ├── deployment.tf │ ├── module │ ├── README.md │ ├── dns.tf │ ├── firewall.tf │ ├── load_balancer.tf │ ├── output.tf │ ├── project.tf │ ├── provider.tf │ ├── server.tf │ ├── userdata │ │ └── kasm_server_init.sh │ ├── variables.tf │ └── vpc.tf │ ├── provider.tf │ ├── secrets.tfvars.example │ ├── terraform.tfvars │ └── variables.tf ├── gcp ├── MULTI_REGION.md ├── MULTI_SERVER.md ├── README.md ├── diagram │ ├── gcp_multi_region.drawio │ ├── gcp_multi_region.png │ ├── gcp_multi_server.drawio │ └── gcp_multi_server.png ├── gcp_credentials.json ├── locals.tf ├── main.tf ├── modules │ ├── certificate_manager │ │ ├── README.md │ │ ├── certificate_manager.tf │ │ ├── outputs.tf │ │ ├── provider.tf │ │ └── variables.tf │ ├── compute_instance │ │ ├── README.md │ │ ├── compute.tf │ │ ├── outputs.tf │ │ ├── provider.tf │ │ └── variables.tf │ ├── dns_records │ │ ├── README.md │ │ ├── dns.tf │ │ ├── provider.tf │ │ └── variables.tf │ ├── private_load_balancer │ │ ├── README copy.md │ │ ├── README.md │ │ ├── load_balancer.tf │ │ ├── outputs.tf │ │ ├── provider.tf │ │ └── variables.tf │ ├── random │ │ ├── README.md │ │ ├── outputs.tf │ │ ├── password.tf │ │ ├── provider.tf │ │ └── variables.tf │ └── service_account_iam │ │ ├── README.md │ │ ├── outputs.tf │ │ ├── provider.tf │ │ ├── service_account.tf │ │ └── variables.tf ├── outputs.tf ├── provider.tf ├── terraform.tfvars ├── userdata │ ├── agent_bootstrap.sh │ ├── cpx_bootstrap.sh │ ├── database_bootstrap.sh │ ├── remote_db_init.sh │ └── webapp_bootstrap.sh └── variables.tf └── oci ├── single_server ├── README.md ├── deployment.tf ├── diagram │ ├── oci_single_server.drawio │ └── oci_single_server.png ├── kasm_ssl.crt ├── kasm_ssl.key ├── module │ ├── README.md │ ├── dns.tf │ ├── instance.tf │ ├── letsencrypt.tf │ ├── provider.tf │ ├── security_list.tf │ ├── ssh_keys.tf │ ├── userdata │ │ └── bootstrap.sh │ ├── variables.tf │ └── vcn.tf ├── oci-private-key.pem ├── provider.tf ├── terraform.tfvars └── variables.tf └── standard ├── README.md ├── deployment.tf ├── diagram ├── oci_multi_server.drawio └── oci_multi_server.png ├── kasm_ssl.crt ├── kasm_ssl.key ├── module ├── README.md ├── agent.tf ├── bastion.tf ├── cpx.tf ├── db.tf ├── dependencies.tf ├── dns.tf ├── letsencrypt.tf ├── load_balancer.tf ├── provider.tf ├── security_lists.tf ├── ssh_keys.tf ├── subnets.tf ├── userdata │ ├── agent_bootstrap.sh │ ├── cpx_bootstrap.sh │ ├── db_bootstrap.sh │ └── webapp_bootstrap.sh ├── variables.tf ├── vcn.tf └── webapp.tf ├── oci-private-key.pem ├── provider.tf ├── terraform.tfvars └── variables.tf /.gitignore: -------------------------------------------------------------------------------- 1 | # Local .terraform directories 2 | **/.terraform/* 3 | .terraform 4 | 5 | # .tfstate files 6 | *.tfstate 7 | *.tfstate.* 8 | 9 | # Crash log files 10 | crash.log 11 | 12 | # Exclude secrets.tfvars files, which are likely to contain sentitive data, such as 13 | # password, private keys, and other secrets. These should not be part of version 14 | # control as they are data points which are potentially sensitive and subject 15 | # to change depending on the environment. 16 | # 17 | secrets.tfvars 18 | 19 | # Ignore override files as they are usually used to override resources locally and so 20 | # are not checked in 21 | override.tf 22 | override.tf.json 23 | *_override.tf 24 | *_override.tf.json 25 | 26 | # Include override files you do wish to add to version control using negated pattern 27 | # 28 | # !example_override.tf 29 | 30 | # Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan 31 | # example: *tfplan* 32 | 33 | # Ignore CLI configuration files 34 | .terraformrc 35 | terraform.rc 36 | 37 | # Ignore lock file 38 | .terraform.lock.hcl 39 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v4.6.0 4 | hooks: 5 | - id: mixed-line-ending 6 | - id: trailing-whitespace 7 | - repo: https://github.com/antonbabenko/pre-commit-terraform 8 | rev: v1.89.1 9 | hooks: 10 | - id: tfupdate 11 | name: Autoupdate Terraform versions 12 | args: 13 | - --args=terraform 14 | - --args=--version "~> 1.0" 15 | - id: tfupdate 16 | name: Autoupdate AWS version 17 | args: 18 | - --args=provider aws 19 | - --args=--version "~> 5.0" 20 | - id: tfupdate 21 | name: Autoupdate OCI version 22 | args: 23 | - --args=provider oci 24 | - --args=--version "~> 5.0" 25 | - id: tfupdate 26 | name: Autoupdate DigitalOcean version 27 | args: 28 | - --args=provider digitalocean 29 | - --args=--version "~> 2.0" 30 | - id: tfupdate 31 | name: Autoupdate Acme version 32 | args: 33 | - --args=provider acme 34 | - --args=--version "~> 2.0" 35 | - id: tfupdate 36 | name: Autoupdate TLS version 37 | args: 38 | - --args=provider tls 39 | - --args=--version "~> 4.0" 40 | - id: terraform_fmt 41 | - id: terraform_tflint 42 | args: 43 | - '--args=--only=terraform_deprecated_interpolation' 44 | - '--args=--only=terraform_deprecated_index' 45 | - '--args=--only=terraform_unused_declarations' 46 | - '--args=--only=terraform_comment_syntax' 47 | - '--args=--only=terraform_documented_outputs' 48 | - '--args=--only=terraform_documented_variables' 49 | - '--args=--only=terraform_typed_variables' 50 | - '--args=--only=terraform_module_pinned_source' 51 | - '--args=--only=terraform_required_version' 52 | - '--args=--only=terraform_required_providers' 53 | - '--args=--minimum-failure-severity=error' 54 | - --args=--fix 55 | - id: terraform_validate 56 | args: 57 | - --tf-init-args=-upgrade 58 | - --hook-config=--retry-once-with-cleanup=true 59 | - id: terraform_docs 60 | args: 61 | - --hook-config=--path-to-file=README.md 62 | - --hook-config=--add-to-existing-file=true 63 | - --hook-config=--create-file-if-not-exist=true 64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kasm Terraform Projects 2 | 3 | These projects are intended to be starting examples and for automating Kasm Workspaces deployments via terraform. 4 | Administators should review the projects and add additional customizations and security enhancements as desired. 5 | 6 | > ***NOTE:*** All of these deployments have been tested and validated with both [Terraform](https://www.terraform.io/) and [OpenTofu](https://opentofu.org/) 7 | 8 | # AWS 9 | - [Multi-Server Single Region](aws/standard/README.md) 10 | - [Multi-Region](aws/multi_region/README.md) 11 | 12 | # Oracle Cloud 13 | - [Single Server](oci/single_server/README.md) 14 | - [Multi-Server Single Region](oci/standard/README.md) 15 | 16 | # DigitalOcean 17 | - [Single Server](digitalocean/single_server/README.md) 18 | 19 | # GCP 20 | - [GCP Requirements](gcp/README.md) 21 | - [Multi-Server Single Region](gcp/MULTI_SERVER.md) 22 | - [Multi-Region](gcp/MULTI_REGION.md) -------------------------------------------------------------------------------- /aws/multi_region/agents/agent.tf: -------------------------------------------------------------------------------- 1 | resource "aws_instance" "agent" { 2 | count = var.num_agents 3 | ami = var.ec2_ami 4 | instance_type = var.agent_instance_type 5 | vpc_security_group_ids = [aws_security_group.agent.id] 6 | subnet_id = aws_subnet.agent.id 7 | key_name = var.aws_key_pair 8 | associate_public_ip_address = true 9 | iam_instance_profile = var.aws_ssm_instance_profile_name 10 | 11 | root_block_device { 12 | volume_size = var.agent_hdd_size_gb 13 | } 14 | 15 | user_data = templatefile("${path.module}/../userdata/agent_bootstrap.sh", 16 | { 17 | kasm_build_url = var.kasm_build 18 | swap_size = var.swap_size 19 | manager_address = var.aws_domain_name 20 | manager_token = var.manager_token 21 | } 22 | ) 23 | 24 | metadata_options { 25 | http_endpoint = "enabled" 26 | http_tokens = "required" 27 | http_put_response_hop_limit = 1 28 | instance_metadata_tags = null 29 | } 30 | 31 | tags = { 32 | Name = "${var.project_name}-${var.aws_region}-kasm-agent-${count.index}" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /aws/multi_region/agents/cert.tf: -------------------------------------------------------------------------------- 1 | resource "aws_acm_certificate" "this" { 2 | domain_name = var.aws_domain_name 3 | subject_alternative_names = ["*.${var.aws_domain_name}"] 4 | validation_method = "DNS" 5 | 6 | 7 | lifecycle { 8 | create_before_destroy = true 9 | } 10 | } 11 | 12 | resource "aws_route53_record" "certificate" { 13 | for_each = { 14 | for dvo in aws_acm_certificate.this.domain_validation_options : dvo.domain_name => { 15 | name = dvo.resource_record_name 16 | record = dvo.resource_record_value 17 | type = dvo.resource_record_type 18 | } 19 | } 20 | name = each.value.name 21 | type = each.value.type 22 | records = [each.value.record] 23 | zone_id = data.aws_route53_zone.this.id 24 | 25 | ttl = 30 26 | allow_overwrite = true 27 | } 28 | 29 | resource "aws_acm_certificate_validation" "this" { 30 | certificate_arn = aws_acm_certificate.this.arn 31 | validation_record_fqdns = [for record in aws_route53_record.certificate : record.fqdn] 32 | } 33 | -------------------------------------------------------------------------------- /aws/multi_region/agents/cpx.tf: -------------------------------------------------------------------------------- 1 | resource "aws_instance" "cpx" { 2 | count = var.num_cpx_nodes 3 | 4 | ami = var.ec2_ami 5 | instance_type = var.cpx_instance_type 6 | vpc_security_group_ids = aws_security_group.cpx[*].id 7 | subnet_id = aws_subnet.cpx[0].id 8 | key_name = var.aws_key_pair 9 | iam_instance_profile = var.aws_ssm_instance_profile_name 10 | 11 | root_block_device { 12 | volume_size = var.cpx_hdd_size_gb 13 | } 14 | 15 | user_data = templatefile("${path.module}/../userdata/cpx_bootstrap.sh", 16 | { 17 | kasm_build_url = var.kasm_build 18 | swap_size = var.swap_size 19 | manager_address = var.aws_domain_name 20 | service_registration_token = var.service_registration_token 21 | } 22 | ) 23 | 24 | metadata_options { 25 | http_endpoint = "enabled" 26 | http_tokens = "required" 27 | http_put_response_hop_limit = 1 28 | instance_metadata_tags = null 29 | } 30 | 31 | tags = { 32 | Name = "${var.project_name}-${var.aws_region}-kasm-cpx-${count.index}" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /aws/multi_region/agents/dependencies.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | kasm_agent_vpc_subnet_cidr_mask = split("/", var.agent_vpc_cidr)[1] 3 | kasm_agent_subnet_cidr_calculation = (8 - (local.kasm_agent_vpc_subnet_cidr_mask - 16)) 4 | kasm_agent_subnet_cidr_size = local.kasm_agent_subnet_cidr_calculation < 3 ? 3 : local.kasm_agent_subnet_cidr_calculation 5 | 6 | region_short_name_for_lb = join("", slice(split("-", var.aws_region), 1, 3)) 7 | } 8 | 9 | data "aws_route53_zone" "this" { 10 | name = var.aws_domain_name 11 | private_zone = false 12 | } 13 | 14 | data "aws_availability_zones" "available" { 15 | state = "available" 16 | } 17 | -------------------------------------------------------------------------------- /aws/multi_region/agents/elb.tf: -------------------------------------------------------------------------------- 1 | resource "aws_lb" "this" { 2 | name = "${var.project_name}-${var.aws_region}-proxy-lb" 3 | internal = false 4 | load_balancer_type = "application" 5 | security_groups = [aws_security_group.public_lb.id] 6 | subnets = aws_subnet.alb[*].id 7 | } 8 | 9 | resource "aws_lb_listener" "this" { 10 | load_balancer_arn = aws_lb.this.arn 11 | port = "443" 12 | protocol = "HTTPS" 13 | certificate_arn = aws_acm_certificate.this.arn 14 | 15 | default_action { 16 | type = "forward" 17 | target_group_arn = aws_lb_target_group.this.arn 18 | } 19 | } 20 | 21 | resource "aws_lb_target_group" "this" { 22 | name = "${var.project_name}-${local.region_short_name_for_lb}-tg" 23 | port = 443 24 | protocol = "HTTPS" 25 | vpc_id = aws_vpc.this.id 26 | 27 | health_check { 28 | path = "/desktop" 29 | matcher = 301 30 | protocol = "HTTPS" 31 | } 32 | } 33 | 34 | resource "aws_lb_target_group_attachment" "this" { 35 | count = var.num_proxy_nodes 36 | 37 | target_group_arn = aws_lb_target_group.this.arn 38 | target_id = aws_instance.proxy[count.index].id 39 | port = 443 40 | } 41 | 42 | resource "aws_route53_record" "alb" { 43 | zone_id = data.aws_route53_zone.this.zone_id 44 | name = "${local.region_short_name_for_lb}-proxy.${var.aws_domain_name}" 45 | type = "A" 46 | 47 | alias { 48 | name = aws_lb.this.dns_name 49 | zone_id = aws_lb.this.zone_id 50 | evaluate_target_health = false 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /aws/multi_region/agents/provider.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = "~> 1.0" 3 | 4 | required_providers { 5 | aws = { 6 | source = "hashicorp/aws" 7 | version = "~> 5.0" 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /aws/multi_region/agents/proxy.tf: -------------------------------------------------------------------------------- 1 | resource "aws_instance" "proxy" { 2 | count = var.num_proxy_nodes 3 | 4 | ami = var.ec2_ami 5 | instance_type = var.proxy_instance_type 6 | vpc_security_group_ids = [aws_security_group.proxy.id] 7 | subnet_id = aws_subnet.proxy[(count.index)].id 8 | key_name = var.aws_key_pair 9 | iam_instance_profile = var.aws_ssm_instance_profile_name 10 | 11 | root_block_device { 12 | volume_size = var.proxy_hdd_size_gb 13 | } 14 | 15 | user_data = templatefile("${path.module}/../userdata/proxy_bootstrap.sh", 16 | { 17 | kasm_build_url = var.kasm_build 18 | swap_size = var.swap_size 19 | manager_address = var.aws_domain_name 20 | proxy_alb_address = "${var.aws_region}-proxy.${var.aws_domain_name}" 21 | } 22 | ) 23 | 24 | metadata_options { 25 | http_endpoint = "enabled" 26 | http_tokens = "required" 27 | http_put_response_hop_limit = 1 28 | instance_metadata_tags = null 29 | } 30 | 31 | tags = { 32 | Name = "${var.project_name}-${var.aws_region}-kasm-proxy-${count.index}" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /aws/multi_region/agents/routes.tf: -------------------------------------------------------------------------------- 1 | resource "aws_route_table" "internet_gateway" { 2 | vpc_id = aws_vpc.this.id 3 | 4 | route { 5 | cidr_block = var.anywhere 6 | gateway_id = aws_internet_gateway.this.id 7 | } 8 | 9 | tags = { 10 | Name = "${var.project_name}-kasm-internet-gateway-route" 11 | } 12 | } 13 | 14 | resource "aws_route_table" "nat_gateway" { 15 | vpc_id = aws_vpc.this.id 16 | 17 | route { 18 | cidr_block = var.anywhere 19 | nat_gateway_id = aws_nat_gateway.this.id 20 | } 21 | 22 | tags = { 23 | Name = "${var.project_name}-kasm-nat-gateway-route" 24 | } 25 | } 26 | 27 | resource "aws_route_table_association" "alb" { 28 | count = 2 29 | subnet_id = aws_subnet.alb[(count.index)].id 30 | route_table_id = aws_route_table.internet_gateway.id 31 | } 32 | 33 | resource "aws_route_table_association" "proxy" { 34 | count = var.num_proxy_nodes 35 | subnet_id = aws_subnet.proxy[(count.index)].id 36 | route_table_id = aws_route_table.nat_gateway.id 37 | } 38 | 39 | resource "aws_route_table_association" "agent" { 40 | subnet_id = aws_subnet.agent.id 41 | route_table_id = aws_route_table.internet_gateway.id 42 | } 43 | 44 | resource "aws_route_table_association" "cpx" { 45 | count = var.num_cpx_nodes > 0 ? 1 : 0 46 | subnet_id = aws_subnet.cpx[0].id 47 | route_table_id = aws_route_table.nat_gateway.id 48 | } 49 | 50 | resource "aws_route_table_association" "windows" { 51 | count = var.num_cpx_nodes > 0 ? 1 : 0 52 | subnet_id = aws_subnet.windows[0].id 53 | route_table_id = aws_route_table.internet_gateway.id 54 | } 55 | -------------------------------------------------------------------------------- /aws/multi_region/agents/subnet.tf: -------------------------------------------------------------------------------- 1 | resource "aws_subnet" "alb" { 2 | count = 2 3 | vpc_id = aws_vpc.this.id 4 | cidr_block = cidrsubnet(var.agent_vpc_cidr, local.kasm_agent_subnet_cidr_size, count.index) 5 | availability_zone = data.aws_availability_zones.available.names[(count.index)] 6 | map_public_ip_on_launch = true 7 | 8 | tags = { 9 | Name = "${var.project_name}-${var.aws_region}-kasm-alb-subnet" 10 | } 11 | } 12 | 13 | resource "aws_subnet" "proxy" { 14 | count = var.num_proxy_nodes 15 | vpc_id = aws_vpc.this.id 16 | cidr_block = cidrsubnet(var.agent_vpc_cidr, local.kasm_agent_subnet_cidr_size, (count.index + 2)) 17 | availability_zone = data.aws_availability_zones.available.names[(count.index)] 18 | 19 | tags = { 20 | Name = "${var.project_name}-${var.aws_region}-kasm-proxy-subnet" 21 | } 22 | } 23 | 24 | resource "aws_subnet" "agent" { 25 | vpc_id = aws_vpc.this.id 26 | cidr_block = cidrsubnet(var.agent_vpc_cidr, local.kasm_agent_subnet_cidr_size, 4) 27 | availability_zone = data.aws_availability_zones.available.names[0] 28 | map_public_ip_on_launch = true 29 | 30 | tags = { 31 | Name = "${var.project_name}-${var.aws_region}-kasm-agent-subnet" 32 | } 33 | } 34 | 35 | resource "aws_subnet" "cpx" { 36 | count = var.num_cpx_nodes > 0 ? 1 : 0 37 | 38 | vpc_id = aws_vpc.this.id 39 | cidr_block = cidrsubnet(var.agent_vpc_cidr, local.kasm_agent_subnet_cidr_size, 5) 40 | availability_zone = data.aws_availability_zones.available.names[0] 41 | 42 | tags = { 43 | Name = "${var.project_name}-${var.aws_region}-kasm-cpx-subnet" 44 | } 45 | } 46 | 47 | resource "aws_subnet" "windows" { 48 | count = var.num_cpx_nodes > 0 ? 1 : 0 49 | 50 | vpc_id = aws_vpc.this.id 51 | cidr_block = cidrsubnet(var.agent_vpc_cidr, local.kasm_agent_subnet_cidr_size, 6) 52 | availability_zone = data.aws_availability_zones.available.names[0] 53 | map_public_ip_on_launch = true 54 | 55 | tags = { 56 | Name = "${var.project_name}-${var.aws_region}-kasm-windows-subnet" 57 | } 58 | } 59 | 60 | -------------------------------------------------------------------------------- /aws/multi_region/agents/vpc.tf: -------------------------------------------------------------------------------- 1 | resource "aws_vpc" "this" { 2 | cidr_block = var.agent_vpc_cidr 3 | enable_dns_hostnames = true 4 | enable_dns_support = true 5 | 6 | tags = { 7 | Name = "${var.project_name}-${var.aws_region}-kasm-vpc" 8 | } 9 | } 10 | 11 | resource "aws_internet_gateway" "this" { 12 | vpc_id = aws_vpc.this.id 13 | 14 | tags = { 15 | Name = "${var.project_name}-${var.aws_region}-kasm-ig" 16 | } 17 | } 18 | 19 | resource "aws_eip" "this" { 20 | domain = "vpc" 21 | } 22 | 23 | resource "aws_nat_gateway" "this" { 24 | allocation_id = aws_eip.this.id 25 | subnet_id = aws_subnet.alb[0].id 26 | 27 | tags = { 28 | Name = "${var.project_name}-${var.aws_region}-kasm-nat" 29 | } 30 | 31 | depends_on = [aws_internet_gateway.this] 32 | } 33 | -------------------------------------------------------------------------------- /aws/multi_region/aws_key_pairs/README.md: -------------------------------------------------------------------------------- 1 | # ssh_keys 2 | 3 | 4 | ## Requirements 5 | 6 | | Name | Version | 7 | |------|---------| 8 | | [terraform](#requirement\_terraform) | ~> 1.0 | 9 | | [tls](#requirement\_tls) | ~> 4.0 | 10 | 11 | ## Providers 12 | 13 | | Name | Version | 14 | |------|---------| 15 | | [tls](#provider\_tls) | 4.0.4 | 16 | 17 | ## Modules 18 | 19 | No modules. 20 | 21 | ## Resources 22 | 23 | | Name | Type | 24 | |------|------| 25 | | [tls_private_key.ssh_key](https://registry.terraform.io/providers/hashicorp/tls/latest/docs/resources/private_key) | resource | 26 | 27 | ## Inputs 28 | 29 | No inputs. 30 | 31 | ## Outputs 32 | 33 | | Name | Description | 34 | |------|-------------| 35 | | [ssh\_key\_info](#output\_ssh\_key\_info) | SSH Keys for use with Kasm Deployment | 36 | 37 | -------------------------------------------------------------------------------- /aws/multi_region/aws_key_pairs/main.tf: -------------------------------------------------------------------------------- 1 | resource "aws_key_pair" "ssh_keys" { 2 | key_name = "${var.project_name}-ssh-key" 3 | public_key = var.ssh_authorized_keys 4 | } -------------------------------------------------------------------------------- /aws/multi_region/aws_key_pairs/outputs.tf: -------------------------------------------------------------------------------- 1 | output "aws_key_pair_name" { 2 | description = "The name of an aws keypair to use." 3 | value = aws_key_pair.ssh_keys.key_name 4 | } -------------------------------------------------------------------------------- /aws/multi_region/aws_key_pairs/providers.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = "~> 1.0" 3 | required_providers { 4 | aws = { 5 | source = "hashicorp/aws" 6 | version = "~> 5.0" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /aws/multi_region/aws_key_pairs/variables.tf: -------------------------------------------------------------------------------- 1 | variable "ssh_authorized_keys" { 2 | description = "The SSH Public Keys to be installed on the OCI compute instance" 3 | type = string 4 | } 5 | 6 | variable "project_name" { 7 | description = "The name of the deployment (e.g dev, staging). A short single word" 8 | type = string 9 | } -------------------------------------------------------------------------------- /aws/multi_region/diagram/aws_multi_region.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kasmtech/terraform/f8618bc382790d4684127b68e3319319d3afefb0/aws/multi_region/diagram/aws_multi_region.png -------------------------------------------------------------------------------- /aws/multi_region/outputs.tf: -------------------------------------------------------------------------------- 1 | output "region1_zone_settings" { 2 | description = "Upstream Auth and Proxy settings to apply to Kasm Primary Region Zone configuration" 3 | value = < { 15 | name = dvo.resource_record_name 16 | record = dvo.resource_record_value 17 | type = dvo.resource_record_type 18 | } 19 | } 20 | name = each.value.name 21 | type = each.value.type 22 | records = [each.value.record] 23 | zone_id = data.aws_route53_zone.this.id 24 | 25 | ttl = 30 26 | allow_overwrite = true 27 | } 28 | 29 | 30 | resource "aws_acm_certificate_validation" "this" { 31 | certificate_arn = aws_acm_certificate.this.arn 32 | validation_record_fqdns = [for record in aws_route53_record.certificate : record.fqdn] 33 | } 34 | -------------------------------------------------------------------------------- /aws/multi_region/primary/db.tf: -------------------------------------------------------------------------------- 1 | resource "aws_instance" "db" { 2 | ami = var.ec2_ami 3 | instance_type = var.db_instance_type 4 | vpc_security_group_ids = [aws_security_group.db.id] 5 | subnet_id = aws_subnet.db.id 6 | key_name = var.aws_key_pair 7 | iam_instance_profile = var.create_aws_ssm_iam_role ? aws_iam_instance_profile.this[0].name : var.aws_ssm_instance_profile_name 8 | 9 | root_block_device { 10 | volume_size = var.db_hdd_size_gb 11 | } 12 | 13 | user_data = templatefile("${path.module}/../userdata/db_bootstrap.sh", 14 | { 15 | kasm_build_url = var.kasm_build 16 | user_password = var.user_password 17 | admin_password = var.admin_password 18 | redis_password = var.redis_password 19 | database_password = var.database_password 20 | manager_token = var.manager_token 21 | service_registration_token = var.service_registration_token 22 | swap_size = var.swap_size 23 | } 24 | ) 25 | 26 | metadata_options { 27 | http_endpoint = "enabled" 28 | http_tokens = "required" 29 | http_put_response_hop_limit = 1 30 | instance_metadata_tags = null 31 | } 32 | 33 | tags = { 34 | Name = "${var.project_name}-kasm-db" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /aws/multi_region/primary/dependencies.tf: -------------------------------------------------------------------------------- 1 | data "aws_availability_zones" "available" { 2 | state = "available" 3 | } 4 | 5 | data "aws_elb_service_account" "main" {} 6 | 7 | data "aws_route53_zone" "this" { 8 | name = var.aws_domain_name 9 | private_zone = false 10 | } 11 | -------------------------------------------------------------------------------- /aws/multi_region/primary/lb_s3_log_bucket.tf: -------------------------------------------------------------------------------- 1 | resource "aws_s3_bucket" "this" { 2 | bucket_prefix = "${var.project_name}-${var.zone_name}-" 3 | force_destroy = true 4 | } 5 | 6 | resource "aws_s3_bucket_policy" "this" { 7 | bucket = aws_s3_bucket.this.id 8 | 9 | policy = jsonencode({ 10 | Id = "Policy" 11 | Version = "2012-10-17" 12 | Statement = [ 13 | { 14 | Action = [ 15 | "s3:PutObject" 16 | ] 17 | Effect = "Allow" 18 | Resource = "${aws_s3_bucket.this.arn}/AWSLogs/*" 19 | Principal = { 20 | AWS = [ 21 | data.aws_elb_service_account.main.arn 22 | ] 23 | } 24 | } 25 | ] 26 | }) 27 | } 28 | 29 | resource "aws_s3_bucket_server_side_encryption_configuration" "this" { 30 | bucket = aws_s3_bucket.this.id 31 | 32 | rule { 33 | apply_server_side_encryption_by_default { 34 | sse_algorithm = "AES256" 35 | } 36 | } 37 | } 38 | 39 | resource "aws_s3_bucket_public_access_block" "this" { 40 | bucket = aws_s3_bucket.this.id 41 | block_public_acls = true 42 | block_public_policy = true 43 | ignore_public_acls = true 44 | restrict_public_buckets = true 45 | } 46 | -------------------------------------------------------------------------------- /aws/multi_region/primary/output.tf: -------------------------------------------------------------------------------- 1 | output "certificate_arn" { 2 | description = "AWS Certificate manager certificate ARN" 3 | value = aws_acm_certificate_validation.this.certificate_arn 4 | } 5 | 6 | output "lb_subnet_ids" { 7 | description = "A list of the Public LB subnet IDs" 8 | value = aws_subnet.alb[*].id 9 | } 10 | 11 | output "webapp_subnet_ids" { 12 | description = "A list of the Kasm Webapp subnet IDs" 13 | value = aws_subnet.webapp[*].id 14 | } 15 | 16 | output "agent_subnet_id" { 17 | description = "Kasm Agent Primary region subnet ID" 18 | value = aws_subnet.agent.id 19 | } 20 | 21 | output "cpx_subnet_id" { 22 | description = "Kasm cpx RDP Primary region subnet ID" 23 | value = one(aws_subnet.cpx[*].id) 24 | } 25 | 26 | output "windows_subnet_id" { 27 | description = "Kasm Windows Primary region subnet ID" 28 | value = one(aws_subnet.windows[*].id) 29 | } 30 | 31 | output "kasm_db_ip" { 32 | description = "Kasm Database server subnet ID" 33 | value = aws_instance.db.private_ip 34 | } 35 | 36 | output "primary_vpc_id" { 37 | description = "Kasm VPC ID" 38 | value = aws_vpc.this.id 39 | } 40 | 41 | output "lb_log_bucket" { 42 | description = "Load balancer logging bucket name" 43 | value = aws_s3_bucket.this.bucket 44 | } 45 | 46 | output "lb_security_group_id" { 47 | description = "Kasm Load balancer security group ID" 48 | value = aws_security_group.public_lb.id 49 | } 50 | 51 | output "webapp_security_group_id" { 52 | description = "Kasm Webapp security group ID" 53 | value = aws_security_group.webapp.id 54 | } 55 | 56 | output "agent_security_group_id" { 57 | description = "Kasm Agent Primary region security group ID" 58 | value = aws_security_group.agent.id 59 | } 60 | 61 | output "cpx_security_group_id" { 62 | description = "Kasm Connection Proxy Primary region security group ID" 63 | value = one(aws_security_group.cpx[*].id) 64 | } 65 | 66 | output "windows_security_group_id" { 67 | description = "Kasm Windows Primary region security group ID" 68 | value = one(aws_security_group.windows[*].id) 69 | } 70 | 71 | output "ssm_iam_profile" { 72 | description = "The SSM IAM Instance Profile name" 73 | value = var.aws_ssm_iam_role_name == "" ? aws_iam_instance_profile.this[0].name : var.aws_ssm_iam_role_name 74 | } 75 | 76 | output "nat_gateway_ip" { 77 | description = "The NAT Gateway IP returned in CIDR notation for use with Windows security group rules" 78 | value = "${aws_nat_gateway.this.public_ip}/32" 79 | } 80 | -------------------------------------------------------------------------------- /aws/multi_region/primary/provider.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = "~> 1.0" 3 | 4 | required_providers { 5 | aws = { 6 | source = "hashicorp/aws" 7 | version = "~> 5.0" 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /aws/multi_region/primary/routes.tf: -------------------------------------------------------------------------------- 1 | resource "aws_route_table" "internet_gateway" { 2 | vpc_id = aws_vpc.this.id 3 | 4 | route { 5 | cidr_block = var.anywhere 6 | gateway_id = aws_internet_gateway.this.id 7 | } 8 | 9 | tags = { 10 | Name = "${var.project_name}-kasm-default-route" 11 | } 12 | } 13 | 14 | resource "aws_route_table" "nat_gateway" { 15 | vpc_id = aws_vpc.this.id 16 | 17 | route { 18 | cidr_block = var.anywhere 19 | nat_gateway_id = aws_nat_gateway.this.id 20 | } 21 | 22 | tags = { 23 | Name = "${var.project_name}-kasm-nat-gateway-route" 24 | } 25 | } 26 | 27 | resource "aws_route_table_association" "alb" { 28 | count = 2 29 | subnet_id = aws_subnet.alb[count.index].id 30 | route_table_id = aws_route_table.internet_gateway.id 31 | } 32 | 33 | resource "aws_route_table_association" "webapp" { 34 | count = var.num_webapps 35 | subnet_id = aws_subnet.webapp[count.index].id 36 | route_table_id = aws_route_table.nat_gateway.id 37 | } 38 | 39 | resource "aws_route_table_association" "db" { 40 | subnet_id = aws_subnet.db.id 41 | route_table_id = aws_route_table.nat_gateway.id 42 | } 43 | 44 | resource "aws_route_table_association" "cpx" { 45 | count = var.num_cpx_nodes > 0 ? 1 : 0 46 | 47 | subnet_id = one(aws_subnet.cpx[*].id) 48 | route_table_id = aws_route_table.nat_gateway.id 49 | } 50 | 51 | resource "aws_route_table_association" "agent" { 52 | subnet_id = aws_subnet.agent.id 53 | route_table_id = aws_route_table.internet_gateway.id 54 | } 55 | 56 | resource "aws_route_table_association" "windows" { 57 | count = var.num_cpx_nodes > 0 ? 1 : 0 58 | 59 | subnet_id = one(aws_subnet.windows[*].id) 60 | route_table_id = aws_route_table.internet_gateway.id 61 | } 62 | -------------------------------------------------------------------------------- /aws/multi_region/primary/ssm.tf: -------------------------------------------------------------------------------- 1 | data "aws_iam_policy_document" "this" { 2 | statement { 3 | effect = "Allow" 4 | 5 | principals { 6 | type = "Service" 7 | identifiers = [ 8 | "ec2.amazonaws.com" 9 | ] 10 | } 11 | 12 | actions = ["sts:AssumeRole"] 13 | } 14 | } 15 | 16 | resource "aws_iam_role" "this" { 17 | count = var.create_aws_ssm_iam_role ? 1 : 0 18 | 19 | name = var.aws_ssm_iam_role_name != "" ? var.aws_ssm_iam_role_name : "Kasm_SSM_IAM_Instance_Role" 20 | assume_role_policy = data.aws_iam_policy_document.this.json 21 | } 22 | 23 | resource "aws_iam_role_policy_attachment" "this" { 24 | count = var.create_aws_ssm_iam_role ? 1 : 0 25 | 26 | policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore" 27 | role = one(aws_iam_role.this[*].name) 28 | } 29 | 30 | resource "aws_iam_instance_profile" "this" { 31 | count = var.create_aws_ssm_iam_role ? 1 : 0 32 | 33 | name = var.aws_ssm_instance_profile_name != "" ? var.aws_ssm_instance_profile_name : "Kasm_SSM_Instance_Profile" 34 | role = one(aws_iam_role.this[*].name) 35 | } 36 | -------------------------------------------------------------------------------- /aws/multi_region/primary/subnet.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | kasm_vpc_subnet_cidr_mask = split("/", var.vpc_subnet_cidr)[1] 3 | kasm_server_subnet_cidr_calculation = (8 - (local.kasm_vpc_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 | 7 | ## Will create Agent subnet x.x.0.0/24 and x.x.1.0/24 (assuming a VPC Subnet CIDR between x.x.0.0/16 and x.x.0.0/21) 8 | resource "aws_subnet" "alb" { 9 | count = 2 10 | 11 | vpc_id = aws_vpc.this.id 12 | cidr_block = cidrsubnet(var.vpc_subnet_cidr, local.kasm_server_subnet_cidr_size, count.index) 13 | availability_zone = data.aws_availability_zones.available.names[count.index] 14 | 15 | tags = { 16 | Name = "${var.project_name}-kasm_alb_subnet" 17 | } 18 | } 19 | 20 | ## Will create WebApp subnets x.x.2.0/24 and x.x.3.0/24 (assuming a VPC Subnet CIDR between x.x.0.0/16 and x.x.0.0/22 and 2 webapps) 21 | resource "aws_subnet" "webapp" { 22 | count = var.num_webapps 23 | 24 | vpc_id = aws_vpc.this.id 25 | cidr_block = cidrsubnet(var.vpc_subnet_cidr, local.kasm_server_subnet_cidr_size, (count.index + 2)) 26 | availability_zone = data.aws_availability_zones.available.names[count.index] 27 | 28 | tags = { 29 | Name = "${var.project_name}-kasm-webapp-subnet" 30 | } 31 | } 32 | 33 | ## Will create DB subnet x.x.4.0/24 (assuming a VPC Subnet CIDR between x.x.0.0/16 and x.x.0.0/22) 34 | resource "aws_subnet" "db" { 35 | vpc_id = aws_vpc.this.id 36 | cidr_block = cidrsubnet(var.vpc_subnet_cidr, local.kasm_server_subnet_cidr_size, 4) 37 | availability_zone = data.aws_availability_zones.available.names[0] 38 | 39 | tags = { 40 | Name = "${var.project_name}-kasm_db_subnet" 41 | } 42 | } 43 | 44 | ## Will create Agent subnet x.x.3.0/24 (assuming a VPC Subnet CIDR between x.x.0.0/16 and x.x.0.0/22) 45 | resource "aws_subnet" "agent" { 46 | vpc_id = aws_vpc.this.id 47 | cidr_block = cidrsubnet(var.vpc_subnet_cidr, local.kasm_server_subnet_cidr_size, 5) 48 | availability_zone = data.aws_availability_zones.available.names[1] 49 | 50 | tags = { 51 | Name = "${var.project_name}-agent-subnet" 52 | } 53 | } 54 | 55 | ## Will create Agent subnet x.x.4.0/24 (assuming a VPC Subnet CIDR between x.x.0.0/16 and x.x.0.0/22) 56 | resource "aws_subnet" "cpx" { 57 | count = var.num_cpx_nodes > 0 ? 1 : 0 58 | vpc_id = aws_vpc.this.id 59 | cidr_block = cidrsubnet(var.vpc_subnet_cidr, local.kasm_server_subnet_cidr_size, 6) 60 | availability_zone = data.aws_availability_zones.available.names[0] 61 | 62 | tags = { 63 | Name = "${var.project_name}-cpx-subnet" 64 | } 65 | } 66 | 67 | ## Will create Agent subnet x.x.5.0/24 (assuming a VPC Subnet CIDR between x.x.0.0/16 and x.x.0.0/22) 68 | resource "aws_subnet" "windows" { 69 | count = var.num_cpx_nodes > 0 ? 1 : 0 70 | vpc_id = aws_vpc.this.id 71 | cidr_block = cidrsubnet(var.vpc_subnet_cidr, local.kasm_server_subnet_cidr_size, 7) 72 | availability_zone = data.aws_availability_zones.available.names[1] 73 | 74 | tags = { 75 | Name = "${var.project_name}-windows-subnet" 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /aws/multi_region/primary/vpc.tf: -------------------------------------------------------------------------------- 1 | resource "aws_vpc" "this" { 2 | cidr_block = var.vpc_subnet_cidr 3 | enable_dns_hostnames = true 4 | enable_dns_support = true 5 | tags = { 6 | Name = "${var.project_name}-kasm-db-vpc" 7 | } 8 | } 9 | 10 | resource "aws_internet_gateway" "this" { 11 | vpc_id = aws_vpc.this.id 12 | tags = { 13 | Name = "${var.project_name}-kasm-ig" 14 | } 15 | } 16 | 17 | resource "aws_eip" "this" { 18 | domain = "vpc" 19 | } 20 | 21 | resource "aws_nat_gateway" "this" { 22 | allocation_id = aws_eip.this.id 23 | subnet_id = aws_subnet.alb[0].id 24 | 25 | tags = { 26 | Name = "${var.project_name}-${var.aws_region}-kasm-nat" 27 | } 28 | 29 | depends_on = [aws_internet_gateway.this] 30 | } 31 | -------------------------------------------------------------------------------- /aws/multi_region/provider.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = "~> 1.0" 3 | 4 | required_providers { 5 | aws = { 6 | source = "hashicorp/aws" 7 | version = "~> 5.0" 8 | } 9 | } 10 | } 11 | 12 | provider "aws" { 13 | access_key = var.aws_access_key 14 | secret_key = var.aws_secret_key 15 | region = var.aws_primary_region 16 | 17 | default_tags { 18 | tags = var.aws_default_tags 19 | } 20 | } 21 | 22 | provider "aws" { 23 | access_key = var.aws_access_key 24 | secret_key = var.aws_secret_key 25 | region = var.secondary_regions_settings.region2.agent_region 26 | alias = "region2" 27 | 28 | default_tags { 29 | tags = var.aws_default_tags 30 | } 31 | } 32 | 33 | ############################################################################## 34 | ### 35 | ### Uncomment the below provider section if you want to deploy a 3rd region. 36 | ### 37 | ### Copy/paste the provider below to deploy additional regions, then refer 38 | ### to the README.md, the deployment.tf file, and the settings.tfvars file for 39 | ### code blocks to copy/paste/configure to deploy the new regions. 40 | ### 41 | ############################################################################## 42 | # provider "aws" { 43 | # access_key = var.aws_access_key 44 | # secret_key = var.aws_secret_key 45 | # region = var.secondary_regions_settings.region3.agent_region 46 | # alias = "region3" 47 | 48 | # default_tags { 49 | # tags = var.aws_default_tags 50 | # } 51 | # } 52 | -------------------------------------------------------------------------------- /aws/multi_region/secrets.tfvars.example: -------------------------------------------------------------------------------- 1 | aws_access_key = "" 2 | aws_secret_key = "" -------------------------------------------------------------------------------- /aws/multi_region/ssh_keys/README.md: -------------------------------------------------------------------------------- 1 | # ssh_keys 2 | 3 | 4 | ## Requirements 5 | 6 | | Name | Version | 7 | |------|---------| 8 | | [terraform](#requirement\_terraform) | ~> 1.0 | 9 | | [tls](#requirement\_tls) | ~> 4.0 | 10 | 11 | ## Providers 12 | 13 | | Name | Version | 14 | |------|---------| 15 | | [tls](#provider\_tls) | 4.0.4 | 16 | 17 | ## Modules 18 | 19 | No modules. 20 | 21 | ## Resources 22 | 23 | | Name | Type | 24 | |------|------| 25 | | [tls_private_key.ssh_key](https://registry.terraform.io/providers/hashicorp/tls/latest/docs/resources/private_key) | resource | 26 | 27 | ## Inputs 28 | 29 | No inputs. 30 | 31 | ## Outputs 32 | 33 | | Name | Description | 34 | |------|-------------| 35 | | [ssh\_key\_info](#output\_ssh\_key\_info) | SSH Keys for use with Kasm Deployment | 36 | 37 | -------------------------------------------------------------------------------- /aws/multi_region/ssh_keys/main.tf: -------------------------------------------------------------------------------- 1 | resource "tls_private_key" "ssh_key" { 2 | count = var.ssh_authorized_keys == "" ? 1 : 0 3 | algorithm = "ED25519" 4 | } 5 | -------------------------------------------------------------------------------- /aws/multi_region/ssh_keys/outputs.tf: -------------------------------------------------------------------------------- 1 | output "ssh_key_info" { 2 | description = "SSH Keys for use with Kasm Deployment" 3 | value = <<-SSHKEYS 4 | SSH Keys: 5 | %{if var.ssh_authorized_keys == ""} 6 | Public Key: ${tls_private_key.ssh_key[0].public_key_openssh} 7 | Private Key: 8 | ${tls_private_key.ssh_key[0].private_key_openssh} 9 | %{endif} 10 | SSHKEYS 11 | } 12 | 13 | output "ssh_public_key" { 14 | description = "The name of an aws keypair to use." 15 | value = var.ssh_authorized_keys == "" ? tls_private_key.ssh_key[0].public_key_openssh : var.ssh_authorized_keys 16 | 17 | } -------------------------------------------------------------------------------- /aws/multi_region/ssh_keys/providers.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = "~> 1.0" 3 | required_providers { 4 | tls = { 5 | source = "hashicorp/tls" 6 | version = "~> 4.0" 7 | } 8 | aws = { 9 | source = "hashicorp/aws" 10 | version = "~> 5.0" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /aws/multi_region/ssh_keys/variables.tf: -------------------------------------------------------------------------------- 1 | variable "ssh_authorized_keys" { 2 | description = "The SSH Public Keys to be installed on the OCI compute instance" 3 | type = string 4 | } 5 | 6 | variable "project_name" { 7 | description = "The name of the deployment (e.g dev, staging). A short single word" 8 | type = string 9 | } -------------------------------------------------------------------------------- /aws/multi_region/terraform.tfvars: -------------------------------------------------------------------------------- 1 | ## AWS Environment settings 2 | ssh_authorized_keys = "" 3 | aws_primary_region = "" 4 | aws_domain_name = "example.kasmweb.com" 5 | primary_vpc_subnet_cidr = "10.0.0.0/16" 6 | 7 | ## Kasm deployment project 8 | project_name = "" 9 | 10 | ## Kasm passwords 11 | database_password = "changeme" 12 | redis_password = "changeme" 13 | user_password = "changeme" 14 | admin_password = "changeme" 15 | manager_token = "changeme" 16 | service_registration_token = "changeme" 17 | 18 | ## Kasm download URL 19 | kasm_build = "https://kasm-static-content.s3.amazonaws.com/kasm_release_1.17.0.bbc15c.tar.gz" 20 | 21 | ## VM Public Access subnets 22 | web_access_cidrs = ["0.0.0.0/0"] 23 | 24 | ## AWS SSM setup for console/SSH access to VMs behind NAT gateway 25 | create_aws_ssm_iam_role = true 26 | aws_ssm_iam_role_name = "" 27 | aws_ssm_instance_profile_name = "" 28 | 29 | ## Kasm Server Settings 30 | swap_size = 2 31 | primary_region_ec2_ami_id = "" 32 | 33 | ## Kasm Webapp Instance Settings 34 | num_webapps = 2 35 | webapp_instance_type = "t3.small" 36 | webapp_hdd_size_gb = 50 37 | 38 | ## Kasm DB Instance Settings 39 | db_instance_type = "t3.medium" 40 | db_hdd_size_gb = 80 41 | 42 | ## Kasm Agent Instance Settings 43 | num_agents = 2 44 | agent_instance_type = "t3.medium" 45 | agent_hdd_size_gb = 150 46 | 47 | ## Kasm CPX Instance Settings 48 | num_cpx_nodes = 1 49 | cpx_instance_type = "t3.small" 50 | cpx_hdd_size_gb = 50 51 | 52 | ## Kasm Dedicated Proxy Instance Settings 53 | num_proxy_nodes = 2 54 | proxy_hdd_size_gb = 40 55 | proxy_instance_type = "t3.micro" 56 | 57 | ## Settings for all additional Agent regions 58 | secondary_regions_settings = { 59 | region2 = { 60 | agent_region = "" 61 | ec2_ami_id = "" 62 | agent_vpc_cidr = "10.1.0.0/16" 63 | } 64 | 65 | ####################################################################### 66 | ### 67 | ### Uncomment and update the settings below for an third region. 68 | ### Copy/paste the settings below (changing the region number) for any 69 | ### additional regions. 70 | ### 71 | ### Make sure to add a provider section for each additional region in 72 | ### the providers.tf file. 73 | ### 74 | ####################################################################### 75 | # region3 = { 76 | # agent_region = "" 77 | # ec2_ami_id = "" 78 | # agent_vpc_cidr = "10.2.0.0/16" 79 | # } 80 | } 81 | 82 | ## Default tags for all AWS resources 83 | aws_default_tags = { 84 | Deployed_by = "Terraform" 85 | Deployment_type = "Multi-Region" 86 | Service_name = "Kasm Workspaces" 87 | Kasm_version = "1.17.0" 88 | } 89 | -------------------------------------------------------------------------------- /aws/multi_region/userdata/agent_bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -ex 3 | echo "Starting Kasm Workspaces Agent Install" 4 | 5 | /bin/dd if=/dev/zero of=/var/swap.1 bs=1M count="${swap_size}" 6 | /sbin/mkswap /var/swap.1 7 | chmod 600 /var/swap.1 8 | /sbin/swapon /var/swap.1 9 | 10 | echo '/var/swap.1 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 | -------------------------------------------------------------------------------- /aws/multi_region/userdata/cpx_bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -ex 3 | echo "Starting Kasm Workspaces Agent Install" 4 | 5 | /bin/dd if=/dev/zero of=/var/swap.1 bs=1M count="${swap_size}" 6 | /sbin/mkswap /var/swap.1 7 | chmod 600 /var/swap.1 8 | /sbin/swapon /var/swap.1 9 | 10 | echo '/var/swap.1 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 | -------------------------------------------------------------------------------- /aws/multi_region/userdata/db_bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -ex 3 | echo "Starting Kasm Workspaces Install" 4 | 5 | /bin/dd if=/dev/zero of=/var/swap.1 bs=1M count="${swap_size}" 6 | /sbin/mkswap /var/swap.1 7 | chmod 600 /var/swap.1 8 | /sbin/swapon /var/swap.1 9 | 10 | echo '/var/swap.1 swap swap defaults 0 0' | tee -a /etc/fstab 11 | 12 | cd /tmp 13 | 14 | wget "${kasm_build_url}" -O kasm_workspaces.tar.gz 15 | tar -xf kasm_workspaces.tar.gz 16 | 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}" 17 | 18 | echo "Done" 19 | -------------------------------------------------------------------------------- /aws/multi_region/userdata/proxy_bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -ex 3 | echo "Starting Kasm Workspaces Agent Install" 4 | 5 | /bin/dd if=/dev/zero of=/var/swap.1 bs=1M count="${swap_size}" 6 | /sbin/mkswap /var/swap.1 7 | chmod 600 /var/swap.1 8 | /sbin/swapon /var/swap.1 9 | 10 | echo '/var/swap.1 swap swap defaults 0 0' | tee -a /etc/fstab 11 | 12 | cd /tmp 13 | 14 | wget ${kasm_build_url} -O kasm_workspaces.tar.gz 15 | tar -xf kasm_workspaces.tar.gz 16 | 17 | echo "Waiting for Kasm WebApp availability..." 18 | while ! (curl -k https://${manager_address}/api/__healthcheck 2>/dev/null | grep -q true) 19 | do 20 | echo "Waiting for API server..." 21 | sleep 5 22 | done 23 | echo "WebApp is alive" 24 | 25 | bash kasm_release/install.sh -S proxy -e -H -p "${proxy_alb_address}" -n "${manager_address}" 26 | 27 | echo "Done" 28 | -------------------------------------------------------------------------------- /aws/multi_region/userdata/webapp_bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -ex 3 | echo "Starting Kasm Workspaces Install" 4 | 5 | /bin/dd if=/dev/zero of=/var/swap.1 bs=1M count="${swap_size}" 6 | /sbin/mkswap /var/swap.1 7 | chmod 600 /var/swap.1 8 | /sbin/swapon /var/swap.1 9 | 10 | echo '/var/swap.1 swap swap defaults 0 0' | tee -a /etc/fstab 11 | 12 | cd /tmp 13 | 14 | wget "${kasm_build_url}" -O kasm_workspaces.tar.gz 15 | tar -xf kasm_workspaces.tar.gz 16 | 17 | echo "Checking for Kasm DB and Redis..." 18 | apt-get update && apt-get install -y netcat-openbsd 19 | while ! nc -w 1 -z "${db_ip}" 5432; do 20 | echo "Database not ready..." 21 | sleep 5 22 | done 23 | echo "DB is alive" 24 | 25 | while ! nc -w 1 -z "${db_ip}" 6379; do 26 | echo "Redis not ready..." 27 | sleep 5 28 | done 29 | echo "Redis is alive" 30 | 31 | 32 | bash kasm_release/install.sh -S app -e -z "${zone_name}" -q "${db_ip}" -Q "${database_password}" -R "${redis_password}" 33 | 34 | echo "Done" 35 | -------------------------------------------------------------------------------- /aws/multi_region/webapps/agent.tf: -------------------------------------------------------------------------------- 1 | resource "aws_instance" "agent" { 2 | count = var.num_agents 3 | 4 | ami = var.ec2_ami 5 | instance_type = var.agent_instance_type 6 | vpc_security_group_ids = [var.agent_security_group_id] 7 | subnet_id = var.agent_subnet_id 8 | key_name = var.aws_key_pair 9 | associate_public_ip_address = true 10 | iam_instance_profile = var.aws_ssm_instance_profile_name 11 | 12 | root_block_device { 13 | volume_size = var.agent_hdd_size_gb 14 | } 15 | 16 | user_data = templatefile("${path.module}/../userdata/agent_bootstrap.sh", 17 | { 18 | kasm_build_url = var.kasm_build 19 | swap_size = var.swap_size 20 | manager_address = var.aws_domain_name 21 | manager_token = var.manager_token 22 | } 23 | ) 24 | 25 | metadata_options { 26 | http_endpoint = "enabled" 27 | http_tokens = "required" 28 | http_put_response_hop_limit = 1 29 | instance_metadata_tags = null 30 | } 31 | 32 | tags = { 33 | Name = "${var.project_name}-${var.zone_name}-kasm-agent-${count.index}" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /aws/multi_region/webapps/cpx.tf: -------------------------------------------------------------------------------- 1 | resource "aws_instance" "cpx" { 2 | count = var.num_cpx_nodes 3 | 4 | ami = var.ec2_ami 5 | instance_type = var.cpx_instance_type 6 | vpc_security_group_ids = [var.cpx_security_group_id] 7 | subnet_id = var.cpx_subnet_id 8 | key_name = var.aws_key_pair 9 | iam_instance_profile = var.aws_ssm_instance_profile_name 10 | 11 | root_block_device { 12 | volume_size = var.cpx_hdd_size_gb 13 | } 14 | 15 | user_data = templatefile("${path.module}/../userdata/cpx_bootstrap.sh", 16 | { 17 | kasm_build_url = var.kasm_build 18 | swap_size = var.swap_size 19 | manager_address = var.aws_domain_name 20 | service_registration_token = var.service_registration_token 21 | } 22 | ) 23 | 24 | metadata_options { 25 | http_endpoint = "enabled" 26 | http_tokens = "required" 27 | http_put_response_hop_limit = 1 28 | instance_metadata_tags = null 29 | } 30 | 31 | tags = { 32 | Name = "${var.project_name}-${var.primary_aws_region}-kasm-cpx-${count.index}" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /aws/multi_region/webapps/dependencies.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | region_short_name_for_lb = join("", slice(split("-", var.faux_aws_region), 1, 3)) 3 | } 4 | 5 | data "aws_route53_zone" "this" { 6 | name = var.aws_domain_name 7 | private_zone = false 8 | } 9 | -------------------------------------------------------------------------------- /aws/multi_region/webapps/elb.tf: -------------------------------------------------------------------------------- 1 | resource "aws_lb" "this" { 2 | name = "${var.project_name}-${var.faux_aws_region}-lb" 3 | internal = false 4 | load_balancer_type = "application" 5 | security_groups = [var.load_balancer_security_group_id] 6 | subnets = var.load_balancer_subnet_ids 7 | 8 | access_logs { 9 | bucket = var.load_balancer_log_bucket 10 | enabled = true 11 | } 12 | } 13 | 14 | resource "aws_lb_listener" "https" { 15 | load_balancer_arn = aws_lb.this.arn 16 | port = "443" 17 | protocol = "HTTPS" 18 | certificate_arn = var.certificate_arn 19 | 20 | default_action { 21 | type = "forward" 22 | target_group_arn = aws_lb_target_group.this.arn 23 | } 24 | } 25 | 26 | resource "aws_lb_listener" "http" { 27 | load_balancer_arn = aws_lb.this.arn 28 | port = "80" 29 | protocol = "HTTP" 30 | 31 | default_action { 32 | type = "redirect" 33 | 34 | redirect { 35 | port = "443" 36 | protocol = "HTTPS" 37 | status_code = "HTTP_301" 38 | } 39 | } 40 | } 41 | 42 | resource "aws_lb_target_group" "this" { 43 | name = "${var.project_name}-${var.faux_aws_region}-tg" 44 | port = 443 45 | protocol = "HTTPS" 46 | vpc_id = var.primary_vpc_id 47 | 48 | health_check { 49 | path = "/api/__healthcheck" 50 | matcher = 200 51 | protocol = "HTTPS" 52 | } 53 | } 54 | resource "aws_lb_target_group_attachment" "this" { 55 | count = var.num_webapps 56 | 57 | target_group_arn = aws_lb_target_group.this.arn 58 | target_id = aws_instance.webapp[count.index].id 59 | port = 443 60 | } 61 | 62 | resource "aws_route53_record" "alb" { 63 | zone_id = data.aws_route53_zone.this.zone_id 64 | name = "${local.region_short_name_for_lb}-lb.${var.aws_domain_name}" 65 | type = "A" 66 | 67 | alias { 68 | name = aws_lb.this.dns_name 69 | zone_id = aws_lb.this.zone_id 70 | evaluate_target_health = true 71 | } 72 | } 73 | 74 | resource "aws_route53_record" "latency" { 75 | zone_id = data.aws_route53_zone.this.zone_id 76 | name = var.aws_domain_name 77 | type = "A" 78 | set_identifier = "${var.project_name}-${local.region_short_name_for_lb}-set-id" 79 | 80 | alias { 81 | name = aws_lb.this.dns_name 82 | zone_id = aws_lb.this.zone_id 83 | evaluate_target_health = true 84 | } 85 | 86 | latency_routing_policy { 87 | region = var.faux_aws_region 88 | } 89 | } 90 | 91 | resource "aws_route53_health_check" "this" { 92 | fqdn = "${local.region_short_name_for_lb}-lb.${var.aws_domain_name}" 93 | port = 443 94 | type = "HTTPS" 95 | resource_path = "/api/__healthcheck" 96 | failure_threshold = "5" 97 | request_interval = "30" 98 | 99 | tags = { 100 | Name = "hc-${local.region_short_name_for_lb}-lb.${var.aws_domain_name}" 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /aws/multi_region/webapps/outputs.tf: -------------------------------------------------------------------------------- 1 | output "kasm_zone_name" { 2 | description = "The zone name used for this region/zone in Kasm" 3 | value = var.aws_to_kasm_zone_map[(var.faux_aws_region)] 4 | } 5 | -------------------------------------------------------------------------------- /aws/multi_region/webapps/provider.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = "~> 1.0" 3 | 4 | required_providers { 5 | aws = { 6 | source = "hashicorp/aws" 7 | version = "~> 5.0" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /aws/multi_region/webapps/webapp.tf: -------------------------------------------------------------------------------- 1 | resource "aws_instance" "webapp" { 2 | count = var.num_webapps 3 | 4 | ami = var.ec2_ami 5 | instance_type = var.webapp_instance_type 6 | vpc_security_group_ids = [var.webapp_security_group_id] 7 | subnet_id = var.webapp_subnet_ids[count.index] 8 | key_name = var.aws_key_pair 9 | iam_instance_profile = var.aws_ssm_instance_profile_name 10 | 11 | root_block_device { 12 | volume_size = var.webapp_hdd_size_gb 13 | } 14 | 15 | user_data = templatefile("${path.module}/../userdata/webapp_bootstrap.sh", 16 | { 17 | kasm_build_url = var.kasm_build 18 | db_ip = var.kasm_db_ip 19 | database_password = var.database_password 20 | redis_password = var.redis_password 21 | swap_size = var.swap_size 22 | zone_name = var.aws_to_kasm_zone_map[(var.faux_aws_region)] 23 | } 24 | ) 25 | 26 | metadata_options { 27 | http_endpoint = "enabled" 28 | http_tokens = "required" 29 | http_put_response_hop_limit = 1 30 | instance_metadata_tags = null 31 | } 32 | 33 | tags = { 34 | Name = "${var.project_name}-${var.zone_name}-kasm-webapp-${count.index}" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /aws/standard/deployment.tf: -------------------------------------------------------------------------------- 1 | module "standard" { 2 | source = "./module" 3 | aws_region = var.aws_region 4 | aws_domain_name = var.aws_domain_name 5 | project_name = var.project_name 6 | num_agents = var.num_agents 7 | num_webapps = var.num_webapps 8 | num_cpx_nodes = var.num_cpx_nodes 9 | vpc_subnet_cidr = var.vpc_subnet_cidr 10 | create_aws_ssm_iam_role = var.create_aws_ssm_iam_role 11 | aws_ssm_iam_role_name = var.aws_ssm_iam_role_name 12 | aws_ssm_instance_profile_name = var.aws_ssm_instance_profile_name 13 | 14 | ## Kasm Server settings 15 | webapp_instance_type = var.webapp_instance_type 16 | webapp_hdd_size_gb = var.webapp_hdd_size_gb 17 | db_instance_type = var.db_instance_type 18 | db_hdd_size_gb = var.db_hdd_size_gb 19 | agent_instance_type = var.agent_instance_type 20 | agent_hdd_size_gb = var.agent_hdd_size_gb 21 | cpx_instance_type = var.cpx_instance_type 22 | cpx_hdd_size_gb = var.cpx_hdd_size_gb 23 | ec2_ami = var.ec2_ami_id 24 | swap_size = var.swap_size 25 | ssh_authorized_keys = var.ssh_authorized_keys 26 | 27 | web_access_cidrs = var.web_access_cidrs 28 | database_password = var.database_password 29 | redis_password = var.redis_password 30 | user_password = var.user_password 31 | admin_password = var.admin_password 32 | manager_token = var.manager_token 33 | service_registration_token = var.service_registration_token 34 | kasm_zone_name = var.kasm_zone_name 35 | kasm_build = var.kasm_build 36 | } 37 | -------------------------------------------------------------------------------- /aws/standard/diagram/aws_multi_server.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kasmtech/terraform/f8618bc382790d4684127b68e3319319d3afefb0/aws/standard/diagram/aws_multi_server.png -------------------------------------------------------------------------------- /aws/standard/module/agent.tf: -------------------------------------------------------------------------------- 1 | resource "aws_instance" "agent" { 2 | count = var.num_agents 3 | 4 | ami = var.ec2_ami 5 | instance_type = var.agent_instance_type 6 | vpc_security_group_ids = [aws_security_group.agent.id] 7 | subnet_id = aws_subnet.agent.id 8 | key_name = aws_key_pair.ssh_keys.key_name 9 | iam_instance_profile = one(aws_iam_instance_profile.this[*].id) 10 | associate_public_ip_address = true 11 | 12 | root_block_device { 13 | volume_size = var.agent_hdd_size_gb 14 | } 15 | 16 | user_data = templatefile("${path.module}/userdata/agent_bootstrap.sh", 17 | { 18 | kasm_build_url = var.kasm_build 19 | swap_size = var.swap_size 20 | manager_address = local.private_lb_hostname 21 | manager_token = var.manager_token 22 | } 23 | ) 24 | 25 | metadata_options { 26 | http_endpoint = "enabled" 27 | http_tokens = "required" 28 | http_put_response_hop_limit = 1 29 | instance_metadata_tags = null 30 | } 31 | 32 | tags = { 33 | Name = "${var.project_name}-${var.kasm_zone_name}-kasm-agent-${count.index}" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /aws/standard/module/cert.tf: -------------------------------------------------------------------------------- 1 | resource "aws_acm_certificate" "this" { 2 | domain_name = var.aws_domain_name 3 | subject_alternative_names = ["*.${var.aws_domain_name}"] 4 | validation_method = "DNS" 5 | 6 | lifecycle { 7 | create_before_destroy = true 8 | } 9 | } 10 | 11 | resource "aws_route53_record" "this" { 12 | for_each = { 13 | for dvo in aws_acm_certificate.this.domain_validation_options : dvo.domain_name => { 14 | name = dvo.resource_record_name 15 | record = dvo.resource_record_value 16 | type = dvo.resource_record_type 17 | } 18 | } 19 | name = each.value.name 20 | type = each.value.type 21 | records = [each.value.record] 22 | zone_id = data.aws_route53_zone.this.id 23 | 24 | ttl = 30 25 | allow_overwrite = true 26 | } 27 | 28 | resource "aws_acm_certificate_validation" "this" { 29 | certificate_arn = aws_acm_certificate.this.arn 30 | validation_record_fqdns = [for record in aws_route53_record.this : record.fqdn] 31 | } 32 | -------------------------------------------------------------------------------- /aws/standard/module/db.tf: -------------------------------------------------------------------------------- 1 | resource "aws_instance" "db" { 2 | ami = var.ec2_ami 3 | instance_type = var.db_instance_type 4 | vpc_security_group_ids = [aws_security_group.db.id] 5 | subnet_id = aws_subnet.db.id 6 | key_name = aws_key_pair.ssh_keys.key_name 7 | iam_instance_profile = one(aws_iam_instance_profile.this[*].id) 8 | 9 | root_block_device { 10 | volume_size = var.db_hdd_size_gb 11 | } 12 | 13 | user_data = templatefile("${path.module}/userdata/db_bootstrap.sh", 14 | { 15 | kasm_build_url = var.kasm_build 16 | user_password = var.user_password 17 | admin_password = var.admin_password 18 | redis_password = var.redis_password 19 | database_password = var.database_password 20 | service_registration_token = var.service_registration_token 21 | manager_token = var.manager_token 22 | swap_size = var.swap_size 23 | } 24 | ) 25 | 26 | metadata_options { 27 | http_endpoint = "enabled" 28 | http_tokens = "required" 29 | http_put_response_hop_limit = 1 30 | instance_metadata_tags = null 31 | } 32 | 33 | tags = { 34 | Name = "${var.project_name}-kasm-db" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /aws/standard/module/dependencies.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | private_lb_hostname = "${var.aws_region}-private.${var.aws_domain_name}" 3 | 4 | } 5 | 6 | data "aws_availability_zones" "available" { 7 | state = "available" 8 | } 9 | 10 | data "aws_route53_zone" "this" { 11 | name = var.aws_domain_name 12 | } 13 | 14 | data "aws_elb_service_account" "main" {} 15 | -------------------------------------------------------------------------------- /aws/standard/module/elb_logs_s3_bucket.tf: -------------------------------------------------------------------------------- 1 | resource "aws_s3_bucket" "this" { 2 | bucket_prefix = "${var.project_name}-${var.kasm_zone_name}-" 3 | force_destroy = true 4 | } 5 | 6 | resource "aws_s3_bucket_policy" "this" { 7 | bucket = aws_s3_bucket.this.id 8 | 9 | policy = jsonencode({ 10 | Id = "Policy" 11 | Version = "2012-10-17" 12 | Statement = [ 13 | { 14 | Action = [ 15 | "s3:PutObject" 16 | ] 17 | Effect = "Allow" 18 | Resource = "${aws_s3_bucket.this.arn}/AWSLogs/*" 19 | Principal = { 20 | AWS = [ 21 | data.aws_elb_service_account.main.arn 22 | ] 23 | } 24 | } 25 | ] 26 | }) 27 | } 28 | 29 | resource "aws_s3_bucket_server_side_encryption_configuration" "this" { 30 | bucket = aws_s3_bucket.this.id 31 | 32 | rule { 33 | apply_server_side_encryption_by_default { 34 | sse_algorithm = "AES256" 35 | } 36 | } 37 | } 38 | 39 | resource "aws_s3_bucket_public_access_block" "this" { 40 | bucket = aws_s3_bucket.this.id 41 | block_public_acls = true 42 | block_public_policy = true 43 | ignore_public_acls = true 44 | restrict_public_buckets = true 45 | } 46 | -------------------------------------------------------------------------------- /aws/standard/module/guac_rdp.tf: -------------------------------------------------------------------------------- 1 | resource "aws_instance" "cpx" { 2 | count = var.num_cpx_nodes 3 | 4 | ami = var.ec2_ami 5 | instance_type = var.cpx_instance_type 6 | vpc_security_group_ids = aws_security_group.cpx[*].id 7 | subnet_id = one(aws_subnet.cpx[*].id) 8 | key_name = aws_key_pair.ssh_keys.key_name 9 | iam_instance_profile = one(aws_iam_instance_profile.this[*].id) 10 | 11 | root_block_device { 12 | volume_size = var.cpx_hdd_size_gb 13 | } 14 | 15 | user_data = templatefile("${path.module}/userdata/cpx_bootstrap.sh", 16 | { 17 | kasm_build_url = var.kasm_build 18 | swap_size = var.swap_size 19 | manager_address = local.private_lb_hostname 20 | service_registration_token = var.service_registration_token 21 | } 22 | ) 23 | 24 | metadata_options { 25 | http_endpoint = "enabled" 26 | http_tokens = "required" 27 | http_put_response_hop_limit = 1 28 | instance_metadata_tags = null 29 | } 30 | 31 | tags = { 32 | Name = "${var.project_name}-${var.kasm_zone_name}-kasm-cpx-${count.index}" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /aws/standard/module/kms.tf: -------------------------------------------------------------------------------- 1 | resource "aws_key_pair" "ssh_keys" { 2 | key_name = "${var.project_name}-ssh-key" 3 | public_key = var.ssh_authorized_keys == "" ? tls_private_key.ssh_key[0].public_key_openssh : var.ssh_authorized_keys 4 | } -------------------------------------------------------------------------------- /aws/standard/module/private_alb.tf: -------------------------------------------------------------------------------- 1 | resource "aws_lb" "private" { 2 | name = "${var.project_name}-private-lb" 3 | internal = true 4 | load_balancer_type = "application" 5 | security_groups = [aws_security_group.private_lb.id] 6 | subnets = aws_subnet.webapp[*].id 7 | 8 | access_logs { 9 | bucket = aws_s3_bucket.this.bucket 10 | enabled = true 11 | } 12 | 13 | tags = { 14 | Name = "${var.project_name}-kasm-private-lb" 15 | } 16 | } 17 | 18 | resource "aws_lb_target_group" "private" { 19 | name = "${var.project_name}-private-target-group" 20 | port = 443 21 | protocol = "HTTPS" 22 | vpc_id = aws_vpc.this.id 23 | 24 | health_check { 25 | path = "/api/__healthcheck" 26 | matcher = 200 27 | protocol = "HTTPS" 28 | } 29 | 30 | tags = { 31 | Name = "${var.project_name}-kasm-private-tg" 32 | } 33 | } 34 | 35 | resource "aws_lb_listener" "private" { 36 | load_balancer_arn = aws_lb.private.arn 37 | port = "443" 38 | protocol = "HTTPS" 39 | certificate_arn = aws_acm_certificate_validation.this.certificate_arn 40 | 41 | default_action { 42 | type = "forward" 43 | target_group_arn = aws_lb_target_group.private.arn 44 | } 45 | 46 | tags = { 47 | Name = "${var.project_name}-kasm-private-https-listener" 48 | } 49 | } 50 | 51 | resource "aws_lb_target_group_attachment" "private" { 52 | count = var.num_webapps 53 | 54 | target_group_arn = aws_lb_target_group.private.arn 55 | target_id = aws_instance.webapp[count.index].id 56 | port = 443 57 | } 58 | 59 | resource "aws_route53_record" "private" { 60 | zone_id = data.aws_route53_zone.this.zone_id 61 | name = local.private_lb_hostname 62 | type = "A" 63 | 64 | alias { 65 | name = aws_lb.private.dns_name 66 | zone_id = aws_lb.private.zone_id 67 | evaluate_target_health = true 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /aws/standard/module/provider.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = "~> 1.0" 3 | 4 | required_providers { 5 | aws = { 6 | source = "hashicorp/aws" 7 | version = "~> 5.0" 8 | } 9 | tls = { 10 | source = "hashicorp/tls" 11 | version = "~> 4.0" 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /aws/standard/module/public_alb.tf: -------------------------------------------------------------------------------- 1 | resource "aws_lb" "public" { 2 | name = "${var.project_name}-lb" 3 | internal = false 4 | load_balancer_type = "application" 5 | security_groups = [aws_security_group.public_lb.id] 6 | subnets = aws_subnet.alb[*].id 7 | 8 | access_logs { 9 | bucket = aws_s3_bucket.this.bucket 10 | enabled = true 11 | } 12 | 13 | tags = { 14 | Name = "${var.project_name}-kasm-public-lb" 15 | } 16 | } 17 | 18 | resource "aws_lb_listener" "https" { 19 | load_balancer_arn = aws_lb.public.arn 20 | port = "443" 21 | protocol = "HTTPS" 22 | certificate_arn = aws_acm_certificate_validation.this.certificate_arn 23 | 24 | default_action { 25 | type = "forward" 26 | target_group_arn = aws_lb_target_group.public.arn 27 | } 28 | 29 | tags = { 30 | Name = "${var.project_name}-kasm-public-https-listener" 31 | } 32 | } 33 | 34 | resource "aws_lb_listener" "http" { 35 | load_balancer_arn = aws_lb.public.arn 36 | port = "80" 37 | protocol = "HTTP" 38 | 39 | default_action { 40 | type = "redirect" 41 | 42 | redirect { 43 | port = "443" 44 | protocol = "HTTPS" 45 | status_code = "HTTP_301" 46 | } 47 | } 48 | 49 | tags = { 50 | Name = "${var.project_name}-kasm-public-http-listener" 51 | } 52 | } 53 | 54 | resource "aws_lb_target_group" "public" { 55 | name = "${var.project_name}-target-group" 56 | port = 443 57 | protocol = "HTTPS" 58 | vpc_id = aws_vpc.this.id 59 | 60 | health_check { 61 | path = "/api/__healthcheck" 62 | matcher = 200 63 | protocol = "HTTPS" 64 | } 65 | 66 | tags = { 67 | Name = "${var.project_name}-kasm-public-tg" 68 | } 69 | } 70 | 71 | resource "aws_lb_target_group_attachment" "public" { 72 | count = var.num_webapps 73 | 74 | target_group_arn = aws_lb_target_group.public.arn 75 | target_id = aws_instance.webapp[count.index].id 76 | port = 443 77 | } 78 | 79 | resource "aws_route53_record" "public" { 80 | zone_id = data.aws_route53_zone.this.zone_id 81 | name = var.aws_domain_name 82 | type = "A" 83 | 84 | alias { 85 | name = aws_lb.public.dns_name 86 | zone_id = aws_lb.public.zone_id 87 | evaluate_target_health = true 88 | } 89 | } 90 | 91 | resource "aws_route53_health_check" "kasm-elb-hc" { 92 | fqdn = var.aws_domain_name 93 | port = 443 94 | type = "HTTPS" 95 | resource_path = "/api/__healthcheck" 96 | failure_threshold = "5" 97 | request_interval = "30" 98 | 99 | tags = { 100 | Name = "hc-${var.aws_domain_name}" 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /aws/standard/module/routes.tf: -------------------------------------------------------------------------------- 1 | resource "aws_route_table" "ig" { 2 | vpc_id = aws_vpc.this.id 3 | 4 | route { 5 | cidr_block = var.anywhere 6 | gateway_id = aws_internet_gateway.this.id 7 | } 8 | 9 | tags = { 10 | Name = "${var.project_name}-kasm-default-route" 11 | } 12 | } 13 | 14 | resource "aws_route_table" "nat" { 15 | vpc_id = aws_vpc.this.id 16 | 17 | route { 18 | cidr_block = var.anywhere 19 | nat_gateway_id = aws_nat_gateway.this.id 20 | } 21 | 22 | tags = { 23 | Name = "${var.project_name}-kasm-natgw-route" 24 | } 25 | } 26 | 27 | resource "aws_route_table_association" "alb" { 28 | count = 2 29 | 30 | subnet_id = aws_subnet.alb[count.index].id 31 | route_table_id = aws_route_table.ig.id 32 | } 33 | 34 | resource "aws_route_table_association" "webapp" { 35 | count = var.num_webapps 36 | 37 | subnet_id = aws_subnet.webapp[count.index].id 38 | route_table_id = aws_route_table.nat.id 39 | } 40 | 41 | resource "aws_route_table_association" "db" { 42 | subnet_id = aws_subnet.db.id 43 | route_table_id = aws_route_table.nat.id 44 | } 45 | 46 | resource "aws_route_table_association" "cpx" { 47 | count = var.num_cpx_nodes > 0 ? 1 : 0 48 | 49 | subnet_id = one(aws_subnet.cpx[*].id) 50 | route_table_id = aws_route_table.nat.id 51 | } 52 | 53 | resource "aws_route_table_association" "agent" { 54 | subnet_id = aws_subnet.agent.id 55 | route_table_id = aws_route_table.ig.id 56 | } 57 | 58 | resource "aws_route_table_association" "windows" { 59 | count = var.num_cpx_nodes > 0 ? 1 : 0 60 | 61 | subnet_id = one(aws_subnet.windows[*].id) 62 | route_table_id = aws_route_table.ig.id 63 | } 64 | -------------------------------------------------------------------------------- /aws/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 | } -------------------------------------------------------------------------------- /aws/standard/module/ssm.tf: -------------------------------------------------------------------------------- 1 | data "aws_iam_policy_document" "this" { 2 | statement { 3 | effect = "Allow" 4 | 5 | principals { 6 | type = "Service" 7 | identifiers = [ 8 | "ec2.amazonaws.com" 9 | ] 10 | } 11 | 12 | actions = ["sts:AssumeRole"] 13 | } 14 | } 15 | 16 | resource "aws_iam_role" "this" { 17 | count = var.create_aws_ssm_iam_role ? 1 : 0 18 | 19 | name = var.aws_ssm_iam_role_name != "" ? var.aws_ssm_iam_role_name : "Kasm_SSM_IAM_Instance_Role" 20 | assume_role_policy = data.aws_iam_policy_document.this.json 21 | } 22 | 23 | resource "aws_iam_role_policy_attachment" "this" { 24 | count = var.create_aws_ssm_iam_role ? 1 : 0 25 | 26 | policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore" 27 | role = one(aws_iam_role.this[*].name) 28 | } 29 | 30 | resource "aws_iam_instance_profile" "this" { 31 | count = var.create_aws_ssm_iam_role ? 1 : 0 32 | 33 | name = var.aws_ssm_instance_profile_name != "" ? var.aws_ssm_instance_profile_name : "Kasm_SSM_Instance_Profile" 34 | role = one(aws_iam_role.this[*].name) 35 | } 36 | -------------------------------------------------------------------------------- /aws/standard/module/subnet.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | kasm_vpc_subnet_cidr_mask = split("/", var.vpc_subnet_cidr)[1] 3 | kasm_server_subnet_cidr_calculation = (8 - (local.kasm_vpc_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 | 7 | ## Will create Agent subnet x.x.0.0/24 and x.x.1.0/24 (assuming a VPC Subnet CIDR between x.x.0.0/16 and x.x.0.0/21) 8 | resource "aws_subnet" "alb" { 9 | count = 2 10 | 11 | vpc_id = aws_vpc.this.id 12 | cidr_block = cidrsubnet(var.vpc_subnet_cidr, local.kasm_server_subnet_cidr_size, count.index) 13 | availability_zone = data.aws_availability_zones.available.names[count.index] 14 | map_public_ip_on_launch = true 15 | 16 | tags = { 17 | Name = "${var.project_name}-kasm-lb-subnet-${count.index}" 18 | } 19 | } 20 | 21 | ## Will create WebApp subnets x.x.2.0/24 and x.x.3.0/24 (assuming a VPC Subnet CIDR between x.x.0.0/16 and x.x.0.0/21) 22 | resource "aws_subnet" "webapp" { 23 | count = var.num_webapps 24 | 25 | vpc_id = aws_vpc.this.id 26 | cidr_block = cidrsubnet(var.vpc_subnet_cidr, local.kasm_server_subnet_cidr_size, (count.index + 2)) 27 | availability_zone = data.aws_availability_zones.available.names[count.index] 28 | 29 | tags = { 30 | Name = "${var.project_name}-kasm-webapp-subnet-${count.index}" 31 | } 32 | } 33 | 34 | ## Will create Agent subnet x.x.4.0/24 (assuming a VPC Subnet CIDR between x.x.0.0/16 and x.x.0.0/21) 35 | resource "aws_subnet" "db" { 36 | vpc_id = aws_vpc.this.id 37 | cidr_block = cidrsubnet(var.vpc_subnet_cidr, local.kasm_server_subnet_cidr_size, 4) 38 | availability_zone = data.aws_availability_zones.available.names[1] 39 | 40 | tags = { 41 | Name = "${var.project_name}-kasm-db-subnet" 42 | } 43 | } 44 | 45 | ## Will create Agent subnet x.x.6.0/24 (assuming a VPC Subnet CIDR between x.x.0.0/16 and x.x.0.0/21) 46 | resource "aws_subnet" "agent" { 47 | vpc_id = aws_vpc.this.id 48 | cidr_block = cidrsubnet(var.vpc_subnet_cidr, local.kasm_server_subnet_cidr_size, 5) 49 | map_public_ip_on_launch = true 50 | availability_zone = data.aws_availability_zones.available.names[1] 51 | 52 | tags = { 53 | Name = "${var.project_name}-agent-subnet" 54 | } 55 | } 56 | 57 | ## Will create CPX subnet x.x.5.0/24 (assuming a VPC Subnet CIDR between x.x.0.0/16 and x.x.0.0/21) 58 | resource "aws_subnet" "cpx" { 59 | count = var.num_cpx_nodes > 0 ? 1 : 0 60 | 61 | vpc_id = aws_vpc.this.id 62 | cidr_block = cidrsubnet(var.vpc_subnet_cidr, local.kasm_server_subnet_cidr_size, 6) 63 | availability_zone = data.aws_availability_zones.available.names[0] 64 | 65 | tags = { 66 | Name = "${var.project_name}-cpx-subnet" 67 | } 68 | } 69 | 70 | ## Will create cpx subnet x.x.7.0/24 (assuming a VPC Subnet CIDR between x.x.0.0/16 and x.x.0.0/21) 71 | resource "aws_subnet" "windows" { 72 | count = var.num_cpx_nodes > 0 ? 1 : 0 73 | 74 | vpc_id = aws_vpc.this.id 75 | cidr_block = cidrsubnet(var.vpc_subnet_cidr, local.kasm_server_subnet_cidr_size, 7) 76 | map_public_ip_on_launch = true 77 | availability_zone = data.aws_availability_zones.available.names[1] 78 | 79 | tags = { 80 | Name = "${var.project_name}-windows-subnet" 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /aws/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 -H -p $PRIVATE_IP -m ${manager_address} -M ${manager_token} 28 | 29 | echo "Done" 30 | -------------------------------------------------------------------------------- /aws/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 -H -p $PRIVATE_IP -n ${manager_address} -k ${service_registration_token} 28 | 29 | echo "Done" 30 | -------------------------------------------------------------------------------- /aws/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 | wget ${kasm_build_url} -O kasm_workspaces.tar.gz 15 | tar -xf kasm_workspaces.tar.gz 16 | bash kasm_release/install.sh -S db -e -H -Q ${database_password} -R ${redis_password} -U ${user_password} -P ${admin_password} -M ${manager_token} -k ${service_registration_token} 17 | 18 | echo "Done" 19 | -------------------------------------------------------------------------------- /aws/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 | wget ${kasm_build_url} -O kasm_workspaces.tar.gz 15 | tar -xf kasm_workspaces.tar.gz 16 | 17 | echo "Checking for Kasm DB and Redis..." 18 | apt-get update && apt-get install -y netcat-openbsd 19 | while ! nc -w 1 -z ${db_ip} 5432; do 20 | echo "Database not ready..." 21 | sleep 5 22 | done 23 | echo "DB is alive" 24 | 25 | while ! nc -w 1 -z ${db_ip} 6379; do 26 | echo "Redis not ready..." 27 | sleep 5 28 | done 29 | echo "Redis is alive" 30 | 31 | 32 | bash kasm_release/install.sh -S app -e -H -z ${zone_name} -q "${db_ip}" -Q ${database_password} -R ${redis_password} 33 | 34 | echo "Done" 35 | -------------------------------------------------------------------------------- /aws/standard/module/vpc.tf: -------------------------------------------------------------------------------- 1 | resource "aws_vpc" "this" { 2 | cidr_block = var.vpc_subnet_cidr 3 | enable_dns_hostnames = true 4 | enable_dns_support = true 5 | tags = { 6 | Name = "${var.project_name}-kasm-vpc" 7 | } 8 | } 9 | 10 | resource "aws_internet_gateway" "this" { 11 | vpc_id = aws_vpc.this.id 12 | 13 | tags = { 14 | Name = "${var.project_name}-kasm-ig" 15 | } 16 | } 17 | 18 | resource "aws_eip" "this" { 19 | domain = "vpc" 20 | 21 | tags = { 22 | Name = "${var.project_name}-kasm-nat-gateway-eip" 23 | } 24 | 25 | } 26 | 27 | resource "aws_nat_gateway" "this" { 28 | allocation_id = aws_eip.this.id 29 | subnet_id = aws_subnet.alb[0].id 30 | 31 | tags = { 32 | Name = "${var.project_name}-kasm-nat-gateway" 33 | } 34 | 35 | depends_on = [aws_internet_gateway.this] 36 | } 37 | -------------------------------------------------------------------------------- /aws/standard/module/webapp.tf: -------------------------------------------------------------------------------- 1 | resource "aws_instance" "webapp" { 2 | count = var.num_webapps 3 | 4 | ami = var.ec2_ami 5 | instance_type = var.webapp_instance_type 6 | vpc_security_group_ids = [aws_security_group.webapp.id] 7 | subnet_id = aws_subnet.webapp[count.index].id 8 | key_name = aws_key_pair.ssh_keys.key_name 9 | iam_instance_profile = one(aws_iam_instance_profile.this[*].id) 10 | 11 | root_block_device { 12 | volume_size = var.webapp_hdd_size_gb 13 | } 14 | 15 | user_data = templatefile("${path.module}/userdata/webapp_bootstrap.sh", 16 | { 17 | kasm_build_url = var.kasm_build 18 | db_ip = aws_instance.db.private_ip 19 | database_password = var.database_password 20 | redis_password = var.redis_password 21 | swap_size = var.swap_size 22 | zone_name = "default" 23 | } 24 | ) 25 | 26 | metadata_options { 27 | http_endpoint = "enabled" 28 | http_tokens = "required" 29 | http_put_response_hop_limit = 1 30 | instance_metadata_tags = null 31 | } 32 | 33 | tags = { 34 | Name = "${var.project_name}-${var.kasm_zone_name}-kasm-webapp-${count.index}" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /aws/standard/output.tf: -------------------------------------------------------------------------------- 1 | output "kasm_zone_settings" { 2 | description = "Upstream Auth settings to apply to Kasm Zone configuration" 3 | value = < 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 | --------------------------------------------------------------------------------