├── .gitignore ├── data.tf ├── cw └── alb │ ├── variables.tf │ └── main.tf ├── cloud-init.cfg ├── cloud-init.tf ├── cw.tf ├── redis.tf ├── iam.tf ├── ssm.tf ├── aurora.tf ├── ec2.tf ├── README.md ├── lb.tf ├── sg.tf ├── variables.tf ├── LICENSE └── cloud-init.sh /.gitignore: -------------------------------------------------------------------------------- 1 | # Local .terraform directories 2 | **/.terraform/* 3 | 4 | # .tfstate files 5 | *.tfstate 6 | *.tfstate.* 7 | 8 | # .tfvars files 9 | *.tfvars 10 | -------------------------------------------------------------------------------- /data.tf: -------------------------------------------------------------------------------- 1 | # AWS Data 2 | data "aws_vpc" "vpc" { 3 | state = "available" 4 | 5 | tags { 6 | Name = "${var.vpc_name}" 7 | } 8 | } 9 | 10 | data "aws_region" "current" {} 11 | 12 | data "aws_subnet_ids" "public" { 13 | vpc_id = "${data.aws_vpc.vpc.id}" 14 | 15 | tags { 16 | Type = "${var.public_subnets}" 17 | } 18 | } 19 | 20 | data "aws_subnet_ids" "private" { 21 | vpc_id = "${data.aws_vpc.vpc.id}" 22 | 23 | tags { 24 | Type = "${var.private_subnets}" 25 | } 26 | } 27 | 28 | data "aws_security_group" "default" { 29 | vpc_id = "${data.aws_vpc.vpc.id}" 30 | 31 | tags { 32 | Name = "${var.default_security_group}" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /cw/alb/variables.tf: -------------------------------------------------------------------------------- 1 | variable "load_balancer" { 2 | description = "Load balancer ARN suffix" 3 | type = "string" 4 | } 5 | 6 | variable "target_group" { 7 | description = "Target group ARN" 8 | type = "string" 9 | } 10 | 11 | variable "cloudwatch_actions" { 12 | description = "List of cloudwatch actions for Alert/Ok" 13 | type = "list" 14 | } 15 | 16 | variable "enable" { 17 | description = "Boolean to enable cloudwatch metrics" 18 | type = "string" 19 | 20 | default = true 21 | } 22 | 23 | # Metric threshholds 24 | variable "http_4xx_count" { 25 | description = "HTTP Code 4xx count threshhold" 26 | type = "string" 27 | } 28 | 29 | variable "http_5xx_count" { 30 | description = "HTTP Code 5xx count threshhold" 31 | type = "string" 32 | } 33 | -------------------------------------------------------------------------------- /cloud-init.cfg: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | 3 | # Create kong system user and group 4 | groups: 5 | - kong 6 | 7 | users: 8 | - default 9 | - name: kong 10 | lock_passwd: true 11 | primary-group: kong 12 | homedir: /usr/local/kong 13 | no-create-home: true 14 | shell: /bin/bash 15 | system: true 16 | 17 | write_files: 18 | - path: /etc/apt/apt.conf.d/00InstallRecommends 19 | owner: root:root 20 | permissions: '0644' 21 | content: | 22 | APT::Install-Recommends "false"; 23 | 24 | # Package configuration 25 | apt: 26 | primary: 27 | - arches: [default] 28 | 29 | apt_update: true 30 | package_upgrade: true 31 | packages: 32 | - apt-listchanges 33 | - unattended-upgrades 34 | - ntp 35 | - runit 36 | - runit-systemd 37 | - dnsutils 38 | - curl 39 | - telnet 40 | - pwgen 41 | - postgresql-client 42 | - perl 43 | - libpcre3 44 | - awscli 45 | - npm 46 | -------------------------------------------------------------------------------- /cloud-init.tf: -------------------------------------------------------------------------------- 1 | data "template_file" "cloud-init" { 2 | template = "${file("${path.module}/cloud-init.cfg")}" 3 | } 4 | 5 | data "template_file" "shell-script" { 6 | template = "${file("${path.module}/cloud-init.sh")}" 7 | 8 | vars { 9 | DB_USER = "${replace(format("%s_%s", var.service, var.environment), "-", "_")}" 10 | CE_PKG = "${var.ce_pkg}" 11 | EE_PKG = "${var.ee_pkg}" 12 | PARAMETER_PATH = "/${var.service}/${var.environment}" 13 | REGION = "${data.aws_region.current.name}" 14 | } 15 | } 16 | 17 | data "template_cloudinit_config" "cloud-init" { 18 | gzip = true 19 | base64_encode = true 20 | 21 | part { 22 | filename = "init.cfg" 23 | content_type = "text/cloud-config" 24 | content = "${data.template_file.cloud-init.rendered}" 25 | } 26 | 27 | part { 28 | content_type = "text/x-shellscript" 29 | content = "${data.template_file.shell-script.rendered}" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /cw.tf: -------------------------------------------------------------------------------- 1 | module "kong-external-alb-cw" { 2 | source = "github.com/zillowgroup/kong-terraform/cw/alb" 3 | 4 | enable = "${var.enable_external_lb}" 5 | load_balancer = "${coalesce(join("", aws_alb.external.*.arn_suffix), "none")}" 6 | target_group = "${coalesce(join("", aws_alb_target_group.external.*.arn), "none")}" 7 | 8 | cloudwatch_actions = "${var.cloudwatch_actions}" 9 | http_4xx_count = "${var.http_4xx_count}" 10 | http_5xx_count = "${var.http_5xx_count}" 11 | } 12 | 13 | module "kong-internal-alb-cw" { 14 | source = "github.com/zillowgroup/kong-terraform/cw/alb" 15 | 16 | enable = "${var.enable_external_lb}" 17 | load_balancer = "${coalesce(join("", aws_alb.internal.*.arn_suffix), "none")}" 18 | target_group = "${coalesce(join("", aws_alb_target_group.internal.*.arn), "none")}" 19 | 20 | cloudwatch_actions = "${var.cloudwatch_actions}" 21 | http_4xx_count = "${var.http_4xx_count}" 22 | http_5xx_count = "${var.http_5xx_count}" 23 | } 24 | -------------------------------------------------------------------------------- /redis.tf: -------------------------------------------------------------------------------- 1 | resource "aws_elasticache_replication_group" "kong" { 2 | count = "${var.enable_redis}" 3 | 4 | replication_group_id = "${var.service}-${var.environment}" 5 | replication_group_description = "${var.description}" 6 | 7 | engine = "redis" 8 | engine_version = "4.0.10" 9 | node_type = "${var.redis_instance_type}" 10 | number_cache_clusters = "${var.redis_instance_count}" 11 | parameter_group_name = "${var.service}-${var.environment}" 12 | port = 6379 13 | 14 | subnet_group_name = "${var.redis_subnets}" 15 | security_group_ids = ["${aws_security_group.redis.id}"] 16 | 17 | tags = "${merge( 18 | map("Name", format("%s-%s", var.service, var.environment)), 19 | map("Environment", var.environment), 20 | map("Description", var.description), 21 | map("Service", var.service), 22 | var.tags 23 | )}" 24 | } 25 | 26 | resource "aws_elasticache_parameter_group" "kong" { 27 | name = "${var.service}-${var.environment}" 28 | family = "redis4.0" 29 | 30 | description = "${var.description}" 31 | } 32 | -------------------------------------------------------------------------------- /iam.tf: -------------------------------------------------------------------------------- 1 | data "aws_iam_policy_document" "kong-ssm" { 2 | statement { 3 | actions = ["ssm:DescribeParameters"] 4 | resources = ["*"] 5 | } 6 | 7 | statement { 8 | actions = ["ssm:GetParameter"] 9 | resources = ["arn:aws:ssm:*:*:parameter/${var.service}/${var.environment}/*"] 10 | } 11 | 12 | statement { 13 | actions = ["kms:Decrypt"] 14 | resources = ["${aws_kms_alias.kong.target_key_arn}"] 15 | } 16 | } 17 | 18 | resource "aws_iam_role_policy" "kong-ssm" { 19 | name = "${var.service}-${var.environment}-ssm" 20 | role = "${aws_iam_role.kong.id}" 21 | 22 | policy = "${data.aws_iam_policy_document.kong-ssm.json}" 23 | } 24 | 25 | data "aws_iam_policy_document" "kong" { 26 | statement { 27 | actions = ["sts:AssumeRole"] 28 | 29 | principals = { 30 | type = "Service" 31 | 32 | identifiers = ["ec2.amazonaws.com"] 33 | } 34 | } 35 | } 36 | 37 | resource "aws_iam_role" "kong" { 38 | name = "${var.service}-${var.environment}" 39 | assume_role_policy = "${data.aws_iam_policy_document.kong.json}" 40 | } 41 | 42 | resource "aws_iam_instance_profile" "kong" { 43 | name = "${var.service}-${var.environment}" 44 | role = "${aws_iam_role.kong.id}" 45 | } 46 | -------------------------------------------------------------------------------- /ssm.tf: -------------------------------------------------------------------------------- 1 | resource "aws_kms_key" "kong" { 2 | description = "${var.service}/${var.environment}" 3 | 4 | tags = "${merge( 5 | map("Name", format("%s-%s", var.service, var.environment)), 6 | map("Environment", var.environment), 7 | map("Description", var.description), 8 | map("Service", var.service), 9 | var.tags 10 | )}" 11 | } 12 | 13 | resource "aws_kms_alias" "kong" { 14 | name = "alias/${var.service}-${var.environment}" 15 | target_key_id = "${aws_kms_key.kong.key_id}" 16 | } 17 | 18 | resource "aws_ssm_parameter" "ee-bintray-auth" { 19 | name = "/${var.service}/${var.environment}/ee/bintray-auth" 20 | type = "SecureString" 21 | value = "placeholder" 22 | 23 | key_id = "${aws_kms_alias.kong.target_key_arn}" 24 | 25 | lifecycle { 26 | ignore_changes = ["value"] 27 | } 28 | } 29 | 30 | resource "aws_ssm_parameter" "ee-license" { 31 | name = "/${var.service}/${var.environment}/ee/license" 32 | type = "SecureString" 33 | value = "placeholder" 34 | 35 | key_id = "${aws_kms_alias.kong.target_key_arn}" 36 | 37 | lifecycle { 38 | ignore_changes = ["value"] 39 | } 40 | } 41 | 42 | resource "aws_ssm_parameter" "ee-admin-token" { 43 | name = "/${var.service}/${var.environment}/ee/admin/token" 44 | type = "SecureString" 45 | value = "zg-kong-2-1" 46 | 47 | key_id = "${aws_kms_alias.kong.target_key_arn}" 48 | 49 | lifecycle { 50 | ignore_changes = ["value"] 51 | } 52 | } 53 | 54 | resource "aws_ssm_parameter" "db-host" { 55 | name = "/${var.service}/${var.environment}/db/host" 56 | type = "String" 57 | value = "${coalesce(join("", aws_rds_cluster.kong.*.endpoint), var.db_host)}" 58 | } 59 | 60 | resource "aws_ssm_parameter" "db-name" { 61 | name = "/${var.service}/${var.environment}/db/name" 62 | type = "String" 63 | value = "${replace(format("%s_%s", var.service, var.environment), "-", "_")}" 64 | } 65 | 66 | resource "aws_ssm_parameter" "db-password" { 67 | name = "/${var.service}/${var.environment}/db/password" 68 | type = "SecureString" 69 | value = "placeholder" 70 | 71 | key_id = "${aws_kms_alias.kong.target_key_arn}" 72 | 73 | lifecycle { 74 | ignore_changes = ["value"] 75 | } 76 | 77 | overwrite = true 78 | } 79 | 80 | resource "aws_ssm_parameter" "db-master-password" { 81 | name = "/${var.service}/${var.environment}/db/password/master" 82 | type = "SecureString" 83 | value = "${var.db_password}" 84 | 85 | key_id = "${aws_kms_alias.kong.target_key_arn}" 86 | 87 | lifecycle { 88 | ignore_changes = ["value"] 89 | } 90 | 91 | overwrite = true 92 | } 93 | -------------------------------------------------------------------------------- /aurora.tf: -------------------------------------------------------------------------------- 1 | resource "aws_rds_cluster" "kong" { 2 | count = "${var.db_instance_count > 0 ? 1 : 0}" 3 | cluster_identifier = "${var.service}-${var.environment}" 4 | engine = "aurora-postgresql" 5 | master_username = "${var.db_username}" 6 | master_password = "${var.db_password}" 7 | 8 | backup_retention_period = "${var.db_backup_retention_period}" 9 | db_subnet_group_name = "${var.db_subnets}" 10 | db_cluster_parameter_group_name = "${var.service}-${var.environment}-cluster" 11 | 12 | vpc_security_group_ids = [ 13 | "${aws_security_group.postgresql.id}", 14 | ] 15 | 16 | tags = "${merge( 17 | map("Name", format("%s-%s", var.service, var.environment)), 18 | map("Environment", var.environment), 19 | map("Description", var.description), 20 | map("Service", var.service), 21 | var.tags 22 | )}" 23 | } 24 | 25 | resource "aws_rds_cluster_instance" "kong" { 26 | count = "${var.db_instance_count}" 27 | identifier = "${var.service}-${var.environment}-${count.index}" 28 | cluster_identifier = "${aws_rds_cluster.kong.id}" 29 | engine = "aurora-postgresql" 30 | instance_class = "${var.db_instance_class}" 31 | 32 | db_subnet_group_name = "${var.db_subnets}" 33 | db_parameter_group_name = "${var.service}-${var.environment}-instance" 34 | 35 | tags = "${merge( 36 | map("Name", format("%s-%s", var.service, var.environment)), 37 | map("Environment", var.environment), 38 | map("Description", var.description), 39 | map("Service", var.service), 40 | var.tags 41 | )}" 42 | } 43 | 44 | resource "aws_rds_cluster_parameter_group" "kong" { 45 | count = "${var.db_instance_count > 0 ? 1 : 0}" 46 | name = "${var.service}-${var.environment}-cluster" 47 | family = "aurora-postgresql9.6" 48 | 49 | description = "${var.description}" 50 | 51 | tags = "${merge( 52 | map("Name", format("%s-%s-cluster", var.service, var.environment)), 53 | map("Environment", var.environment), 54 | map("Description", var.description), 55 | map("Service", var.service), 56 | var.tags 57 | )}" 58 | } 59 | 60 | resource "aws_db_parameter_group" "kong" { 61 | count = "${var.db_instance_count > 0 ? 1 : 0}" 62 | name = "${var.service}-${var.environment}-instance" 63 | family = "aurora-postgresql9.6" 64 | 65 | description = "${var.description}" 66 | 67 | tags = "${merge( 68 | map("Name", format("%s-%s-instance", var.service, var.environment)), 69 | map("Environment", var.environment), 70 | map("Description", var.description), 71 | map("Service", var.service), 72 | var.tags 73 | )}" 74 | } 75 | -------------------------------------------------------------------------------- /cw/alb/main.tf: -------------------------------------------------------------------------------- 1 | resource "aws_cloudwatch_metric_alarm" "unhealthy-host-count" { 2 | count = "${var.enable}" 3 | 4 | alarm_name = "${element(split("/", var.load_balancer), 1)}-unhealthy-host-count" 5 | comparison_operator = "GreaterThanThreshold" 6 | evaluation_periods = 1 7 | metric_name = "UnHealthyHostCount" 8 | namespace = "AWS/ApplicationELB" 9 | period = 60 10 | statistic = "Minimum" 11 | threshold = 0 12 | 13 | actions_enabled = "true" 14 | alarm_actions = "${var.cloudwatch_actions}" 15 | alarm_description = "Unhealthy host count is greater than 0 for ${element(split("/", var.load_balancer), 1)}" 16 | ok_actions = "${var.cloudwatch_actions}" 17 | 18 | dimensions = { 19 | "TargetGroup" = "${element(split(":", var.target_group), 5)}" 20 | "LoadBalancer" = "${var.load_balancer}" 21 | } 22 | } 23 | 24 | resource "aws_cloudwatch_metric_alarm" "http-code-4xx-count" { 25 | count = "${var.enable}" 26 | 27 | alarm_name = "${element(split("/", var.load_balancer), 1)}-http-code-4xx-count" 28 | comparison_operator = "GreaterThanThreshold" 29 | evaluation_periods = 1 30 | metric_name = "HTTPCode_Target_4XX_Count" 31 | namespace = "AWS/ApplicationELB" 32 | period = 60 33 | statistic = "Minimum" 34 | threshold = "${var.http_4xx_count}" 35 | treat_missing_data = "ignore" 36 | 37 | actions_enabled = "true" 38 | alarm_actions = "${var.cloudwatch_actions}" 39 | alarm_description = "HTTP Code 4xx count is greater than ${var.http_4xx_count} for ${element(split("/", var.load_balancer), 1)}" 40 | ok_actions = "${var.cloudwatch_actions}" 41 | 42 | dimensions = { 43 | LoadBalancer = "${var.load_balancer}" 44 | } 45 | } 46 | 47 | resource "aws_cloudwatch_metric_alarm" "http-code-5xx-count" { 48 | count = "${var.enable}" 49 | 50 | alarm_name = "${element(split("/", var.load_balancer), 1)}-http-code-5xx-count" 51 | comparison_operator = "GreaterThanThreshold" 52 | evaluation_periods = 1 53 | metric_name = "HTTPCode_Target_5XX_Count" 54 | namespace = "AWS/ApplicationELB" 55 | period = 60 56 | statistic = "Minimum" 57 | threshold = "${var.http_5xx_count}" 58 | treat_missing_data = "ignore" 59 | 60 | actions_enabled = "true" 61 | alarm_actions = "${var.cloudwatch_actions}" 62 | alarm_description = "HTTP Code 5xx count is greater than ${var.http_5xx_count} for ${element(split("/", var.load_balancer), 1)}" 63 | ok_actions = "${var.cloudwatch_actions}" 64 | 65 | dimensions = { 66 | LoadBalancer = "${var.load_balancer}" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /ec2.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | tags = ["${null_resource.tags.*.triggers}"] 3 | } 4 | 5 | resource "null_resource" "tags" { 6 | count = "${length(keys(var.tags))}" 7 | 8 | triggers = "${map( 9 | "key", "${element(keys(var.tags), count.index)}", 10 | "value", "${element(values(var.tags), count.index)}", 11 | "propagate_at_launch", "true" 12 | )}" 13 | } 14 | 15 | resource "aws_launch_configuration" "kong" { 16 | name_prefix = "${var.service}-${var.environment}-" 17 | image_id = "${var.ec2_ami[data.aws_region.current.name]}" 18 | instance_type = "${var.ec2_instance_type}" 19 | iam_instance_profile = "${aws_iam_instance_profile.kong.name}" 20 | key_name = "${var.ec2_key_name}" 21 | 22 | security_groups = [ 23 | "${data.aws_security_group.default.id}", 24 | "${aws_security_group.kong.id}", 25 | ] 26 | 27 | associate_public_ip_address = false 28 | enable_monitoring = true 29 | placement_tenancy = "default" 30 | user_data = "${data.template_cloudinit_config.cloud-init.rendered}" 31 | 32 | root_block_device { 33 | volume_size = "${var.ec2_root_volume_size}" 34 | volume_type = "${var.ec2_root_volume_type}" 35 | } 36 | 37 | lifecycle { 38 | create_before_destroy = true 39 | } 40 | 41 | depends_on = [ 42 | "aws_rds_cluster.kong" 43 | ] 44 | } 45 | 46 | resource "aws_autoscaling_group" "kong" { 47 | name = "${var.service}-${var.environment}" 48 | vpc_zone_identifier = ["${data.aws_subnet_ids.private.ids}"] 49 | 50 | launch_configuration = "${aws_launch_configuration.kong.name}" 51 | 52 | desired_capacity = "${var.asg_desired_capacity}" 53 | force_delete = false 54 | health_check_grace_period = "${var.asg_health_check_grace_period}" 55 | health_check_type = "ELB" 56 | max_size = "${var.asg_max_size}" 57 | min_size = "${var.asg_min_size}" 58 | 59 | target_group_arns = ["${ 60 | compact( 61 | concat( 62 | aws_alb_target_group.external.*.arn, 63 | aws_alb_target_group.internal.*.arn, 64 | aws_alb_target_group.internal-admin.*.arn, 65 | aws_alb_target_group.internal-gui.*.arn 66 | ) 67 | ) 68 | }"] 69 | 70 | tags = ["${concat( 71 | list( 72 | map( 73 | "key", "Name", 74 | "value", format("%s-%s", var.service, var.environment), 75 | "propagate_at_launch", true 76 | ) 77 | ), 78 | list( 79 | map( 80 | "key", "Environment", 81 | "value", var.environment, 82 | "propagate_at_launch", true 83 | ) 84 | ), 85 | list( 86 | map( 87 | "key", "Description", 88 | "value", var.description, 89 | "propagate_at_launch", true 90 | ) 91 | ), 92 | list( 93 | map( 94 | "key", "Service", 95 | "value", var.service, 96 | "propagate_at_launch", true 97 | ) 98 | ), 99 | local.tags 100 | ) 101 | }"] 102 | 103 | depends_on = [ 104 | "aws_rds_cluster.kong" 105 | ] 106 | } 107 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ZG Kong Cluster Terraform Module 2 | 3 | [Kong API Gateway](https://konghq.com/) is an API gateway microservices 4 | management layer. This is the terraform module used to provision Kong 5 | clusters at Zillow Group, available under the Apache License 2.0 6 | license. Both Kong Community and Enterprise Edition are supported. 7 | 8 | By default, the following resources will be provisioned: 9 | 10 | - Auora PostgreSQL cluster for Kong's configuration store 11 | - An Auto Scaling Group (ASG) and EC2 instances running Kong (Kong nodes) 12 | - An external load balancer (HTTPS only) 13 | - HTTPS:443 - Kong Proxy 14 | - An internal load balancer (HTTP and HTTPS) 15 | - HTTP:80 - Kong Proxy 16 | - HTTPS:443 - Kong Proxy 17 | - HTTPS:8444 - Kong Admin API (Enterprise Edition only) 18 | - HTTPS:8445 - Kong Admin GUI (Enterprise Edition only) 19 | - Security groups granting least privilege access to resources 20 | - An IAM instance profile for access to Kong specific SSM Parameter Store 21 | metadata and secrets 22 | 23 | Optionally, a redis cluster can be provisioned for rate-limiting counters 24 | and caching, and most default resources can be disabled. See variables.tf 25 | for a complete list and description of tunables. 26 | 27 | The Kong nodes are based on [Minimal Ubuntu](https://wiki.ubuntu.com/Minimal). 28 | Using cloud-init, the following is provisioned on top of the AMI: 29 | 30 | - A kong service user 31 | - Minimal set of dependancies and debugging tools 32 | - Kongfig for Kong configuration management 33 | - Kong, running under runit process supervision 34 | - Splunk plugin for Kong 35 | - Log rotation of Kong log files 36 | 37 | Prerequisites: 38 | 39 | - An AWS VPC 40 | - Private and public subnets labeled using the "Type" tag 41 | - An SSH Key 42 | - An SSL managed certificate to associate with HTTPS load balancers 43 | 44 | Required variables: 45 | 46 | vpc_name VPC Name for the AWS account and region specified 47 | environment Resource environment tag (i.e. dev, stage, prod) 48 | ec2_instance_type EC2 instance type 49 | ec2_key_name AWS SSH Key 50 | ssl_cert_external SSL certificate domain name for the external API HTTPS listener 51 | ssl_cert_internal SSL certificate domain name for the internal API HTTPS listener 52 | ssl_cert_internal_gui SSL certificate domain name for the GUI HTTPS listener 53 | 54 | Example main.tf: 55 | 56 | provider "aws" { 57 | region = "us-west-2" 58 | profile = "dev" 59 | } 60 | 61 | module "kong" { 62 | source = "github.com/zillowgroup/kong-terraform" 63 | 64 | vpc_name = "my-vpc" 65 | environment = "dev" 66 | ec2_instance_type = "t2.small" 67 | ec2_ebs_optimized = false 68 | ec2_key_name = "my-key" 69 | ssl_cert_external = "*.domain.name" 70 | ssl_cert_internal = "*.domain.name" 71 | ssl_cert_internal_gui = "*.domain.name" 72 | 73 | enable_internal_lb = true 74 | 75 | db_instance_count = 3 76 | 77 | tags = { 78 | Owner = "devops@domain.name" 79 | Team = "DevOps" 80 | } 81 | } 82 | 83 | Create the resources in AWS: 84 | 85 | terraform init 86 | terraform plan -out kong.plan 87 | terraform apply kong.plan 88 | 89 | While resources are being provisioned, login to the AWS console and navigate 90 | to: 91 | 92 | EC2 -> Systems Manager Shared Resources -> Parameter Store 93 | 94 | Update the Kong database password parameter with one of your choosing: 95 | 96 | /[service]/[environment]/db/password 97 | 98 | Note: You can generate a random, secure password using: 99 | 100 | pwgen -s 16 101 | 102 | This step is manual to avoid checking in secrets into a repository. 103 | Additionally, if installing Enterprise Edition: 104 | 105 | Update the license key by editing the parameter (default value is "placeholder"): 106 | 107 | /[service]/[environment]/ee/license 108 | 109 | Update the Bintray authentication paramater (default value is "placeholder", format is 110 | "username:apikey")" for Enterprise Edition downloads: 111 | 112 | /[service]/[environment]/ee/bintray-auth 113 | 114 | To login to the EC2 instance(s): 115 | 116 | ssh -i [/path/to/key/specified/in/ec2_key_name] ubuntu@[ec2-instance] 117 | 118 | After you login to an EC2 instance, it is **highly** recommended to update 119 | the master PostgreSQL password using psql from the command line: 120 | 121 | PG_HOST=$(grep ^pg_host /etc/kong/kong.conf | cut -d= -f2 | awk '{print $1}') 122 | PGPASSWORD=KongChangeMeNow#1 psql -h $PG_HOST template1 123 | > ALTER USER root WITH PASSWORD '[new password]'; 124 | 125 | Then update the key in the Parameter Store to the same value: 126 | 127 | /[service]/[environment]/db/password/master 128 | 129 | You are now ready to manage APIs! 130 | -------------------------------------------------------------------------------- /lb.tf: -------------------------------------------------------------------------------- 1 | data "aws_acm_certificate" "external-cert" { 2 | domain = "${var.ssl_cert_external}" 3 | } 4 | 5 | data "aws_acm_certificate" "internal-cert" { 6 | domain = "${var.ssl_cert_internal}" 7 | } 8 | 9 | data "aws_acm_certificate" "gui-cert" { 10 | domain = "${var.ssl_cert_internal_gui}" 11 | } 12 | 13 | # External - HTTPS only 14 | resource "aws_alb_target_group" "external" { 15 | count = "${var.enable_external_lb}" 16 | 17 | name = "${var.service}-${var.environment}-external" 18 | port = 8000 19 | protocol = "HTTP" 20 | vpc_id = "${data.aws_vpc.vpc.id}" 21 | 22 | health_check { 23 | healthy_threshold = "${var.health_check_healthy_threshold}" 24 | interval = "${var.health_check_interval}" 25 | path = "/status" 26 | port = 8000 27 | timeout = "${var.health_check_timeout}" 28 | unhealthy_threshold = "${var.health_check_unhealthy_threshold}" 29 | } 30 | 31 | tags = "${merge( 32 | map("Name", format("%s-%s-external", var.service, var.environment)), 33 | map("Environment", var.environment), 34 | map("Description", var.description), 35 | map("Service", var.service), 36 | var.tags 37 | )}" 38 | } 39 | 40 | resource "aws_alb" "external" { 41 | count = "${var.enable_external_lb}" 42 | 43 | name = "${var.service}-${var.environment}-external" 44 | internal = false 45 | subnets = ["${data.aws_subnet_ids.public.ids}"] 46 | 47 | security_groups = [ 48 | "${aws_security_group.external-lb.id}", 49 | ] 50 | 51 | enable_deletion_protection = "${var.enable_deletion_protection}" 52 | idle_timeout = "${var.idle_timeout}" 53 | 54 | tags = "${merge( 55 | map("Name", format("%s-%s-external", var.service, var.environment)), 56 | map("Environment", var.environment), 57 | map("Description", var.description), 58 | map("Service", var.service), 59 | var.tags 60 | )}" 61 | } 62 | 63 | resource "aws_alb_listener" "external-https" { 64 | count = "${var.enable_external_lb}" 65 | 66 | load_balancer_arn = "${aws_alb.external.arn}" 67 | port = "443" 68 | protocol = "HTTPS" 69 | 70 | ssl_policy = "${var.ssl_policy}" 71 | certificate_arn = "${data.aws_acm_certificate.external-cert.arn}" 72 | 73 | default_action { 74 | target_group_arn = "${element(aws_alb_target_group.external.*.arn, 0)}" 75 | type = "forward" 76 | } 77 | } 78 | 79 | # Internal 80 | resource "aws_alb_target_group" "internal" { 81 | count = "${var.enable_internal_lb}" 82 | 83 | name = "${var.service}-${var.environment}-internal" 84 | port = 8000 85 | protocol = "HTTP" 86 | vpc_id = "${data.aws_vpc.vpc.id}" 87 | 88 | health_check { 89 | healthy_threshold = "${var.health_check_healthy_threshold}" 90 | interval = "${var.health_check_interval}" 91 | path = "/status" 92 | port = 8000 93 | timeout = "${var.health_check_timeout}" 94 | unhealthy_threshold = "${var.health_check_unhealthy_threshold}" 95 | } 96 | 97 | tags = "${merge( 98 | map("Name", format("%s-%s-internal", var.service, var.environment)), 99 | map("Environment", var.environment), 100 | map("Description", var.description), 101 | map("Service", var.service), 102 | var.tags 103 | )}" 104 | } 105 | 106 | resource "aws_alb_target_group" "internal-admin" { 107 | count = "${var.enable_ee}" 108 | 109 | name = "${var.service}-${var.environment}-internal-admin" 110 | port = 8001 111 | protocol = "HTTP" 112 | vpc_id = "${data.aws_vpc.vpc.id}" 113 | 114 | health_check { 115 | healthy_threshold = "${var.health_check_healthy_threshold}" 116 | interval = "${var.health_check_interval}" 117 | path = "/status" 118 | port = 8000 119 | timeout = "${var.health_check_timeout}" 120 | unhealthy_threshold = "${var.health_check_unhealthy_threshold}" 121 | } 122 | 123 | tags = "${merge( 124 | map("Name", format("%s-%s-internal-admin", var.service, var.environment)), 125 | map("Environment", var.environment), 126 | map("Description", var.description), 127 | map("Service", var.service), 128 | var.tags 129 | )}" 130 | } 131 | 132 | resource "aws_alb_target_group" "internal-gui" { 133 | count = "${var.enable_ee}" 134 | 135 | name = "${var.service}-${var.environment}-internal-gui" 136 | port = 8002 137 | protocol = "HTTP" 138 | vpc_id = "${data.aws_vpc.vpc.id}" 139 | 140 | health_check { 141 | healthy_threshold = "${var.health_check_healthy_threshold}" 142 | interval = "${var.health_check_interval}" 143 | path = "/status" 144 | port = 8000 145 | timeout = "${var.health_check_timeout}" 146 | unhealthy_threshold = "${var.health_check_unhealthy_threshold}" 147 | } 148 | 149 | tags = "${merge( 150 | map("Name", format("%s-%s-internal-gui", var.service, var.environment)), 151 | map("Environment", var.environment), 152 | map("Description", var.description), 153 | map("Service", var.service), 154 | var.tags 155 | )}" 156 | } 157 | 158 | resource "aws_alb" "internal" { 159 | count = "${var.enable_internal_lb}" 160 | 161 | name = "${var.service}-${var.environment}-internal" 162 | internal = true 163 | subnets = ["${data.aws_subnet_ids.private.ids}"] 164 | 165 | security_groups = [ 166 | "${aws_security_group.internal-lb.id}", 167 | ] 168 | 169 | enable_deletion_protection = "${var.enable_deletion_protection}" 170 | idle_timeout = "${var.idle_timeout}" 171 | 172 | tags = "${merge( 173 | map("Name", format("%s-%s-internal", var.service, var.environment)), 174 | map("Environment", var.environment), 175 | map("Description", var.description), 176 | map("Service", var.service), 177 | var.tags 178 | )}" 179 | } 180 | 181 | resource "aws_alb_listener" "internal-http" { 182 | count = "${var.enable_internal_lb}" 183 | 184 | load_balancer_arn = "${aws_alb.internal.arn}" 185 | port = "80" 186 | protocol = "HTTP" 187 | 188 | default_action { 189 | target_group_arn = "${element(aws_alb_target_group.internal.*.arn, 0)}" 190 | type = "forward" 191 | } 192 | } 193 | 194 | resource "aws_alb_listener" "internal-https" { 195 | count = "${var.enable_internal_lb}" 196 | 197 | load_balancer_arn = "${aws_alb.internal.arn}" 198 | port = "443" 199 | protocol = "HTTPS" 200 | 201 | ssl_policy = "${var.ssl_policy}" 202 | certificate_arn = "${data.aws_acm_certificate.internal-cert.arn}" 203 | 204 | default_action { 205 | target_group_arn = "${element(aws_alb_target_group.internal.*.arn, 0)}" 206 | type = "forward" 207 | } 208 | } 209 | 210 | resource "aws_alb_listener" "internal-admin" { 211 | count = "${var.enable_ee}" 212 | 213 | load_balancer_arn = "${aws_alb.internal.arn}" 214 | port = "8444" 215 | protocol = "HTTPS" 216 | 217 | ssl_policy = "${var.ssl_policy}" 218 | certificate_arn = "${data.aws_acm_certificate.gui-cert.arn}" 219 | 220 | default_action { 221 | target_group_arn = "${aws_alb_target_group.internal-admin.arn}" 222 | type = "forward" 223 | } 224 | } 225 | 226 | resource "aws_alb_listener" "internal-gui" { 227 | count = "${var.enable_ee}" 228 | 229 | load_balancer_arn = "${aws_alb.internal.arn}" 230 | port = "8445" 231 | protocol = "HTTPS" 232 | 233 | ssl_policy = "${var.ssl_policy}" 234 | certificate_arn = "${data.aws_acm_certificate.gui-cert.arn}" 235 | 236 | default_action { 237 | target_group_arn = "${aws_alb_target_group.internal-gui.arn}" 238 | type = "forward" 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /sg.tf: -------------------------------------------------------------------------------- 1 | # PostgreSQL security group 2 | resource "aws_security_group" "postgresql" { 3 | description = "Kong RDS instance" 4 | name = "${var.service}-${var.environment}-postgresql" 5 | vpc_id = "${data.aws_vpc.vpc.id}" 6 | 7 | tags = "${merge( 8 | map("Name", format("%s-%s-postgresql", var.service, var.environment)), 9 | map("Environment", var.environment), 10 | map("Description", var.description), 11 | map("Service", var.service), 12 | var.tags 13 | )}" 14 | } 15 | 16 | resource "aws_security_group_rule" "postgresql-ingress-kong" { 17 | security_group_id = "${aws_security_group.postgresql.id}" 18 | 19 | type = "ingress" 20 | from_port = 5432 21 | to_port = 5432 22 | protocol = "tcp" 23 | 24 | source_security_group_id = "${aws_security_group.kong.id}" 25 | } 26 | 27 | resource "aws_security_group_rule" "postgresql-ingress-bastion" { 28 | security_group_id = "${aws_security_group.postgresql.id}" 29 | 30 | type = "ingress" 31 | from_port = 5432 32 | to_port = 5432 33 | protocol = "tcp" 34 | 35 | cidr_blocks = "${var.bastion_cidr_blocks}" 36 | } 37 | 38 | # Redis security group 39 | resource "aws_security_group" "redis" { 40 | description = "Kong redis cluster" 41 | name = "${var.service}-${var.environment}-redis" 42 | vpc_id = "${data.aws_vpc.vpc.id}" 43 | 44 | tags = "${merge( 45 | map("Name", format("%s-%s-redis", var.service, var.environment)), 46 | map("Environment", var.environment), 47 | map("Description", var.description), 48 | map("Service", var.service), 49 | var.tags 50 | )}" 51 | } 52 | 53 | resource "aws_security_group_rule" "redis-ingress-kong" { 54 | security_group_id = "${aws_security_group.redis.id}" 55 | 56 | type = "ingress" 57 | from_port = 6379 58 | to_port = 6379 59 | protocol = "tcp" 60 | 61 | source_security_group_id = "${aws_security_group.kong.id}" 62 | } 63 | 64 | resource "aws_security_group_rule" "redis-ingress-bastion" { 65 | security_group_id = "${aws_security_group.redis.id}" 66 | 67 | type = "ingress" 68 | from_port = 6379 69 | to_port = 6379 70 | protocol = "tcp" 71 | 72 | cidr_blocks = "${var.bastion_cidr_blocks}" 73 | } 74 | 75 | # Kong node security group and rules 76 | resource "aws_security_group" "kong" { 77 | description = "Kong EC2 instances" 78 | name = "${var.service}-${var.environment}" 79 | vpc_id = "${data.aws_vpc.vpc.id}" 80 | 81 | tags = "${merge( 82 | map("Name", format("%s-%s", var.service, var.environment)), 83 | map("Environment", var.environment), 84 | map("Description", var.description), 85 | map("Service", var.service), 86 | var.tags 87 | )}" 88 | } 89 | 90 | resource "aws_security_group_rule" "kong-ingress-admin-bastion" { 91 | security_group_id = "${aws_security_group.kong.id}" 92 | 93 | type = "ingress" 94 | from_port = 8001 95 | to_port = 8001 96 | protocol = "tcp" 97 | 98 | cidr_blocks = ["${var.bastion_cidr_blocks}"] 99 | } 100 | 101 | # External load balancer access 102 | resource "aws_security_group_rule" "kong-admin-ingress-external-lb" { 103 | security_group_id = "${aws_security_group.kong.id}" 104 | 105 | type = "ingress" 106 | from_port = 8001 107 | to_port = 8001 108 | protocol = "tcp" 109 | 110 | source_security_group_id = "${aws_security_group.external-lb.id}" 111 | } 112 | 113 | resource "aws_security_group_rule" "kong-api-ingress-external-lb" { 114 | security_group_id = "${aws_security_group.kong.id}" 115 | 116 | type = "ingress" 117 | from_port = 8000 118 | to_port = 8000 119 | protocol = "tcp" 120 | 121 | source_security_group_id = "${aws_security_group.external-lb.id}" 122 | } 123 | 124 | # Internal load balancer access 125 | resource "aws_security_group_rule" "kong-admin-ingress-internal-lb" { 126 | security_group_id = "${aws_security_group.kong.id}" 127 | 128 | type = "ingress" 129 | from_port = 8001 130 | to_port = 8001 131 | protocol = "tcp" 132 | 133 | source_security_group_id = "${aws_security_group.internal-lb.id}" 134 | } 135 | 136 | resource "aws_security_group_rule" "kong-api-ingress-internal-lb" { 137 | security_group_id = "${aws_security_group.kong.id}" 138 | 139 | type = "ingress" 140 | from_port = 8000 141 | to_port = 8000 142 | protocol = "tcp" 143 | 144 | source_security_group_id = "${aws_security_group.internal-lb.id}" 145 | } 146 | 147 | resource "aws_security_group_rule" "kong-gui-ingress-internal-lb" { 148 | count = "${var.enable_ee}" 149 | 150 | security_group_id = "${aws_security_group.kong.id}" 151 | 152 | type = "ingress" 153 | from_port = 8002 154 | to_port = 8002 155 | protocol = "tcp" 156 | 157 | source_security_group_id = "${aws_security_group.internal-lb.id}" 158 | } 159 | 160 | # HTTP outbound for Debian packages 161 | resource "aws_security_group_rule" "kong-egress-http" { 162 | security_group_id = "${aws_security_group.kong.id}" 163 | 164 | type = "egress" 165 | from_port = 80 166 | to_port = 80 167 | protocol = "tcp" 168 | 169 | cidr_blocks = ["0.0.0.0/0"] 170 | } 171 | 172 | # HTTPS outbound for awscli, kong, kongfig 173 | resource "aws_security_group_rule" "kong-egress-https" { 174 | security_group_id = "${aws_security_group.kong.id}" 175 | 176 | type = "egress" 177 | from_port = 443 178 | to_port = 443 179 | protocol = "tcp" 180 | 181 | cidr_blocks = ["0.0.0.0/0"] 182 | } 183 | 184 | # Load balancers 185 | # External 186 | resource "aws_security_group" "external-lb" { 187 | description = "Kong External Load Balancer" 188 | name = "${var.service}-${var.environment}-external-lb" 189 | vpc_id = "${data.aws_vpc.vpc.id}" 190 | 191 | tags = "${merge( 192 | map("Name", format("%s-%s-external-lb", var.service, var.environment)), 193 | map("Environment", var.environment), 194 | map("Description", var.description), 195 | map("Service", var.service), 196 | var.tags 197 | )}" 198 | } 199 | 200 | resource "aws_security_group_rule" "external-lb-ingress-api" { 201 | security_group_id = "${aws_security_group.external-lb.id}" 202 | 203 | type = "ingress" 204 | from_port = 443 205 | to_port = 443 206 | protocol = "tcp" 207 | 208 | cidr_blocks = ["${var.external_cidr_blocks}"] 209 | } 210 | 211 | resource "aws_security_group_rule" "external-lb-egress-kong-admin" { 212 | security_group_id = "${aws_security_group.external-lb.id}" 213 | 214 | type = "egress" 215 | from_port = 8001 216 | to_port = 8001 217 | protocol = "tcp" 218 | 219 | source_security_group_id = "${aws_security_group.kong.id}" 220 | } 221 | 222 | resource "aws_security_group_rule" "external-lb-egress-kong-api" { 223 | security_group_id = "${aws_security_group.external-lb.id}" 224 | 225 | type = "egress" 226 | from_port = 8000 227 | to_port = 8000 228 | protocol = "tcp" 229 | 230 | source_security_group_id = "${aws_security_group.kong.id}" 231 | } 232 | 233 | # Internal 234 | resource "aws_security_group" "internal-lb" { 235 | description = "Kong Internal Load Balancer" 236 | name = "${var.service}-${var.environment}-internal-lb" 237 | vpc_id = "${data.aws_vpc.vpc.id}" 238 | 239 | tags = "${merge( 240 | map("Name", format("%s-%s-internal-lb", var.service, var.environment)), 241 | map("Environment", var.environment), 242 | map("Description", var.description), 243 | map("Service", var.service), 244 | var.tags 245 | )}" 246 | } 247 | 248 | resource "aws_security_group_rule" "internal-lb-ingress-kong-http-api" { 249 | security_group_id = "${aws_security_group.internal-lb.id}" 250 | 251 | type = "ingress" 252 | from_port = 80 253 | to_port = 80 254 | protocol = "tcp" 255 | 256 | cidr_blocks = ["${var.internal_http_cidr_blocks}"] 257 | } 258 | 259 | resource "aws_security_group_rule" "internal-lb-ingress-kong-https-api" { 260 | security_group_id = "${aws_security_group.internal-lb.id}" 261 | 262 | type = "ingress" 263 | from_port = 443 264 | to_port = 443 265 | protocol = "tcp" 266 | 267 | cidr_blocks = ["${var.internal_https_cidr_blocks}"] 268 | } 269 | 270 | resource "aws_security_group_rule" "internal-lb-ingress-kong-admin" { 271 | count = "${var.enable_ee}" 272 | 273 | security_group_id = "${aws_security_group.internal-lb.id}" 274 | 275 | type = "ingress" 276 | from_port = 8444 277 | to_port = 8444 278 | protocol = "tcp" 279 | 280 | cidr_blocks = ["${var.admin_cidr_blocks}"] 281 | } 282 | 283 | resource "aws_security_group_rule" "internal-lb-ingress-kong-gui" { 284 | count = "${var.enable_ee}" 285 | 286 | security_group_id = "${aws_security_group.internal-lb.id}" 287 | 288 | type = "ingress" 289 | from_port = 8445 290 | to_port = 8445 291 | protocol = "tcp" 292 | 293 | cidr_blocks = ["${var.gui_cidr_blocks}"] 294 | } 295 | 296 | resource "aws_security_group_rule" "internal-lb-egress-kong-admin" { 297 | security_group_id = "${aws_security_group.internal-lb.id}" 298 | 299 | type = "egress" 300 | from_port = 8001 301 | to_port = 8001 302 | protocol = "tcp" 303 | 304 | source_security_group_id = "${aws_security_group.kong.id}" 305 | } 306 | 307 | resource "aws_security_group_rule" "internal-lb-egress-kong-api" { 308 | security_group_id = "${aws_security_group.internal-lb.id}" 309 | 310 | type = "egress" 311 | from_port = 8000 312 | to_port = 8000 313 | protocol = "tcp" 314 | 315 | source_security_group_id = "${aws_security_group.kong.id}" 316 | } 317 | 318 | resource "aws_security_group_rule" "internal-lb-egress-kong-gui" { 319 | security_group_id = "${aws_security_group.internal-lb.id}" 320 | 321 | type = "egress" 322 | from_port = 8002 323 | to_port = 8002 324 | protocol = "tcp" 325 | 326 | source_security_group_id = "${aws_security_group.kong.id}" 327 | } 328 | -------------------------------------------------------------------------------- /variables.tf: -------------------------------------------------------------------------------- 1 | # Network settings 2 | variable "vpc_name" { 3 | description = "VPC Name for the AWS account and region specified" 4 | type = "string" 5 | } 6 | 7 | variable "private_subnets" { 8 | description = "'Type' tag on private subnets" 9 | type = "string" 10 | 11 | default = "private-dynamic" 12 | } 13 | 14 | variable "public_subnets" { 15 | description = "'Type' tag on public subnets for external load balancers" 16 | type = "string" 17 | 18 | default = "public" 19 | } 20 | 21 | variable "default_security_group" { 22 | description = "Name of the default VPC security group for EC2 access" 23 | type = "string" 24 | 25 | default = "default" 26 | } 27 | 28 | # Access control 29 | variable "bastion_cidr_blocks" { 30 | description = "Bastion hosts allowed access to PostgreSQL and Kong Admin" 31 | type = "list" 32 | 33 | default = [ 34 | "127.0.0.1/32", 35 | ] 36 | } 37 | 38 | variable "external_cidr_blocks" { 39 | description = "External ingress access to Kong API via the load balancer" 40 | type = "list" 41 | 42 | default = [ 43 | "0.0.0.0/0", 44 | ] 45 | } 46 | 47 | variable "internal_http_cidr_blocks" { 48 | description = "Internal ingress access to Kong API via the load balancer (HTTP)" 49 | type = "list" 50 | 51 | default = [ 52 | "0.0.0.0/0", 53 | ] 54 | } 55 | 56 | variable "internal_https_cidr_blocks" { 57 | description = "Internal ingress access to Kong API via the load balancer (HTTPS)" 58 | type = "list" 59 | 60 | default = [ 61 | "0.0.0.0/0", 62 | ] 63 | } 64 | 65 | variable "admin_cidr_blocks" { 66 | description = "Internal ingress access to Kong Admin API (Enterprise Edition only)" 67 | type = "list" 68 | 69 | default = [ 70 | "0.0.0.0/0", 71 | ] 72 | } 73 | 74 | variable "gui_cidr_blocks" { 75 | description = "Internal ingress access to Kong GUI (Enterprise Edition only)" 76 | type = "list" 77 | 78 | default = [ 79 | "0.0.0.0/0", 80 | ] 81 | } 82 | 83 | # Required tags 84 | variable "description" { 85 | description = "Resource description tag" 86 | type = "string" 87 | 88 | default = "Kong API Gateway" 89 | } 90 | 91 | variable "environment" { 92 | description = "Resource environment tag (i.e. dev, stage, prod)" 93 | type = "string" 94 | } 95 | 96 | variable "service" { 97 | description = "Resource service tag" 98 | type = "string" 99 | 100 | default = "zg-kong-2-1" 101 | } 102 | 103 | # Additional tags 104 | variable "tags" { 105 | description = "Tags to apply to resources" 106 | type = "map" 107 | 108 | default = {} 109 | } 110 | 111 | # Enterprise Edition 112 | variable "enable_ee" { 113 | description = "Boolean to enable Kong Enterprise Edition settings (requires license key in SSM)" 114 | type = "string" 115 | } 116 | 117 | # EC2 settings 118 | 119 | # https://wiki.ubuntu.com/Minimal 120 | variable "ec2_ami" { 121 | description = "Map of Ubuntu Minimal AMIs by region" 122 | type = "map" 123 | 124 | default = { 125 | us-east-1 = "ami-7029320f" 126 | us-east-2 = "ami-0350efe0754b8e179" 127 | us-west-1 = "ami-657f9006" 128 | us-west-2 = "ami-59694f21" 129 | } 130 | } 131 | 132 | variable "ec2_instance_type" { 133 | description = "EC2 instance type" 134 | type = "string" 135 | } 136 | 137 | variable "ec2_root_volume_size" { 138 | description = "Size of the root volume (in Gigabytes)" 139 | type = "string" 140 | 141 | default = 8 142 | } 143 | 144 | variable "ec2_root_volume_type" { 145 | description = "Type of the root volume (standard, gp2, or io)" 146 | type = "string" 147 | 148 | default = "gp2" 149 | } 150 | 151 | variable "ec2_key_name" { 152 | description = "AWS SSH Key" 153 | type = "string" 154 | } 155 | 156 | variable "asg_max_size" { 157 | description = "The maximum size of the auto scale group" 158 | type = "string" 159 | 160 | default = 4 161 | } 162 | 163 | variable "asg_min_size" { 164 | description = "The minimum size of the auto scale group" 165 | type = "string" 166 | 167 | default = 2 168 | } 169 | 170 | variable "asg_desired_capacity" { 171 | description = "The number of instances that should be running in the group" 172 | type = "string" 173 | 174 | default = 3 175 | } 176 | 177 | variable "asg_health_check_grace_period" { 178 | description = "Time in seconds after instance comes into service before checking health" 179 | type = "string" 180 | 181 | # Terraform default is 300 182 | default = 300 183 | } 184 | 185 | # Kong packages 186 | variable "ee_pkg" { 187 | description = "Filename of the Enterprise Edition package" 188 | type = "string" 189 | 190 | default = "kong-enterprise-edition-0.31-1.zesty.all.deb" 191 | } 192 | 193 | variable "ce_pkg" { 194 | description = "Filename of the Community Edition package" 195 | type = "string" 196 | 197 | default = "kong-community-edition-0.12.3.zesty.all.deb" 198 | } 199 | 200 | # Load Balancer settings 201 | variable "enable_external_lb" { 202 | description = "Boolean to enable/create the external load balancer, exposing Kong to the Internet" 203 | type = "string" 204 | 205 | default = true 206 | } 207 | 208 | variable "enable_internal_lb" { 209 | description = "Boolean to enable/create the internal load balancer for the forward proxy" 210 | type = "string" 211 | 212 | default = true 213 | } 214 | 215 | variable "deregistration_delay" { 216 | description = "Seconds to wait before changing the state of a deregistering target from draining to unused" 217 | type = "string" 218 | 219 | # Terraform default is 300 220 | default = 300 221 | } 222 | 223 | variable "enable_deletion_protection" { 224 | description = "Boolean to enable delete protection on the ALB" 225 | type = "string" 226 | 227 | # Terraform default is false 228 | default = true 229 | } 230 | 231 | variable "health_check_healthy_threshold" { 232 | description = "Number of consecutives checks before a unhealthy target is considered healthy" 233 | type = "string" 234 | 235 | # Terraform default is 5 236 | default = 5 237 | } 238 | 239 | variable "health_check_interval" { 240 | description = "Seconds between health checks" 241 | type = "string" 242 | 243 | # Terraform default is 30 244 | default = 5 245 | } 246 | 247 | variable "health_check_matcher" { 248 | description = "HTTP Code(s) that result in a successful response from a target (comma delimited)" 249 | type = "string" 250 | 251 | default = 200 252 | } 253 | 254 | variable "health_check_timeout" { 255 | description = "Seconds waited before a health check fails" 256 | type = "string" 257 | 258 | # Terraform default is 5 259 | default = 3 260 | } 261 | 262 | variable "health_check_unhealthy_threshold" { 263 | description = "Number of consecutive checks before considering a target unhealthy" 264 | type = "string" 265 | 266 | # Terraform default is 2 267 | default = 2 268 | } 269 | 270 | variable "idle_timeout" { 271 | description = "Seconds a connection can idle before being disconnected" 272 | type = "string" 273 | 274 | # Terraform default is 60 275 | default = 60 276 | } 277 | 278 | variable "ssl_cert_external" { 279 | description = "SSL certificate domain name for the external API HTTPS listener" 280 | type = "string" 281 | } 282 | 283 | variable "ssl_cert_internal" { 284 | description = "SSL certificate domain name for the internal API HTTPS listener" 285 | type = "string" 286 | } 287 | 288 | variable "ssl_cert_internal_gui" { 289 | description = "SSL certificate domain name for the GUI HTTPS listener" 290 | type = "string" 291 | } 292 | 293 | variable "ssl_policy" { 294 | description = "SSL Policy for HTTPS Listeners" 295 | type = "string" 296 | 297 | default = "ELBSecurityPolicy-TLS-1-2-2017-01" 298 | } 299 | 300 | # Cloudwatch alarms 301 | variable "cloudwatch_actions" { 302 | description = "List of cloudwatch actions for Alert/Ok" 303 | type = "list" 304 | 305 | default = [] 306 | } 307 | 308 | variable "http_4xx_count" { 309 | description = "HTTP Code 4xx count threshhold" 310 | type = "string" 311 | 312 | default = 50 313 | } 314 | 315 | variable "http_5xx_count" { 316 | description = "HTTP Code 5xx count threshhold" 317 | type = "string" 318 | 319 | default = 50 320 | } 321 | # Datastore settings 322 | variable "db_instance_class" { 323 | description = "Database instance class" 324 | type = "string" 325 | 326 | default = "db.r4.large" 327 | } 328 | 329 | variable "db_instance_count" { 330 | description = "Number of database instances (0 to leverage an existing db)" 331 | type = "string" 332 | 333 | default = 2 334 | } 335 | 336 | variable "db_host" { 337 | description = "Database host name/endpoint if using an existing Aurora cluster" 338 | type = "string" 339 | 340 | default = "placeholder" 341 | } 342 | 343 | variable "db_username" { 344 | description = "Database master username" 345 | type = "string" 346 | 347 | default = "root" 348 | } 349 | 350 | variable "db_password" { 351 | description = "Initial database master password" 352 | type = "string" 353 | 354 | default = "zg-kong-2-1" 355 | } 356 | 357 | variable "db_subnets" { 358 | description = "Database instance subnet group name" 359 | type = "string" 360 | 361 | default = "db-subnets" 362 | } 363 | 364 | variable "db_backup_retention_period" { 365 | description = "The number of days to retain backups" 366 | type = "string" 367 | 368 | default = 7 369 | } 370 | 371 | # Redis settings (for rate_limiting only) 372 | variable "enable_redis" { 373 | description = "Boolean to enable redis AWS resource" 374 | type = "string" 375 | 376 | default = false 377 | } 378 | 379 | variable "redis_instance_type" { 380 | description = "Redis node instance type" 381 | type = "string" 382 | 383 | default = "cache.t2.small" 384 | } 385 | 386 | variable "redis_instance_count" { 387 | description = "Number of redis nodes" 388 | type = "string" 389 | 390 | default = 2 391 | } 392 | 393 | variable "redis_subnets" { 394 | description = "Redis cluster subnet group name" 395 | type = "string" 396 | 397 | default = "cache-subnets" 398 | } 399 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2018 Zillow Group 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /cloud-init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Function to grab SSM parameters 4 | aws_get_parameter() { 5 | aws ssm --region ${REGION} get-parameter \ 6 | --name "${PARAMETER_PATH}/$1" \ 7 | --with-decryption \ 8 | --output text \ 9 | --query Parameter.Value 2>/dev/null 10 | } 11 | 12 | # Enable auto updates 13 | echo "Enabling auto updates" 14 | echo unattended-upgrades unattended-upgrades/enable_auto_updates boolean true \ 15 | | debconf-set-selections 16 | dpkg-reconfigure -f noninteractive unattended-upgrades 17 | echo "Done." 18 | 19 | # Install kongfig 20 | echo "Installing Kongfig" 21 | npm install -g kongfig 22 | echo "Done." 23 | 24 | # Install Kong 25 | echo "Installing Kong" 26 | EE_LICENSE=$(aws_get_parameter ee/license) 27 | EE_CREDS=$(aws_get_parameter ee/bintray-auth) 28 | if [ "$EE_LICENSE" != "placeholder" ]; then 29 | curl -L https://kong.bintray.com/kong-enterprise-edition-deb/dists/${EE_PKG} \ 30 | -u $EE_CREDS \ 31 | -o ${EE_PKG} 32 | 33 | if [ ! -f ${EE_PKG} ]; then 34 | echo "Error: Enterprise edition download failed, aborting." 35 | exit 1 36 | fi 37 | dpkg -i ${EE_PKG} 38 | 39 | cat < /etc/kong/license.json 40 | $EE_LICENSE 41 | EOF 42 | chown root:kong /etc/kong/license.json 43 | chmod 640 /etc/kong/license.json 44 | else 45 | curl -sL https://kong.bintray.com/kong-community-edition-deb/dists/${CE_PKG} \ 46 | -o ${CE_PKG} 47 | dpkg -i ${CE_PKG} 48 | fi 49 | echo "Done." 50 | 51 | # Setup database 52 | echo "Setting up Kong database" 53 | PGPASSWORD=$(aws_get_parameter "db/password/master") 54 | DB_HOST=$(aws_get_parameter "db/host") 55 | DB_NAME=$(aws_get_parameter "db/name") 56 | DB_PASSWORD=$(aws_get_parameter "db/password") 57 | export PGPASSWORD 58 | 59 | RESULT=$(psql --host $DB_HOST --username root \ 60 | --tuples-only --no-align postgres \ 61 | < /etc/kong/kong.conf 83 | # kong.conf, Zillow Group Kong configuration file 84 | # Written by Dennis Kelly 85 | # 86 | # 2018-03-13: Support for 0.12 and load balancing 87 | # 2017-06-20: Initial release 88 | # 89 | # Notes: 90 | # - See kong.conf.default for further information 91 | 92 | # Database settings 93 | database = postgres 94 | pg_host = $DB_HOST 95 | pg_user = ${DB_USER} 96 | pg_password = $DB_PASSWORD 97 | pg_database = $DB_NAME 98 | 99 | # Load balancer headers 100 | real_ip_header = X-Forwarded-For 101 | trusted_ips = 0.0.0.0/0 102 | 103 | # For /status to load balancers 104 | admin_listen = 0.0.0.0:8001 105 | 106 | # SSL is performed by load balancers 107 | ssl = off 108 | admin_ssl = off 109 | admin_gui_ssl = off 110 | 111 | # Add splunk plugin 112 | custom_plugins = splunk-log 113 | EOF 114 | chmod 640 /etc/kong/kong.conf 115 | chgrp kong /etc/kong/kong.conf 116 | 117 | if [ "$EE_LICENSE" != "placeholder" ]; then 118 | echo "" >> /etc/kong/kong.conf 119 | echo "# Enterprise Edition Settings" >> /etc/kong/kong.conf 120 | echo "vitals = on" >> /etc/kong/kong.conf 121 | 122 | for DIR in gui lib portal; do 123 | chown -R kong:kong /usr/local/kong/$DIR 124 | done 125 | else 126 | # CE does not create the kong directory 127 | mkdir /usr/local/kong 128 | fi 129 | 130 | chown root:kong /usr/local/kong 131 | chmod 2775 /usr/local/kong 132 | echo "Done." 133 | 134 | # Initialize Kong 135 | echo "Initializing Kong" 136 | sudo -u kong kong migrations up 137 | sudo -u kong kong prepare 138 | echo "Done." 139 | 140 | cat <<'EOF' > /usr/local/kong/nginx.conf 141 | worker_processes auto; 142 | daemon off; 143 | 144 | pid pids/nginx.pid; 145 | error_log logs/error.log notice; 146 | 147 | worker_rlimit_nofile 65536; 148 | 149 | events { 150 | worker_connections 8192; 151 | multi_accept on; 152 | } 153 | 154 | http { 155 | include nginx-kong.conf; 156 | } 157 | EOF 158 | chown root:kong /usr/local/kong/nginx.conf 159 | 160 | # Splunk plugin 161 | cat <<'EOF' > /usr/local/share/lua/5.1/kong/plugins/log-serializers/splunk.lua 162 | local tablex = require "pl.tablex" 163 | 164 | local _M = {} 165 | 166 | local EMPTY = tablex.readonly({}) 167 | 168 | function _M.serialize(ngx) 169 | local authenticated_entity 170 | if ngx.ctx.authenticated_credential ~= nil then 171 | authenticated_entity = { 172 | id = ngx.ctx.authenticated_credential.id, 173 | consumer_id = ngx.ctx.authenticated_credential.consumer_id 174 | } 175 | end 176 | 177 | local headers = ngx.req.get_headers() 178 | if headers.apikey ~= nil then 179 | headers.apikey = "[REDACTED]" 180 | end 181 | 182 | return { 183 | sourcetype = "kong:api", 184 | host = ngx.var.host, 185 | event = { 186 | request = { 187 | started_at = ngx.req.start_time() * 1000, 188 | uri = ngx.var.request_uri, 189 | url = ngx.var.scheme .. "://" .. ngx.var.host .. ":" .. ngx.var.server_port .. ngx.var.request_uri, 190 | querystring = ngx.req.get_uri_args(), -- parameters, as a table 191 | method = ngx.req.get_method(), -- http method 192 | headers = ngx.req.get_headers(), 193 | size = ngx.var.request_length 194 | }, 195 | upstream_uri = ngx.var.upstream_uri, 196 | response = { 197 | status = ngx.status, 198 | headers = ngx.resp.get_headers(), 199 | size = ngx.var.bytes_sent 200 | }, 201 | tries = (ngx.ctx.balancer_address or EMPTY).tries, 202 | latencies = { 203 | kong = (ngx.ctx.KONG_ACCESS_TIME or 0) + 204 | (ngx.ctx.KONG_RECEIVE_TIME or 0) + 205 | (ngx.ctx.KONG_REWRITE_TIME or 0) + 206 | (ngx.ctx.KONG_BALANCER_TIME or 0), 207 | proxy = ngx.ctx.KONG_WAITING_TIME or -1, 208 | request = ngx.var.request_time * 1000 209 | }, 210 | authenticated_entity = authenticated_entity, 211 | api = ngx.ctx.api, 212 | consumer = ngx.ctx.authenticated_consumer, 213 | client_ip = ngx.var.remote_addr 214 | } 215 | } 216 | end 217 | 218 | return _M 219 | EOF 220 | 221 | mkdir /usr/local/share/lua/5.1/kong/plugins/splunk-log 222 | cat <<'EOF' > /usr/local/share/lua/5.1/kong/plugins/splunk-log/handler.lua 223 | local basic_serializer = require "kong.plugins.log-serializers.splunk" 224 | local BasePlugin = require "kong.plugins.base_plugin" 225 | local cjson = require "cjson" 226 | local url = require "socket.url" 227 | 228 | local string_format = string.format 229 | local cjson_encode = cjson.encode 230 | 231 | local HttpLogHandler = BasePlugin:extend() 232 | 233 | HttpLogHandler.PRIORITY = 12 234 | HttpLogHandler.VERSION = "0.1.0" 235 | 236 | local HTTP = "http" 237 | local HTTPS = "https" 238 | 239 | -- Generates the raw http message. 240 | -- @param `method` http method to be used to send data 241 | -- @param `content_type` the type to set in the header 242 | -- @param `parsed_url` contains the host details 243 | -- @param `body` Body of the message as a string (must be encoded according to the `content_type` parameter) 244 | -- @return raw http message 245 | local function generate_post_payload(token, method, content_type, parsed_url, body) 246 | local url 247 | if parsed_url.query then 248 | url = parsed_url.path .. "?" .. parsed_url.query 249 | else 250 | url = parsed_url.path 251 | end 252 | local headers = string_format( 253 | "%s %s HTTP/1.1\r\nHost: %s\r\nConnection: Keep-Alive\r\nContent-Type: %s\r\nContent-Length: %s\r\n", 254 | method:upper(), url, parsed_url.host, content_type, #body) 255 | 256 | local auth_header = string_format( 257 | "Authorization: Splunk %s\r\n", 258 | token 259 | ) 260 | headers = headers .. auth_header 261 | 262 | return string_format("%s\r\n%s", headers, body) 263 | end 264 | 265 | -- Parse host url. 266 | -- @param `url` host url 267 | -- @return `parsed_url` a table with host details like domain name, port, path etc 268 | local function parse_url(host_url) 269 | local parsed_url = url.parse(host_url) 270 | if not parsed_url.port then 271 | if parsed_url.scheme == HTTP then 272 | parsed_url.port = 80 273 | elseif parsed_url.scheme == HTTPS then 274 | parsed_url.port = 443 275 | end 276 | end 277 | if not parsed_url.path then 278 | parsed_url.path = "/" 279 | end 280 | return parsed_url 281 | end 282 | 283 | -- Log to a Http end point. 284 | -- This basically is structured as a timer callback. 285 | -- @param `premature` see openresty ngx.timer.at function 286 | -- @param `conf` plugin configuration table, holds http endpoint details 287 | -- @param `body` raw http body to be logged 288 | -- @param `name` the plugin name (used for logging purposes in case of errors etc.) 289 | local function log(premature, conf, body, name) 290 | if premature then 291 | return 292 | end 293 | name = "[" .. name .. "] " 294 | 295 | local ok, err 296 | local parsed_url = parse_url(conf.endpoint) 297 | local host = parsed_url.host 298 | local port = tonumber(parsed_url.port) 299 | 300 | local sock = ngx.socket.tcp() 301 | sock:settimeout(conf.timeout) 302 | 303 | ok, err = sock:connect(host, port) 304 | if not ok then 305 | ngx.log(ngx.ERR, name .. "failed to connect to " .. host .. ":" .. tostring(port) .. ": ", err) 306 | return 307 | end 308 | 309 | if parsed_url.scheme == HTTPS then 310 | local _, err = sock:sslhandshake(true, host, false) 311 | if err then 312 | ngx.log(ngx.ERR, name .. "failed to do SSL handshake with " .. host .. ":" .. tostring(port) .. ": ", err) 313 | end 314 | end 315 | 316 | ok, err = sock:send(generate_post_payload(conf.token, conf.method, conf.content_type, parsed_url, body)) 317 | if not ok then 318 | ngx.log(ngx.ERR, name .. "failed to send data to " .. host .. ":" .. tostring(port) .. ": ", err) 319 | end 320 | 321 | ok, err = sock:setkeepalive(conf.keepalive) 322 | if not ok then 323 | ngx.log(ngx.ERR, name .. "failed to keepalive to " .. host .. ":" .. tostring(port) .. ": ", err) 324 | return 325 | end 326 | end 327 | 328 | -- Only provide `name` when deriving from this class. Not when initializing an instance. 329 | function HttpLogHandler:new(name) 330 | HttpLogHandler.super.new(self, name or "splunk-log") 331 | end 332 | 333 | -- serializes context data into an html message body. 334 | -- @param `ngx` The context table for the request being logged 335 | -- @param `conf` plugin configuration table, holds http endpoint details 336 | -- @return html body as string 337 | function HttpLogHandler:serialize(ngx, conf) 338 | return cjson_encode(basic_serializer.serialize(ngx)) 339 | end 340 | 341 | function HttpLogHandler:log(conf) 342 | HttpLogHandler.super.log(self) 343 | 344 | local ok, err = ngx.timer.at(0, log, conf, self:serialize(ngx, conf), self._name) 345 | if not ok then 346 | ngx.log(ngx.ERR, "[" .. self._name .. "] failed to create timer: ", err) 347 | end 348 | end 349 | 350 | return HttpLogHandler 351 | EOF 352 | 353 | cat <<'EOF' > /usr/local/share/lua/5.1/kong/plugins/splunk-log/schema.lua 354 | return { 355 | fields = { 356 | endpoint = { required = true, type = "url" }, 357 | token = { required = true, type = "string" }, 358 | method = { default = "POST", enum = { "POST", "PUT", "PATCH" } }, 359 | content_type = { default = "application/json", enum = { "application/json" } }, 360 | timeout = { default = 10000, type = "number" }, 361 | keepalive = { default = 60000, type = "number" } 362 | } 363 | } 364 | EOF 365 | 366 | # Log rotation 367 | cat <<'EOF' > /etc/logrotate.d/kong 368 | /usr/local/kong/logs/*.log { 369 | rotate 14 370 | daily 371 | compress 372 | missingok 373 | notifempty 374 | create 640 kong kong 375 | sharedscripts 376 | 377 | postrotate 378 | /usr/bin/sv 1 /etc/sv/kong 379 | endscript 380 | } 381 | EOF 382 | 383 | # Start Kong under supervision 384 | echo "Starting Kong under supervision" 385 | mkdir -p /etc/sv/kong /etc/sv/kong/log 386 | 387 | cat <<'EOF' > /etc/sv/kong/run 388 | #!/bin/sh -e 389 | exec 2>&1 390 | 391 | ulimit -n 65536 392 | sudo -u kong kong prepare 393 | exec chpst -u kong /usr/local/openresty/nginx/sbin/nginx -p /usr/local/kong -c nginx.conf 394 | EOF 395 | 396 | cat <<'EOF' > /etc/sv/kong/log/run 397 | #!/bin/sh -e 398 | 399 | [ -d /var/log/kong ] || mkdir -p /var/log/kong 400 | chown kong:kong /var/log/kong 401 | 402 | exec chpst -u kong /usr/bin/svlogd -tt /var/log/kong 403 | EOF 404 | chmod 744 /etc/sv/kong/run /etc/sv/kong/log/run 405 | 406 | cd /etc/service 407 | ln -s /etc/sv/kong 408 | echo "Done." 409 | 410 | # Verify Admin API is up 411 | RUNNING=0 412 | for I in 1 2 3 4 5; do 413 | curl -s -I http://localhost:8001/status | grep -q "200 OK" 414 | if [ $? = 0 ]; then 415 | RUNNING=1 416 | break 417 | fi 418 | sleep 1 419 | done 420 | 421 | if [ $RUNNING = 0 ]; then 422 | echo "Cannot connect to admin API, aborting" 423 | exit 1 424 | fi 425 | 426 | # Enable healthchecks using a kong endpoint 427 | curl -s -I http://localhost:8000/status | grep -q "200 OK" 428 | if [ $? != 0 ]; then 429 | curl -s -X POST http://localhost:8001/apis \ 430 | -d name=status -d uris=/status -d methods=GET \ 431 | -d upstream_url=http://localhost:8001/status > /dev/null 432 | fi 433 | 434 | if [ "$EE_LICENSE" != "placeholder" ]; then 435 | echo "Configuring enterprise edition RBAC settings" 436 | 437 | # Admin user 438 | curl -s -I http://localhost:8001/rbac/users/admin | grep -q "200 OK" 439 | if [ $? != 0 ]; then 440 | curl -X POST http://localhost:8001/rbac/users \ 441 | -d name=admin -d user_token=zg-kong-2-1 > /dev/null 442 | curl -X POST http://localhost:8001/rbac/users/admin/roles \ 443 | -d roles=super-admin > /dev/null 444 | curl -X POST http://localhost:8001/rbac/users \ 445 | -d name=monitor -d user_token=monitor > /dev/null 446 | fi 447 | 448 | # Monitor permissions, role, and user for ALB healthcheck 449 | curl -s -I http://localhost:8001/rbac/roles/monitor | grep -q "200 OK" 450 | if [ $? != 0 ]; then 451 | curl -s -X POST http://localhost:8001/rbac/permissions \ 452 | -d name=monitor -d resources=status -d actions=read > /dev/null 453 | curl -s -X POST http://localhost:8001/rbac/roles \ 454 | -d name=monitor -d comment='Load balancer access to /status' > /dev/null 455 | curl -s -X POST http://localhost:8001/rbac/roles/monitor/permissions \ 456 | -d permissions=monitor > /dev/null 457 | curl -s -X POST http://localhost:8001/rbac/users \ 458 | -d name=monitor -d user_token=monitor 459 | curl -s -X POST http://localhost:8001/rbac/users/monitor/roles \ 460 | -d roles=monitor > /dev/null 461 | 462 | # Add authentication token for /status 463 | curl -s -X POST http://localhost:8001/apis/status/plugins \ 464 | -d name=request-transformer-advanced \ 465 | -d config.add.headers=Kong-Admin-Token:monitor > /dev/null 466 | fi 467 | 468 | sv stop /etc/sv/kong 469 | echo "enforce_rbac = on" >> /etc/kong/kong.conf 470 | sudo -u kong kong prepare 471 | sv start /etc/sv/kong 472 | fi 473 | --------------------------------------------------------------------------------