├── .github └── workflows │ └── terraform.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── aurora.tf ├── cloud-init.cfg ├── cloud-init.sh ├── cloud-init.tf ├── cw.tf ├── cw └── lb │ ├── main.tf │ └── variables.tf ├── data.tf ├── ec2.tf ├── iam.tf ├── lb.tf ├── locals.tf ├── outputs.tf ├── passwords.tf ├── rds.tf ├── redis.tf ├── security.tf ├── ssm.tf └── variables.tf /.github/workflows/terraform.yml: -------------------------------------------------------------------------------- 1 | name: 'Terraform GitHub Actions' 2 | on: 3 | - pull_request 4 | jobs: 5 | terraform: 6 | name: 'Terraform' 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: 'Checkout' 10 | uses: actions/checkout@master 11 | - name: 'Terraform Format' 12 | uses: hashicorp/terraform-github-actions@master 13 | with: 14 | tf_actions_version: 0.12.13 15 | tf_actions_subcommand: 'fmt' 16 | env: 17 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 18 | - name: 'Terraform Init' 19 | uses: hashicorp/terraform-github-actions@master 20 | with: 21 | tf_actions_version: 0.12.13 22 | tf_actions_subcommand: 'init' 23 | env: 24 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Local .terraform directories 2 | **/.terraform/* 3 | 4 | # .tfstate files 5 | *.tfstate 6 | *.tfstate.* 7 | 8 | # Crash log files 9 | crash.log 10 | crash.*.log 11 | 12 | # Exclude all .tfvars files, which are likely to contain sensitive 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 | *.tfvars 17 | *.tfvars.json 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 | # !example_override.tf 28 | 29 | # Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan 30 | # example: *tfplan* 31 | 32 | # Ignore CLI configuration files 33 | .terraformrc 34 | terraform.rc 35 | 36 | # Ignore terraform lock 37 | .terraform.lock.hcl 38 | 39 | tmp/ 40 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## [3.0] - 2019-09-23 4 | 5 | ### Added 6 | 7 | - RDS support (now the default over Aurora). 8 | - Dev Portal support in Enterprise Edition. 9 | - [decK: declarative Kong configuration](https://github.com/hbagdi/deck) support. 10 | - Use random provider to automatically generate passwords. 11 | 12 | ### Changed 13 | 14 | - Complete refactor for Terraform 0.12. Older versions are no longer supported. 15 | - Migrated to Kong 1.x. Older versions are no longer supported. 16 | - Removed kongfig in favor of decK (supports services and routes, and actively maintained). 17 | - Additional database and cache configuration. 18 | - Variable `vpc_name` renamed to simply `vpc`. 19 | 20 | ### Fixed 21 | 22 | - Removed unused variable `ec2_ebs_optimized` that was causing confusion and errors for some. 23 | 24 | ## [2.1] - 2018-09-18 25 | 26 | First public release. 27 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) Zillow Group 2018 2 | Copyright (c) Kong Inc. 2019 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kong Cluster Terraform Module for AWS 2 | 3 | :warning: This terraform module serves as reference point for getting started. 4 | While it may work for certain scenarios, it is NOT intended to work with 5 | all setups. Please fork the repo or copy over code from here 6 | (liberal Apache-licensed). 7 | 8 | 9 | [Kong API Gateway](https://konghq.com/) is an API gateway microservices 10 | management layer. Both Kong and Enterprise Edition are supported. 11 | 12 | By default, the following resources will be provisioned: 13 | 14 | - RDS PostgreSQL database for Kong's configuration store 15 | - An Auto Scaling Group (ASG) and EC2 instances running Kong (Kong nodes) 16 | - An external load balancer (HTTPS only) 17 | - HTTPS:443 - Kong Proxy 18 | - An internal load balancer (HTTP and HTTPS) 19 | - HTTP:80 - Kong Proxy 20 | - HTTPS:443 - Kong Proxy 21 | - HTTPS:8444 - Kong Admin API (Enterprise Edition only) 22 | - HTTPS:8445 - Kong Manager (Enterprise Edition only) 23 | - HTTPS:8446 - Kong Dev Portal GUI (Enterprise Edition only) 24 | - HTTPS:8447 - Kong Dev Portal API (Enterprise Edition only) 25 | - Security groups granting least privilege access to resources 26 | - An IAM instance profile for access to Kong specific SSM Parameter Store 27 | metadata and secrets 28 | 29 | Optionally, a redis cluster can be provisioned for rate-limiting counters and 30 | caching, and most default resources can be disabled. See variables.tf for a 31 | complete list and description of tunables. 32 | 33 | The Kong nodes are based on [Minimal Ubuntu](https://wiki.ubuntu.com/Minimal). 34 | Using cloud-init, the following is provisioned on top of the AMI: 35 | 36 | - A kong service user 37 | - Minimal set of dependencies and debugging tools 38 | - decK for Kong declarative configuration management 39 | - Kong, running under runit process supervision 40 | - Log rotation of Kong log files 41 | 42 | Prerequisites: 43 | 44 | - An AWS VPC 45 | - Private and public subnets tagged with a subnet_tag (default = 'Tier' tag) 46 | - Database subnet group 47 | - Cache subnet group (if enabling Redis) 48 | - An SSH Key 49 | - An SSL managed certificate to associate with HTTPS load balancers 50 | 51 | ## Variables 52 | 53 | 54 | 55 | 56 | 57 | 58 | 61 | 69 | 70 | 71 | 72 | 73 | 74 | 77 | 80 | 81 | 82 | 83 | 84 | 85 | 88 | 91 | 92 | 93 | 94 | 95 | 96 | 99 | 102 | 103 | 104 | 105 | 106 | 107 | 110 | 113 | 114 | 115 | 116 | 117 | 118 | 121 | 129 | 130 | 131 | 132 | 133 | 134 | 137 | 140 | 141 | 142 | 143 | 144 | 145 | 148 | 151 | 152 | 153 | 154 | 155 | 156 | 159 | 162 | 163 | 164 | 165 | 166 | 167 | 170 | 173 | 174 | 175 | 176 | 177 | 178 | 181 | 184 | 185 | 186 | 187 | 188 | 189 | 192 | 195 | 196 | 197 | 198 | 199 | 200 | 203 | 206 | 207 | 208 | 209 | 210 | 211 | 214 | 217 | 218 | 219 | 220 | 221 | 222 | 225 | 228 | 229 | 230 | 231 | 232 | 233 | 236 | 239 | 240 | 241 | 242 | 243 | 244 | 247 | 250 | 251 | 252 | 253 | 254 | 255 | 258 | 261 | 262 | 263 | 264 | 265 | 266 | 269 | 272 | 273 | 274 | 275 | 276 | 277 | 280 | 283 | 284 | 285 | 286 | 287 | 288 | 291 | 294 | 295 | 296 | 297 | 298 | 299 | 302 | 305 | 306 | 307 | 308 | 309 | 310 | 313 | 316 | 317 | 318 | 319 | 320 | 321 | 324 | 335 | 336 | 337 | 338 | 339 | 340 | 343 | 346 | 347 | 348 | 349 | 350 | 351 | 354 | 357 | 358 | 359 | 360 | 361 | 362 | 365 | 368 | 369 | 370 | 371 | 372 | 373 | 376 | 379 | 380 | 381 | 382 | 383 | 384 | 387 | 390 | 391 | 392 | 393 | 394 | 395 | 398 | 401 | 402 | 403 | 404 | 405 | 406 | 409 | 412 | 413 | 414 | 415 | 416 | 417 | 420 | 423 | 424 | 425 | 426 | 427 | 428 | 431 | 434 | 435 | 436 | 437 | 438 | 439 | 442 | 445 | 446 | 447 | 448 | 449 | 450 | 453 | 456 | 457 | 458 | 459 | 460 | 461 | 464 | 467 | 468 | 469 | 470 | 471 | 472 | 475 | 478 | 479 | 480 | 481 | 482 | 483 | 486 | 494 | 495 | 496 | 497 | 498 | 499 | 502 | 505 | 506 | 507 | 508 | 509 | 510 | 513 | 516 | 517 | 518 | 519 | 520 | 521 | 524 | 527 | 528 | 529 | 530 | 531 | 532 | 535 | 538 | 539 | 540 | 541 | 542 | 543 | 546 | 549 | 550 | 551 | 552 | 553 | 554 | 557 | 560 | 561 | 562 | 563 | 564 | 565 | 568 | 571 | 572 | 573 | 574 | 575 | 576 | 579 | 582 | 583 | 584 | 585 | 586 | 587 | 590 | 598 | 599 | 600 | 601 | 602 | 603 | 606 | 614 | 615 | 616 | 617 | 618 | 619 | 622 | 630 | 631 | 632 | 633 | 634 | 635 | 639 | 643 | 644 | 645 | 646 | 647 | 648 | 651 | 659 | 660 | 661 | 662 | 663 | 664 | 668 | 672 | 673 | 674 | 675 | 676 | 677 | 680 | 683 | 684 | 685 | 686 | 687 | 688 | 691 | 694 | 695 | 696 | 697 | 698 | 699 | 702 | 705 | 706 | 707 | 708 | 709 | 710 | 713 | 716 | 717 | 718 | 719 | 720 | 721 | 724 | 727 | 728 | 729 | 730 | 731 | 732 | 735 | 738 | 739 | 740 | 741 | 742 | 743 | 746 | 749 | 750 | 751 | 752 | 753 | 754 | 757 | 760 | 761 | 762 | 763 | 764 | 765 | 768 | 771 | 772 | 773 | 774 | 775 | 776 | 779 | 782 | 783 | 784 | 785 | 786 | 787 | 790 | 793 | 794 | 795 | 796 | 797 | 798 | 801 | 804 | 805 | 806 | 807 | 808 | 809 | 812 | 815 | 816 | 817 | 818 | 819 | 820 | 823 | 826 | 827 | 828 | 829 | 830 | 831 | 834 | 837 | 838 | 839 | 840 | 841 | 842 | 845 | 848 | 849 | 850 | 851 | 852 | 853 | 856 | 859 | 860 | 861 | 862 | 863 | 864 | 867 | 870 | 871 | 872 |
NameDescriptionTypeDefault Required
admin_cidr_blocksAccess to Kong Admin API (Enterprise Edition only) 59 | 60 | `list(string)` 62 | 63 | ```json 64 | [ 65 | "0.0.0.0/0" 66 | ] 67 | ``` 68 | no
asg_desired_capacityThe number of instances that should be running in the group 75 | 76 | `string` 78 | 79 | `2`no
asg_health_check_grace_periodTime in seconds after instance comes into service before checking health 86 | 87 | `string` 89 | 90 | `300`no
asg_max_sizeThe maximum size of the auto scale group 97 | 98 | `string` 100 | 101 | `3`no
asg_min_sizeThe minimum size of the auto scale group 108 | 109 | `string` 111 | 112 | `1`no
bastion_cidr_blocksBastion hosts allowed access to PostgreSQL and Kong Admin 119 | 120 | `list(string)` 122 | 123 | ```json 124 | [ 125 | "127.0.0.1/32" 126 | ] 127 | ``` 128 | no
ce_pkgFilename of the Community Edition package 135 | 136 | `string` 138 | 139 | `"kong-1.3.0.bionic.amd64.deb"`no
cloudwatch_actionsList of cloudwatch actions for Alert/Ok 146 | 147 | `list(string)` 149 | 150 | `[]`no
db_backup_retention_periodThe number of days to retain backups 157 | 158 | `string` 160 | 161 | `7`no
db_engine_modeEngine mode for Aurora 168 | 169 | `string` 171 | 172 | `"provisioned"`no
db_engine_versionDatabase engine version 179 | 180 | `string` 182 | 183 | `"11.4"`no
db_familyDatabase parameter group family 190 | 191 | `string` 193 | 194 | `"postgres11"`no
db_instance_classDatabase instance class 201 | 202 | `string` 204 | 205 | `"db.t2.micro"`no
db_instance_countNumber of database instances (0 to leverage an existing db) 212 | 213 | `string` 215 | 216 | `1`no
db_multi_azBoolean to specify if RDS is multi-AZ 223 | 224 | `string` 226 | 227 | `false`no
db_storage_sizeSize of the database storage in Gigabytes 234 | 235 | `string` 237 | 238 | `20`no
db_storage_typeType of the database storage 245 | 246 | `string` 248 | 249 | `"gp2"`no
db_subnetsDatabase instance subnet group name 256 | 257 | `string` 259 | 260 | `"db-subnets"`no
db_usernameDatabase master username 267 | 268 | `string` 270 | 271 | `"root"`no
deck_versionVersion of decK to install 278 | 279 | `string` 281 | 282 | `"0.5.2"`no
additional_security_groupsIDs of the additional security groups attached to Kong EC2 instance 289 | 290 | `list(string)` 292 | 293 | `[]`no
deregistration_delaySeconds to wait before changing the state of a deregistering target from draining to unused 300 | 301 | `string` 303 | 304 | `300`no
descriptionResource description tag 311 | 312 | `string` 314 | 315 | `"Kong API Gateway"`no
ec2_amiMap of Ubuntu Minimal AMIs by region 322 | 323 | `map(string)` 325 | 326 | ```json 327 | { 328 | "us-east-1": "ami-7029320f", 329 | "us-east-2": "ami-0350efe0754b8e179", 330 | "us-west-1": "ami-657f9006", 331 | "us-west-2": "ami-59694f21" 332 | } 333 | ``` 334 | no
ec2_instance_typeEC2 instance type 341 | 342 | `string` 344 | 345 | `"t2.micro"`no
ec2_key_nameAWS SSH Key 352 | 353 | `string` 355 | 356 | `""`no
ec2_root_volume_sizeSize of the root volume (in Gigabytes) 363 | 364 | `string` 366 | 367 | `8`no
ec2_root_volume_typeType of the root volume (standard, gp2, or io) 374 | 375 | `string` 377 | 378 | `"gp2"`no
ee_licenseEnterprise Edition license key (JSON format) 385 | 386 | `string` 388 | 389 | `"placeholder"`no
ee_pkgFilename of the Enterprise Edition package 396 | 397 | `string` 399 | 400 | `"kong-enterprise-edition-0.36-2.bionic.all.deb"`no
enable_auroraBoolean to enable Aurora 407 | 408 | `string` 410 | 411 | `"false"`no
enable_deletion_protectionBoolean to enable delete protection on the ALB 418 | 419 | `string` 421 | 422 | `true`no
enable_eeBoolean to enable Kong Enterprise Edition settings 429 | 430 | `string` 432 | 433 | `false`no
enable_external_lbBoolean to enable/create the external load balancer, exposing Kong to the Internet 440 | 441 | `string` 443 | 444 | `true`no
enable_internal_lbBoolean to enable/create the internal load balancer for the forward proxy 451 | 452 | `string` 454 | 455 | `true`no
enable_redisBoolean to enable redis AWS resource 462 | 463 | `string` 465 | 466 | `false`no
environmentResource environment tag (i.e. dev, stage, prod) 473 | 474 | `string` 476 | 477 | n/ayes
external_cidr_blocksExternal ingress access to Kong Proxy via the load balancer 484 | 485 | `list(string)` 487 | 488 | ```json 489 | [ 490 | "0.0.0.0/0" 491 | ] 492 | ``` 493 | no
health_check_healthy_thresholdNumber of consecutives checks before a unhealthy target is considered healthy 500 | 501 | `string` 503 | 504 | `5`no
health_check_intervalSeconds between health checks 511 | 512 | `string` 514 | 515 | `5`no
health_check_matcherHTTP Code(s) that result in a successful response from a target (comma delimited) 522 | 523 | `string` 525 | 526 | `200`no
health_check_timeoutSeconds waited before a health check fails 533 | 534 | `string` 536 | 537 | `3`no
health_check_unhealthy_thresholdNumber of consecutive checks before considering a target unhealthy 544 | 545 | `string` 547 | 548 | `2`no
http_4xx_countHTTP Code 4xx count threshhold 555 | 556 | `string` 558 | 559 | `50`no
http_5xx_countHTTP Code 5xx count threshhold 566 | 567 | `string` 569 | 570 | `50`no
idle_timeoutSeconds a connection can idle before being disconnected 577 | 578 | `string` 580 | 581 | `60`no
internal_http_cidr_blocksInternal ingress access to Kong Proxy via the load balancer (HTTP) 588 | 589 | `list(string)` 591 | 592 | ```json 593 | [ 594 | "0.0.0.0/0" 595 | ] 596 | ``` 597 | no
internal_https_cidr_blocksInternal ingress access to Kong Proxy via the load balancer (HTTPS) 604 | 605 | `list(string)` 607 | 608 | ```json 609 | [ 610 | "0.0.0.0/0" 611 | ] 612 | ``` 613 | no
manager_cidr_blocksAccess to Kong Manager (Enterprise Edition only) 620 | 621 | `list(string)` 623 | 624 | ```json 625 | [ 626 | "0.0.0.0/0" 627 | ] 628 | ``` 629 | no
manager_hostHostname to access Kong Manager (Enterprise Edition only) 636 | 637 | `string` 638 | 640 | 641 | `"default`" 642 | no
portal_cidr_blocksAccess to Portal (Enterprise Edition only) 649 | 650 | `list(string)` 652 | 653 | ```json 654 | [ 655 | "0.0.0.0/0" 656 | ] 657 | ``` 658 | no
portal_hostHostname to access Portal (Enterprise Edition only) 665 | 666 | `string` 667 | 669 | 670 | `"default`" 671 | no
private_subnetsSubnet tag on private subnets 678 | 679 | `string` 681 | 682 | `"private"`no
public_subnetsSubnet tag on public subnets for external load balancers 689 | 690 | `string` 692 | 693 | `"public"`no
redis_engine_versionRedis engine version 700 | 701 | `string` 703 | 704 | `"5.0.5"`no
redis_familyRedis parameter group family 711 | 712 | `string` 714 | 715 | `"redis5.0"`no
redis_instance_countNumber of redis nodes 722 | 723 | `string` 725 | 726 | `2`no
redis_instance_typeRedis node instance type 733 | 734 | `string` 736 | 737 | `"cache.t2.small"`no
redis_subnetsRedis cluster subnet group name 744 | 745 | `string` 747 | 748 | `"cache-subnets"`no
serviceResource service tag 755 | 756 | `string` 758 | 759 | `"kong"`no
ssl_cert_adminSSL certificate domain name for the Kong Admin API HTTPS listener 766 | 767 | `string` 769 | 770 | n/ayes
ssl_cert_externalSSL certificate domain name for the external Kong Proxy HTTPS listener 777 | 778 | `string` 780 | 781 | n/ayes
ssl_cert_internalSSL certificate domain name for the internal Kong Proxy HTTPS listener 788 | 789 | `string` 791 | 792 | n/ayes
ssl_cert_managerSSL certificate domain name for the Kong Manager HTTPS listener 799 | 800 | `string` 802 | 803 | n/ayes
ssl_cert_portalSSL certificate domain name for the Dev Portal listener 810 | 811 | `string` 813 | 814 | n/ayes
ssl_policySSL Policy for HTTPS Listeners 821 | 822 | `string` 824 | 825 | `"ELBSecurityPolicy-TLS-1-2-2017-01"`no
subnet_tagTag used on subnets to define Tier 832 | 833 | `string` 835 | 836 | `"Tier"`no
tagsTags to apply to resources 843 | 844 | `map` 846 | 847 | `{}`no
vpc_idVPC ID for the AWS account and region specified 854 | 855 | `string` 857 | 858 | n/ayes
db_final_snapshot_identifierIf specified a final snapshot will be made of the RDS/Aurora instance. If left blank, the finalsnapshot will be skipped 865 | 866 | `string` 868 | 869 | ""no
873 | 874 | Note: Admin, manager, and portal are Enterprise features. While the SSL 875 | certificate needs to be defined, it can be the same as the external and/or 876 | internal; however, no resources associated with it are created unless enabled. 877 | 878 | ## Outputs 879 | 880 | | Name | Description | 881 | |------|-------------| 882 | | admin\_token | The admin token for Kong | 883 | | lb\_endpoint\_external | The external load balancer endpoint | 884 | | lb\_endpoint\_internal | The internal load balancer endpoint | 885 | | master\_password | The master password for Kong | 886 | | rds\_endpoint | The endpoint for the Kong database | 887 | | rds\_password | The database password for Kong | 888 | 889 | ## Examples 890 | 891 | Example main.tf: 892 | 893 | provider "aws" { 894 | region = "us-west-2" 895 | profile = "dev" 896 | } 897 | 898 | module "kong" { 899 | source = "github.com/kong/kong-terraform-aws?ref=v3.3" 900 | 901 | vpc = "my-vpc" 902 | environment = "dev" 903 | ec2_key_name = "my-key" 904 | ssl_cert_external = "*.domain.name" 905 | ssl_cert_internal = "*.domain.name" 906 | ssl_cert_admin = "*.domain.name" 907 | ssl_cert_manager = "*.domain.name" 908 | ssl_cert_portal = "*.domain.name" 909 | 910 | tags = { 911 | Owner = "devops@domain.name" 912 | Team = "DevOps" 913 | } 914 | } 915 | 916 | Create the resources in AWS: 917 | 918 | terraform init 919 | terraform plan -out kong.plan 920 | terraform apply kong.plan 921 | 922 | If installing Enterprise Edition, while resources are being provisioned login 923 | to the AWS console and navigate to: 924 | 925 | Systems Manager -> Parameter Store 926 | 927 | Update the license key by editing the parameter (default value is "placeholder"): 928 | 929 | /[service]/[environment]/ee/license 930 | 931 | Alternatively, if your terraform files and state are secure, you can pass them 932 | as variables to the module for a completely hands-off installation. 933 | 934 | To login to the EC2 instance(s): 935 | 936 | ssh -i [/path/to/key/specified/in/ec2_key_name] ubuntu@[ec2-instance] 937 | 938 | You are now ready to manage APIs! 939 | -------------------------------------------------------------------------------- /aurora.tf: -------------------------------------------------------------------------------- 1 | resource "aws_rds_cluster" "kong" { 2 | count = var.enable_aurora && var.db_instance_count > 0 ? 1 : 0 3 | 4 | cluster_identifier = format("%s-%s", var.service, var.environment) 5 | engine = "aurora-postgresql" 6 | engine_version = var.db_engine_version 7 | engine_mode = var.db_engine_mode 8 | master_username = var.db_username 9 | master_password = random_string.master_password.result 10 | 11 | backup_retention_period = var.db_backup_retention_period 12 | db_subnet_group_name = var.db_subnets 13 | db_cluster_parameter_group_name = format("%s-%s-cluster", var.service, var.environment) 14 | 15 | vpc_security_group_ids = [aws_security_group.postgresql.id] 16 | 17 | skip_final_snapshot = var.db_final_snapshot_identifier == "" ? true : false 18 | final_snapshot_identifier = var.db_final_snapshot_identifier == "" ? null : var.db_final_snapshot_identifier 19 | 20 | tags = merge( 21 | { 22 | "Name" = format("%s-%s", var.service, var.environment), 23 | "Environment" = var.environment, 24 | "Description" = var.description, 25 | "Service" = var.service, 26 | }, 27 | var.tags 28 | ) 29 | } 30 | 31 | resource "aws_rds_cluster_instance" "kong" { 32 | count = var.enable_aurora ? var.db_instance_count : 0 33 | 34 | identifier = format("%s-%s-%s", var.service, var.environment, count.index) 35 | cluster_identifier = aws_rds_cluster.kong[0].id 36 | engine = "aurora-postgresql" 37 | engine_version = var.db_engine_version 38 | instance_class = var.db_instance_class 39 | 40 | db_subnet_group_name = var.db_subnets 41 | db_parameter_group_name = format("%s-%s", var.service, var.environment) 42 | 43 | tags = merge( 44 | { 45 | "Name" = format("%s-%s", var.service, var.environment), 46 | "Environment" = var.environment, 47 | "Description" = var.description, 48 | "Service" = var.service, 49 | }, 50 | var.tags 51 | ) 52 | } 53 | 54 | resource "aws_rds_cluster_parameter_group" "kong" { 55 | count = var.enable_aurora && var.db_instance_count > 0 ? 1 : 0 56 | 57 | name = format("%s-%s-cluster", var.service, var.environment) 58 | family = var.db_family 59 | description = var.description 60 | 61 | tags = merge( 62 | { 63 | "Name" = format("%s-%s-cluster", var.service, var.environment), 64 | "Environment" = var.environment, 65 | "Description" = var.description, 66 | "Service" = var.service, 67 | }, 68 | var.tags 69 | ) 70 | } 71 | 72 | -------------------------------------------------------------------------------- /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 | - zlib1g-dev 46 | -------------------------------------------------------------------------------- /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 | 18 | # Installing decK 19 | # https://github.com/hbagdi/deck 20 | curl -sL https://github.com/Kong/deck/releases/download/v${DECK_VERSION}/deck_${DECK_VERSION}_linux_amd64.tar.gz \ 21 | -o deck.tar.gz 22 | tar zxf deck.tar.gz deck 23 | sudo mv deck /usr/local/bin 24 | sudo chown root:kong /usr/local/bin/deck 25 | sudo chmod 755 /usr/local/bin/deck 26 | 27 | # Install Kong 28 | echo "Installing Kong" 29 | EE_LICENSE=$(aws_get_parameter ee/license) 30 | if [ "$EE_LICENSE" != "placeholder" ]; then 31 | curl -sL "https://download.konghq.com/gateway-2.x-ubuntu-bionic/pool/all/k/kong-enterprise-edition/${EE_PKG}" \ 32 | -o ${EE_PKG} 33 | 34 | if [ ! -f ${EE_PKG} ]; then 35 | echo "Error: Enterprise edition download failed, aborting." 36 | exit 1 37 | fi 38 | dpkg -i ${EE_PKG} 39 | 40 | cat < /etc/kong/license.json 41 | $EE_LICENSE 42 | EOF 43 | chown root:kong /etc/kong/license.json 44 | chmod 640 /etc/kong/license.json 45 | else 46 | curl -sL "https://download.konghq.com/gateway-2.x-ubuntu-bionic/pool/all/k/kong/${CE_PKG}" \ 47 | -o ${CE_PKG} 48 | dpkg -i ${CE_PKG} 49 | fi 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 85 | # kong.conf, Kong configuration file 86 | # Written by Dennis Kelly 87 | # Updated by Dennis Kelly 88 | # 89 | # 2020-01-23: Support for EE Kong Manager Auth 90 | # 2019-09-30: Support for 1.x releases and Dev Portal 91 | # 2018-03-13: Support for 0.12 and load balancing 92 | # 2017-06-20: Initial release 93 | # 94 | # Notes: 95 | # - See kong.conf.default for further information 96 | 97 | # Database settings 98 | database = postgres 99 | pg_host = $DB_HOST 100 | pg_user = ${DB_USER} 101 | pg_password = $DB_PASSWORD 102 | pg_database = $DB_NAME 103 | 104 | # Load balancer headers 105 | real_ip_header = X-Forwarded-For 106 | trusted_ips = 0.0.0.0/0 107 | 108 | # SSL terminiation is performed by load balancers 109 | proxy_listen = 0.0.0.0:8000 110 | # For /status to load balancers 111 | admin_listen = 0.0.0.0:8001 112 | EOF 113 | chmod 640 /etc/kong/kong.conf 114 | chgrp kong /etc/kong/kong.conf 115 | 116 | if [ "$EE_LICENSE" != "placeholder" ]; then 117 | cat <> /etc/kong/kong.conf 118 | 119 | # Enterprise Edition Settings 120 | # SSL terminiation is performed by load balancers 121 | admin_gui_listen = 0.0.0.0:8002 122 | portal_gui_listen = 0.0.0.0:8003 123 | portal_api_listen = 0.0.0.0:8004 124 | 125 | admin_api_uri = https://${MANAGER_HOST}:8444 126 | admin_gui_url = https://${MANAGER_HOST}:8445 127 | 128 | portal = on 129 | portal_gui_protocol = https 130 | portal_gui_host = ${PORTAL_HOST}:8446 131 | portal_api_url = http://${PORTAL_HOST}:8447 132 | portal_cors_origins = https://${PORTAL_HOST}:8446, https://${PORTAL_HOST}:8447 133 | 134 | vitals = on 135 | EOF 136 | 137 | for DIR in gui lib portal; do 138 | chown -R kong:kong /usr/local/kong/$DIR 139 | done 140 | else 141 | # CE does not create the kong directory 142 | mkdir -p /usr/local/kong 143 | fi 144 | 145 | chown root:kong /usr/local/kong 146 | chmod 2775 /usr/local/kong 147 | 148 | # Initialize Kong 149 | echo "Initializing Kong" 150 | if [ "$EE_LICENSE" != "placeholder" ]; then 151 | ADMIN_TOKEN=$(aws_get_parameter "ee/admin/token") 152 | sudo -u kong KONG_PASSWORD=$ADMIN_TOKEN kong migrations bootstrap 153 | else 154 | sudo -u kong kong migrations bootstrap 155 | fi 156 | 157 | cat <<'EOF' > /usr/local/kong/nginx.conf 158 | worker_processes auto; 159 | daemon off; 160 | 161 | pid pids/nginx.pid; 162 | error_log logs/error.log notice; 163 | 164 | worker_rlimit_nofile 65536; 165 | 166 | events { 167 | worker_connections 8192; 168 | multi_accept on; 169 | } 170 | 171 | http { 172 | include nginx-kong.conf; 173 | } 174 | EOF 175 | chown root:kong /usr/local/kong/nginx.conf 176 | 177 | # Log rotation 178 | cat <<'EOF' > /etc/logrotate.d/kong 179 | /usr/local/kong/logs/*.log { 180 | rotate 14 181 | daily 182 | compress 183 | missingok 184 | notifempty 185 | create 640 kong kong 186 | sharedscripts 187 | 188 | postrotate 189 | /usr/bin/sv 1 /etc/sv/kong 190 | endscript 191 | } 192 | EOF 193 | 194 | # Additional environment variables 195 | # cat <> /etc/environment 196 | # DECK_REDIS_HOST=$(aws_get_parameter redis/primary-endpoint) 197 | # EOF 198 | 199 | # Start Kong under supervision 200 | echo "Starting Kong under supervision" 201 | mkdir -p /etc/sv/kong /etc/sv/kong/log 202 | 203 | cat <<'EOF' > /etc/sv/kong/run 204 | #!/bin/sh -e 205 | exec 2>&1 206 | 207 | ulimit -n 65536 208 | sudo -u kong kong prepare 209 | exec chpst -u kong /usr/local/openresty/nginx/sbin/nginx -p /usr/local/kong -c nginx.conf 210 | EOF 211 | 212 | cat <<'EOF' > /etc/sv/kong/log/run 213 | #!/bin/sh -e 214 | 215 | [ -d /var/log/kong ] || mkdir -p /var/log/kong 216 | chown kong:kong /var/log/kong 217 | 218 | exec chpst -u kong /usr/bin/svlogd -tt /var/log/kong 219 | EOF 220 | chmod 744 /etc/sv/kong/run /etc/sv/kong/log/run 221 | 222 | cd /etc/service 223 | ln -s /etc/sv/kong 224 | 225 | # Verify Admin API is up 226 | RUNNING=0 227 | for I in 1 2 3 4 5 6 7 8 9; do 228 | curl -s -I http://localhost:8001/status | grep -q "200 OK" 229 | if [ $? = 0 ]; then 230 | RUNNING=1 231 | break 232 | fi 233 | sleep 1 234 | done 235 | 236 | if [ $RUNNING = 0 ]; then 237 | echo "Cannot connect to admin API, avoiding further configuration." 238 | exit 1 239 | fi 240 | 241 | # Enable healthchecks using a kong endpoint 242 | curl -s -I http://localhost:8000/status | grep -q "200 OK" 243 | if [ $? != 0 ]; then 244 | echo "Configuring healthcheck" 245 | curl -s -X POST http://localhost:8001/services \ 246 | -d name=status \ 247 | -d host=localhost \ 248 | -d port=8001 \ 249 | -d path=/status > /dev/null 250 | curl -s -X POST http://localhost:8001/services/status/routes \ 251 | -d name=status \ 252 | -d 'methods[]=HEAD' \ 253 | -d 'methods[]=GET' \ 254 | -d 'paths[]=/status' > /dev/null 255 | curl -s -X POST http://localhost:8001/services/status/plugins \ 256 | -d name=ip-restriction \ 257 | -d "config.whitelist=127.0.0.1" \ 258 | -d "config.whitelist=${VPC_CIDR_BLOCK}" > /dev/null 259 | fi 260 | 261 | if [ "$EE_LICENSE" != "placeholder" ]; then 262 | echo "Configuring enterprise edition settings" 263 | 264 | # Monitor role, endpoints, user, for healthcheck 265 | curl -s -X GET -I http://localhost:8001/rbac/roles/monitor | grep -q "200 OK" 266 | if [ $? != 0 ]; then 267 | COMMENT="Load balancer access to /status" 268 | 269 | curl -s -X POST http://localhost:8001/rbac/roles \ 270 | -d name=monitor \ 271 | -d comment="$COMMENT" > /dev/null 272 | curl -s -X POST http://localhost:8001/rbac/roles/monitor/endpoints \ 273 | -d endpoint=/status -d actions=read \ 274 | -d comment="$COMMENT" > /dev/null 275 | curl -s -X POST http://localhost:8001/rbac/users \ 276 | -d name=monitor -d user_token=monitor \ 277 | -d comment="$COMMENT" > /dev/null 278 | curl -s -X POST http://localhost:8001/rbac/users/monitor/roles \ 279 | -d roles=monitor > /dev/null 280 | 281 | # Add authentication token for /status 282 | curl -s -X POST http://localhost:8001/services/status/plugins \ 283 | -d name=request-transformer \ 284 | -d 'config.add.headers[]=Kong-Admin-Token:monitor' > /dev/null 285 | fi 286 | 287 | sv stop /etc/sv/kong 288 | cat <> /etc/kong/kong.conf 289 | enforce_rbac = on 290 | admin_gui_auth = basic-auth 291 | admin_gui_session_conf = { "secret":"${SESSION_SECRET}", "cookie_secure":false } 292 | EOF 293 | 294 | sv start /etc/sv/kong 295 | fi 296 | -------------------------------------------------------------------------------- /cloud-init.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | render_variables = { 3 | DB_USER = replace(format("%s_%s", var.service, var.environment), "-", "_") 4 | CE_PKG = var.ce_pkg 5 | EE_PKG = var.ee_pkg 6 | PARAMETER_PATH = format("/%s/%s", var.service, var.environment) 7 | REGION = data.aws_region.current.name 8 | VPC_CIDR_BLOCK = data.aws_vpc.vpc.cidr_block 9 | DECK_VERSION = var.deck_version 10 | MANAGER_HOST = local.manager_host 11 | PORTAL_HOST = local.portal_host 12 | SESSION_SECRET = random_string.session_secret.result 13 | } 14 | } 15 | 16 | data "cloudinit_config" "cloud-init" { 17 | gzip = true 18 | base64_encode = true 19 | 20 | part { 21 | filename = "init.cfg" 22 | content_type = "text/cloud-config" 23 | content = file("${path.module}/cloud-init.cfg") 24 | } 25 | 26 | part { 27 | content_type = "text/x-shellscript" 28 | content = templatefile("${path.module}/cloud-init.sh", local.render_variables) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /cw.tf: -------------------------------------------------------------------------------- 1 | module "kong_external_lb_cw" { 2 | source = "./cw/lb" 3 | 4 | enable = var.enable_external_lb 5 | load_balancer = coalesce(join("", aws_lb.external.*.arn_suffix), "none") 6 | target_group = coalesce(join("", aws_lb_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_lb_cw" { 14 | source = "./cw/lb" 15 | 16 | enable = var.enable_internal_lb 17 | load_balancer = coalesce(join("", aws_lb.internal.*.arn_suffix), "none") 18 | target_group = coalesce(join("", aws_lb_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 | -------------------------------------------------------------------------------- /cw/lb/main.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | load_balancer = element(split("/", var.load_balancer), 1) 3 | } 4 | 5 | resource "aws_cloudwatch_metric_alarm" "unhealthy-host-count" { 6 | count = var.enable ? 1 : 0 7 | 8 | alarm_name = format("%s-unhealthy-host-count", local.load_balancer) 9 | comparison_operator = "GreaterThanThreshold" 10 | evaluation_periods = 1 11 | metric_name = "UnHealthyHostCount" 12 | namespace = "AWS/ApplicationELB" 13 | period = 60 14 | statistic = "Minimum" 15 | threshold = 0 16 | 17 | actions_enabled = "true" 18 | alarm_actions = var.cloudwatch_actions 19 | alarm_description = format("Unhealthy host count is greater than 0 for %s", local.load_balancer) 20 | ok_actions = var.cloudwatch_actions 21 | 22 | dimensions = { 23 | "TargetGroup" = element(split(":", var.target_group), 5) 24 | "LoadBalancer" = var.load_balancer 25 | } 26 | } 27 | 28 | resource "aws_cloudwatch_metric_alarm" "http-code-4xx-count" { 29 | count = var.enable ? 1 : 0 30 | 31 | alarm_name = format("%s-http-code-4xx-count", local.load_balancer) 32 | comparison_operator = "GreaterThanThreshold" 33 | evaluation_periods = 1 34 | metric_name = "HTTPCode_Target_4XX_Count" 35 | namespace = "AWS/ApplicationELB" 36 | period = 60 37 | statistic = "Minimum" 38 | threshold = var.http_4xx_count 39 | treat_missing_data = "ignore" 40 | 41 | actions_enabled = "true" 42 | alarm_actions = var.cloudwatch_actions 43 | alarm_description = format("HTTP Code 4xx count is greater than %s for %s", var.http_4xx_count, local.load_balancer) 44 | ok_actions = var.cloudwatch_actions 45 | 46 | dimensions = { 47 | LoadBalancer = var.load_balancer 48 | } 49 | } 50 | 51 | resource "aws_cloudwatch_metric_alarm" "http-code-5xx-count" { 52 | count = var.enable ? 1 : 0 53 | 54 | alarm_name = format("%s-http-code-5xx-count", local.load_balancer) 55 | comparison_operator = "GreaterThanThreshold" 56 | evaluation_periods = 1 57 | metric_name = "HTTPCode_Target_5XX_Count" 58 | namespace = "AWS/ApplicationELB" 59 | period = 60 60 | statistic = "Minimum" 61 | threshold = var.http_5xx_count 62 | treat_missing_data = "ignore" 63 | 64 | actions_enabled = "true" 65 | alarm_actions = var.cloudwatch_actions 66 | alarm_description = format("HTTP Code 5xx count is greater than %s for %s", var.http_5xx_count, local.load_balancer) 67 | ok_actions = var.cloudwatch_actions 68 | 69 | dimensions = { 70 | LoadBalancer = var.load_balancer 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /cw/lb/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(string) 14 | } 15 | 16 | variable "enable" { 17 | description = "Boolean to enable cloudwatch metrics" 18 | type = bool 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 | -------------------------------------------------------------------------------- /data.tf: -------------------------------------------------------------------------------- 1 | # AWS Data 2 | data "aws_vpc" "vpc" { 3 | state = "available" 4 | 5 | filter { 6 | name = "vpc-id" 7 | values = [var.vpc_id] 8 | } 9 | } 10 | 11 | data "aws_region" "current" {} 12 | 13 | data "aws_subnets" "public" { 14 | filter { 15 | name = "vpc-id" 16 | values = [data.aws_vpc.vpc.id] 17 | } 18 | 19 | filter { 20 | name = "tag:${var.subnet_tag}" 21 | values = [var.public_subnets] 22 | } 23 | } 24 | 25 | data "aws_subnets" "private" { 26 | filter { 27 | name = "vpc-id" 28 | values = [data.aws_vpc.vpc.id] 29 | } 30 | 31 | filter { 32 | name = "tag:${var.subnet_tag}" 33 | values = [var.private_subnets] 34 | } 35 | } 36 | 37 | data "aws_acm_certificate" "external-cert" { 38 | count = var.enable_external_lb ? 1 : 0 39 | domain = var.ssl_cert_external 40 | } 41 | 42 | data "aws_acm_certificate" "internal-cert" { 43 | count = var.enable_external_lb ? 1 : 0 44 | domain = var.ssl_cert_internal 45 | } 46 | 47 | data "aws_acm_certificate" "admin-cert" { 48 | count = var.enable_external_lb ? 1 : 0 49 | domain = var.ssl_cert_admin 50 | } 51 | 52 | data "aws_acm_certificate" "manager-cert" { 53 | count = var.enable_external_lb ? 1 : 0 54 | domain = var.ssl_cert_manager 55 | } 56 | 57 | data "aws_acm_certificate" "portal-cert" { 58 | count = var.enable_external_lb ? 1 : 0 59 | domain = var.ssl_cert_portal 60 | } 61 | -------------------------------------------------------------------------------- /ec2.tf: -------------------------------------------------------------------------------- 1 | resource "aws_launch_configuration" "kong" { 2 | name_prefix = format("%s-%s-", var.service, var.environment) 3 | image_id = var.ec2_ami[data.aws_region.current.name] 4 | instance_type = var.ec2_instance_type 5 | iam_instance_profile = aws_iam_instance_profile.kong.name 6 | key_name = var.ec2_key_name 7 | 8 | security_groups = setunion( 9 | [aws_security_group.kong.id], 10 | var.additional_security_groups, 11 | ) 12 | 13 | associate_public_ip_address = false 14 | enable_monitoring = true 15 | placement_tenancy = "default" 16 | user_data = data.cloudinit_config.cloud-init.rendered 17 | 18 | root_block_device { 19 | volume_size = var.ec2_root_volume_size 20 | volume_type = var.ec2_root_volume_type 21 | } 22 | 23 | lifecycle { 24 | create_before_destroy = true 25 | } 26 | } 27 | 28 | resource "aws_autoscaling_group" "kong" { 29 | name = format("%s-%s", var.service, var.environment) 30 | vpc_zone_identifier = data.aws_subnets.private.ids 31 | 32 | launch_configuration = aws_launch_configuration.kong.name 33 | 34 | desired_capacity = var.asg_desired_capacity 35 | force_delete = false 36 | health_check_grace_period = var.asg_health_check_grace_period 37 | health_check_type = "ELB" 38 | max_size = var.asg_max_size 39 | min_size = var.asg_min_size 40 | 41 | target_group_arns = compact( 42 | concat( 43 | aws_lb_target_group.external.*.arn, 44 | aws_lb_target_group.internal.*.arn, 45 | aws_lb_target_group.admin.*.arn, 46 | aws_lb_target_group.manager.*.arn, 47 | aws_lb_target_group.portal-gui.*.arn, 48 | aws_lb_target_group.portal.*.arn 49 | ) 50 | ) 51 | 52 | tag { 53 | key = "Name" 54 | value = format("%s-%s", var.service, var.environment) 55 | propagate_at_launch = true 56 | } 57 | tag { 58 | key = "Environment" 59 | value = var.environment 60 | propagate_at_launch = true 61 | } 62 | tag { 63 | key = "Description" 64 | value = var.description 65 | propagate_at_launch = true 66 | } 67 | tag { 68 | key = "Service" 69 | value = var.service 70 | propagate_at_launch = true 71 | } 72 | 73 | dynamic "tag" { 74 | for_each = var.tags 75 | 76 | content { 77 | key = tag.key 78 | value = tag.value 79 | propagate_at_launch = true 80 | } 81 | } 82 | 83 | depends_on = [ 84 | aws_db_instance.kong, 85 | aws_rds_cluster.kong, 86 | aws_elasticache_replication_group.kong 87 | ] 88 | } 89 | -------------------------------------------------------------------------------- /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 = format("%s-%s-ssm", var.service, var.environment) 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 | identifiers = ["ec2.amazonaws.com"] 32 | } 33 | } 34 | } 35 | 36 | resource "aws_iam_role" "kong" { 37 | name = format("%s-%s", var.service, var.environment) 38 | assume_role_policy = data.aws_iam_policy_document.kong.json 39 | } 40 | 41 | resource "aws_iam_instance_profile" "kong" { 42 | name = format("%s-%s", var.service, var.environment) 43 | role = aws_iam_role.kong.id 44 | } 45 | -------------------------------------------------------------------------------- /lb.tf: -------------------------------------------------------------------------------- 1 | # External - HTTPS only 2 | resource "aws_lb_target_group" "external" { 3 | count = var.enable_external_lb ? 1 : 0 4 | 5 | name = format("%s-%s-external", var.service, var.environment) 6 | port = 8000 7 | protocol = "HTTP" 8 | vpc_id = data.aws_vpc.vpc.id 9 | 10 | health_check { 11 | healthy_threshold = var.health_check_healthy_threshold 12 | interval = var.health_check_interval 13 | path = "/status" 14 | port = 8000 15 | timeout = var.health_check_timeout 16 | unhealthy_threshold = var.health_check_unhealthy_threshold 17 | } 18 | 19 | tags = merge( 20 | { 21 | "Name" = format("%s-%s-external", var.service, var.environment), 22 | "Environment" = var.environment, 23 | "Description" = var.description, 24 | "Service" = var.service, 25 | }, 26 | var.tags 27 | ) 28 | } 29 | 30 | resource "aws_lb" "external" { 31 | count = var.enable_external_lb ? 1 : 0 32 | 33 | name = format("%s-%s-external", var.service, var.environment) 34 | internal = false 35 | subnets = data.aws_subnets.public.ids 36 | 37 | security_groups = [aws_security_group.external-lb.id] 38 | 39 | enable_deletion_protection = var.enable_deletion_protection 40 | idle_timeout = var.idle_timeout 41 | 42 | tags = merge( 43 | { 44 | "Name" = format("%s-%s-external", var.service, var.environment), 45 | "Environment" = var.environment, 46 | "Description" = var.description, 47 | "Service" = var.service, 48 | }, 49 | var.tags 50 | ) 51 | } 52 | 53 | resource "aws_lb_listener" "external-https" { 54 | count = var.enable_external_lb ? 1 : 0 55 | 56 | load_balancer_arn = aws_lb.external[0].arn 57 | port = "443" 58 | protocol = "HTTPS" 59 | 60 | ssl_policy = var.ssl_policy 61 | certificate_arn = data.aws_acm_certificate.external-cert[0].arn 62 | 63 | default_action { 64 | target_group_arn = aws_lb_target_group.external[0].arn 65 | type = "forward" 66 | } 67 | } 68 | 69 | # Internal 70 | resource "aws_lb_target_group" "internal" { 71 | count = var.enable_internal_lb ? 1 : 0 72 | 73 | name = format("%s-%s-internal", var.service, var.environment) 74 | port = 8000 75 | protocol = "HTTP" 76 | vpc_id = data.aws_vpc.vpc.id 77 | 78 | health_check { 79 | healthy_threshold = var.health_check_healthy_threshold 80 | interval = var.health_check_interval 81 | path = "/status" 82 | port = 8000 83 | timeout = var.health_check_timeout 84 | unhealthy_threshold = var.health_check_unhealthy_threshold 85 | } 86 | 87 | tags = merge( 88 | { 89 | "Name" = format("%s-%s-internal", var.service, var.environment), 90 | "Environment" = var.environment, 91 | "Description" = var.description, 92 | "Service" = var.service, 93 | }, 94 | var.tags 95 | ) 96 | } 97 | 98 | resource "aws_lb_target_group" "admin" { 99 | count = var.enable_ee ? 1 : 0 100 | 101 | name = format("%s-%s-admin", var.service, var.environment) 102 | port = 8001 103 | protocol = "HTTP" 104 | vpc_id = data.aws_vpc.vpc.id 105 | 106 | health_check { 107 | healthy_threshold = var.health_check_healthy_threshold 108 | interval = var.health_check_interval 109 | path = "/status" 110 | port = 8000 111 | timeout = var.health_check_timeout 112 | unhealthy_threshold = var.health_check_unhealthy_threshold 113 | } 114 | 115 | tags = merge( 116 | { 117 | "Name" = format("%s-%s-admin", var.service, var.environment), 118 | "Environment" = var.environment, 119 | "Description" = var.description, 120 | "Service" = var.service, 121 | }, 122 | var.tags 123 | ) 124 | } 125 | 126 | resource "aws_lb_target_group" "manager" { 127 | count = var.enable_ee ? 1 : 0 128 | 129 | name = format("%s-%s-manager", var.service, var.environment) 130 | port = 8002 131 | protocol = "HTTP" 132 | vpc_id = data.aws_vpc.vpc.id 133 | 134 | health_check { 135 | healthy_threshold = var.health_check_healthy_threshold 136 | interval = var.health_check_interval 137 | path = "/status" 138 | port = 8000 139 | timeout = var.health_check_timeout 140 | unhealthy_threshold = var.health_check_unhealthy_threshold 141 | } 142 | 143 | tags = merge( 144 | { 145 | "Name" = format("%s-%s-manager", var.service, var.environment), 146 | "Environment" = var.environment, 147 | "Description" = var.description, 148 | "Service" = var.service, 149 | }, 150 | var.tags 151 | ) 152 | } 153 | 154 | resource "aws_lb_target_group" "portal-gui" { 155 | count = var.enable_ee ? 1 : 0 156 | 157 | name = format("%s-%s-porter-gui", var.service, var.environment) 158 | port = 8003 159 | protocol = "HTTP" 160 | vpc_id = data.aws_vpc.vpc.id 161 | 162 | health_check { 163 | healthy_threshold = var.health_check_healthy_threshold 164 | interval = var.health_check_interval 165 | path = "/status" 166 | port = 8000 167 | timeout = var.health_check_timeout 168 | unhealthy_threshold = var.health_check_unhealthy_threshold 169 | } 170 | 171 | tags = merge( 172 | { 173 | "Name" = format("%s-%s-porter-gui", var.service, var.environment), 174 | "Environment" = var.environment, 175 | "Description" = var.description, 176 | "Service" = var.service, 177 | }, 178 | var.tags 179 | ) 180 | } 181 | 182 | resource "aws_lb_target_group" "portal" { 183 | count = var.enable_ee ? 1 : 0 184 | 185 | name = format("%s-%s-portal", var.service, var.environment) 186 | port = 8004 187 | protocol = "HTTP" 188 | vpc_id = data.aws_vpc.vpc.id 189 | 190 | health_check { 191 | healthy_threshold = var.health_check_healthy_threshold 192 | interval = var.health_check_interval 193 | path = "/status" 194 | port = 8000 195 | timeout = var.health_check_timeout 196 | unhealthy_threshold = var.health_check_unhealthy_threshold 197 | } 198 | 199 | tags = merge( 200 | { 201 | "Name" = format("%s-%s-portal", var.service, var.environment), 202 | "Environment" = var.environment, 203 | "Description" = var.description, 204 | "Service" = var.service, 205 | }, 206 | var.tags 207 | ) 208 | } 209 | 210 | resource "aws_lb" "internal" { 211 | count = var.enable_internal_lb ? 1 : 0 212 | 213 | name = format("%s-%s-internal", var.service, var.environment) 214 | internal = true 215 | subnets = data.aws_subnets.private.ids 216 | 217 | security_groups = [aws_security_group.internal-lb.id] 218 | 219 | enable_deletion_protection = var.enable_deletion_protection 220 | idle_timeout = var.idle_timeout 221 | 222 | tags = merge( 223 | { 224 | "Name" = format("%s-%s-internal", var.service, var.environment), 225 | "Environment" = var.environment, 226 | "Description" = var.description, 227 | "Service" = var.service, 228 | }, 229 | var.tags 230 | ) 231 | } 232 | 233 | resource "aws_lb_listener" "internal-http" { 234 | count = var.enable_internal_lb ? 1 : 0 235 | 236 | load_balancer_arn = aws_lb.internal[0].arn 237 | port = 80 238 | protocol = "HTTP" 239 | 240 | default_action { 241 | target_group_arn = aws_lb_target_group.internal[0].arn 242 | type = "forward" 243 | } 244 | } 245 | 246 | resource "aws_lb_listener" "internal-https" { 247 | count = var.enable_internal_lb ? 1 : 0 248 | 249 | load_balancer_arn = aws_lb.internal[0].arn 250 | port = 443 251 | protocol = "HTTPS" 252 | 253 | ssl_policy = var.ssl_policy 254 | certificate_arn = data.aws_acm_certificate.internal-cert[0].arn 255 | 256 | default_action { 257 | target_group_arn = aws_lb_target_group.internal[0].arn 258 | type = "forward" 259 | } 260 | } 261 | 262 | resource "aws_lb_listener" "admin" { 263 | count = var.enable_ee ? 1 : 0 264 | 265 | load_balancer_arn = aws_lb.internal[0].arn 266 | port = 8444 267 | protocol = "HTTPS" 268 | 269 | ssl_policy = var.ssl_policy 270 | certificate_arn = data.aws_acm_certificate.internal-cert[0].arn 271 | 272 | default_action { 273 | target_group_arn = aws_lb_target_group.admin[0].arn 274 | type = "forward" 275 | } 276 | } 277 | 278 | resource "aws_lb_listener" "manager" { 279 | count = var.enable_ee ? 1 : 0 280 | 281 | load_balancer_arn = aws_lb.internal[0].arn 282 | port = 8445 283 | protocol = "HTTPS" 284 | 285 | ssl_policy = var.ssl_policy 286 | certificate_arn = data.aws_acm_certificate.internal-cert[0].arn 287 | 288 | default_action { 289 | target_group_arn = aws_lb_target_group.manager[0].arn 290 | type = "forward" 291 | } 292 | } 293 | 294 | resource "aws_lb_listener" "portal-gui" { 295 | count = var.enable_ee ? 1 : 0 296 | 297 | load_balancer_arn = aws_lb.internal[0].arn 298 | port = 8446 299 | protocol = "HTTPS" 300 | 301 | ssl_policy = var.ssl_policy 302 | certificate_arn = data.aws_acm_certificate.internal-cert[0].arn 303 | 304 | default_action { 305 | target_group_arn = aws_lb_target_group.portal-gui[0].arn 306 | type = "forward" 307 | } 308 | } 309 | 310 | resource "aws_lb_listener" "portal" { 311 | count = var.enable_ee ? 1 : 0 312 | 313 | load_balancer_arn = aws_lb.internal[0].arn 314 | port = 8447 315 | protocol = "HTTPS" 316 | 317 | ssl_policy = var.ssl_policy 318 | certificate_arn = data.aws_acm_certificate.internal-cert[0].arn 319 | 320 | default_action { 321 | target_group_arn = aws_lb_target_group.portal[0].arn 322 | type = "forward" 323 | } 324 | } 325 | -------------------------------------------------------------------------------- /locals.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | enable_rds = var.enable_aurora ? false : true 3 | 4 | manager_host = var.manager_host == "default" ? join("", aws_lb.internal.*.dns_name) : var.manager_host 5 | portal_host = var.portal_host == "default" ? join("", aws_lb.internal.*.dns_name) : var.portal_host 6 | } 7 | -------------------------------------------------------------------------------- /outputs.tf: -------------------------------------------------------------------------------- 1 | output "rds_endpoint" { 2 | value = coalesce(aws_rds_cluster.kong.*.endpoint) 3 | description = "The endpoint for the Kong database" 4 | } 5 | 6 | output "rds_password" { 7 | sensitive = true 8 | value = random_string.db_password.result 9 | description = "The database password for Kong" 10 | } 11 | 12 | output "master_password" { 13 | sensitive = true 14 | value = random_string.master_password.result 15 | description = "The master password for Kong" 16 | } 17 | 18 | output "admin_token" { 19 | sensitive = true 20 | value = random_string.admin_token.result 21 | description = "The admin token for Kong" 22 | } 23 | 24 | output "lb_endpoint_external" { 25 | value = coalesce(aws_lb.external.*.dns_name) 26 | description = "The external load balancer endpoint" 27 | } 28 | 29 | output "lb_endpoint_internal" { 30 | value = coalesce(aws_lb.internal.*.dns_name) 31 | description = "The internal load balancer endpoint" 32 | } 33 | -------------------------------------------------------------------------------- /passwords.tf: -------------------------------------------------------------------------------- 1 | resource "random_string" "master_password" { 2 | length = 32 3 | special = false 4 | } 5 | 6 | resource "random_string" "db_password" { 7 | length = 32 8 | special = false 9 | } 10 | 11 | resource "random_string" "admin_token" { 12 | length = 32 13 | special = false 14 | } 15 | 16 | resource "random_string" "session_secret" { 17 | length = 32 18 | special = false 19 | } 20 | -------------------------------------------------------------------------------- /rds.tf: -------------------------------------------------------------------------------- 1 | resource "aws_db_instance" "kong" { 2 | count = local.enable_rds ? 1 : 0 3 | identifier = format("%s-%s", var.service, var.environment) 4 | 5 | 6 | engine = "postgres" 7 | engine_version = var.db_engine_version 8 | instance_class = var.db_instance_class 9 | allocated_storage = var.db_storage_size 10 | storage_type = "gp2" 11 | 12 | backup_retention_period = var.db_backup_retention_period 13 | db_subnet_group_name = var.db_subnets 14 | multi_az = var.db_multi_az 15 | parameter_group_name = var.db_instance_count > 0 ? aws_db_parameter_group.kong[0].name : format("%s-%s", var.service, var.environment) 16 | 17 | 18 | username = "root" 19 | password = random_string.master_password.result 20 | 21 | vpc_security_group_ids = [aws_security_group.postgresql.id] 22 | 23 | skip_final_snapshot = var.db_final_snapshot_identifier == "" ? true : false 24 | final_snapshot_identifier = var.db_final_snapshot_identifier == "" ? null : var.db_final_snapshot_identifier 25 | 26 | tags = merge( 27 | { 28 | "Name" = format("%s-%s", var.service, var.environment), 29 | "Environment" = var.environment, 30 | "Description" = var.description, 31 | "Service" = var.service, 32 | }, 33 | var.tags 34 | ) 35 | } 36 | 37 | resource "aws_db_parameter_group" "kong" { 38 | count = var.db_instance_count > 0 ? 1 : 0 39 | 40 | name = format("%s-%s", var.service, var.environment) 41 | family = var.db_family 42 | description = var.description 43 | 44 | tags = merge( 45 | { 46 | "Name" = format("%s-%s", var.service, var.environment), 47 | "Environment" = var.environment, 48 | "Description" = var.description, 49 | "Service" = var.service, 50 | }, 51 | var.tags 52 | ) 53 | } 54 | -------------------------------------------------------------------------------- /redis.tf: -------------------------------------------------------------------------------- 1 | resource "aws_elasticache_replication_group" "kong" { 2 | count = var.enable_redis ? 1 : 0 3 | 4 | description = var.description 5 | replication_group_id = format("%s-%s", var.service, var.environment) 6 | 7 | engine = "redis" 8 | engine_version = var.redis_engine_version 9 | node_type = var.redis_instance_type 10 | num_cache_clusters = var.redis_instance_count 11 | parameter_group_name = format("%s-%s", 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 | { 19 | "Name" = format("%s-%s", var.service, var.environment), 20 | "Environment" = var.environment, 21 | "Description" = var.description, 22 | "Service" = var.service, 23 | }, 24 | var.tags 25 | ) 26 | depends_on = [aws_elasticache_parameter_group.kong] 27 | } 28 | 29 | resource "aws_elasticache_parameter_group" "kong" { 30 | name = format("%s-%s", var.service, var.environment) 31 | family = var.redis_family 32 | 33 | description = var.description 34 | } 35 | -------------------------------------------------------------------------------- /security.tf: -------------------------------------------------------------------------------- 1 | # PostgreSQL security group 2 | resource "aws_security_group" "postgresql" { 3 | description = "Kong RDS instance" 4 | name = format("%s-%s-postgresql", var.service, var.environment) 5 | vpc_id = data.aws_vpc.vpc.id 6 | 7 | tags = merge( 8 | { 9 | "Name" = format("%s-%s-postgresql", var.service, var.environment), 10 | "Environment" = var.environment, 11 | "Description" = var.description, 12 | "Service" = var.service, 13 | }, 14 | var.tags 15 | ) 16 | } 17 | 18 | resource "aws_security_group_rule" "postgresql-ingress-kong" { 19 | security_group_id = aws_security_group.postgresql.id 20 | 21 | type = "ingress" 22 | from_port = 5432 23 | to_port = 5432 24 | protocol = "tcp" 25 | 26 | source_security_group_id = aws_security_group.kong.id 27 | } 28 | 29 | resource "aws_security_group_rule" "postgresql-ingress-bastion" { 30 | security_group_id = aws_security_group.postgresql.id 31 | 32 | type = "ingress" 33 | from_port = 5432 34 | to_port = 5432 35 | protocol = "tcp" 36 | 37 | cidr_blocks = var.bastion_cidr_blocks 38 | } 39 | 40 | # Redis security group 41 | resource "aws_security_group" "redis" { 42 | description = "Kong redis cluster" 43 | name = format("%s-%s-redis", var.service, var.environment) 44 | vpc_id = data.aws_vpc.vpc.id 45 | 46 | tags = merge( 47 | { 48 | "Name" = format("%s-%s-redis", var.service, var.environment), 49 | "Environment" = var.environment, 50 | "Description" = var.description, 51 | "Service" = var.service, 52 | }, 53 | var.tags 54 | ) 55 | } 56 | 57 | resource "aws_security_group_rule" "redis-ingress-kong" { 58 | security_group_id = aws_security_group.redis.id 59 | 60 | type = "ingress" 61 | from_port = 6379 62 | to_port = 6379 63 | protocol = "tcp" 64 | 65 | source_security_group_id = aws_security_group.kong.id 66 | } 67 | 68 | resource "aws_security_group_rule" "redis-ingress-bastion" { 69 | security_group_id = aws_security_group.redis.id 70 | 71 | type = "ingress" 72 | from_port = 6379 73 | to_port = 6379 74 | protocol = "tcp" 75 | 76 | cidr_blocks = var.bastion_cidr_blocks 77 | } 78 | 79 | # Kong node security group and rules 80 | resource "aws_security_group" "kong" { 81 | description = "Kong EC2 instances" 82 | name = format("%s-%s", var.service, var.environment) 83 | vpc_id = data.aws_vpc.vpc.id 84 | 85 | tags = merge( 86 | { 87 | "Name" = format("%s-%s", var.service, var.environment), 88 | "Environment" = var.environment, 89 | "Description" = var.description, 90 | "Service" = var.service, 91 | }, 92 | var.tags 93 | ) 94 | } 95 | 96 | resource "aws_security_group_rule" "admin-ingress-bastion" { 97 | security_group_id = aws_security_group.kong.id 98 | 99 | type = "ingress" 100 | from_port = 8001 101 | to_port = 8001 102 | protocol = "tcp" 103 | 104 | cidr_blocks = var.bastion_cidr_blocks 105 | } 106 | 107 | # External load balancer access 108 | resource "aws_security_group_rule" "proxy-ingress-external-lb" { 109 | security_group_id = aws_security_group.kong.id 110 | 111 | type = "ingress" 112 | from_port = 8000 113 | to_port = 8000 114 | protocol = "tcp" 115 | 116 | source_security_group_id = aws_security_group.external-lb.id 117 | } 118 | 119 | resource "aws_security_group_rule" "admin-ingress-external-lb" { 120 | security_group_id = aws_security_group.kong.id 121 | 122 | type = "ingress" 123 | from_port = 8001 124 | to_port = 8001 125 | protocol = "tcp" 126 | 127 | source_security_group_id = aws_security_group.external-lb.id 128 | } 129 | 130 | # Internal load balancer access 131 | resource "aws_security_group_rule" "proxy-ingress-internal-lb" { 132 | security_group_id = aws_security_group.kong.id 133 | 134 | type = "ingress" 135 | from_port = 8000 136 | to_port = 8000 137 | protocol = "tcp" 138 | 139 | source_security_group_id = aws_security_group.internal-lb.id 140 | } 141 | 142 | resource "aws_security_group_rule" "admin-ingress-internal-lb" { 143 | security_group_id = aws_security_group.kong.id 144 | 145 | type = "ingress" 146 | from_port = 8001 147 | to_port = 8001 148 | protocol = "tcp" 149 | 150 | source_security_group_id = aws_security_group.internal-lb.id 151 | } 152 | 153 | resource "aws_security_group_rule" "manager-ingress-internal-lb" { 154 | count = var.enable_ee ? 1 : 0 155 | 156 | security_group_id = aws_security_group.kong.id 157 | 158 | type = "ingress" 159 | from_port = 8002 160 | to_port = 8002 161 | protocol = "tcp" 162 | 163 | source_security_group_id = aws_security_group.internal-lb.id 164 | } 165 | 166 | resource "aws_security_group_rule" "portal-gui-ingress-internal-lb" { 167 | count = var.enable_ee ? 1 : 0 168 | 169 | security_group_id = aws_security_group.kong.id 170 | 171 | type = "ingress" 172 | from_port = 8003 173 | to_port = 8003 174 | protocol = "tcp" 175 | 176 | source_security_group_id = aws_security_group.internal-lb.id 177 | } 178 | 179 | resource "aws_security_group_rule" "portal-ingress-internal-lb" { 180 | count = var.enable_ee ? 1 : 0 181 | 182 | security_group_id = aws_security_group.kong.id 183 | 184 | type = "ingress" 185 | from_port = 8004 186 | to_port = 8004 187 | protocol = "tcp" 188 | 189 | source_security_group_id = aws_security_group.internal-lb.id 190 | } 191 | 192 | # HTTP outbound for Debian packages 193 | resource "aws_security_group_rule" "kong-egress-http" { 194 | security_group_id = aws_security_group.kong.id 195 | 196 | type = "egress" 197 | from_port = 80 198 | to_port = 80 199 | protocol = "tcp" 200 | 201 | cidr_blocks = ["0.0.0.0/0"] 202 | } 203 | 204 | # HTTPS outbound for awscli, kong 205 | resource "aws_security_group_rule" "kong-egress-https" { 206 | security_group_id = aws_security_group.kong.id 207 | 208 | type = "egress" 209 | from_port = 443 210 | to_port = 443 211 | protocol = "tcp" 212 | 213 | cidr_blocks = ["0.0.0.0/0"] 214 | } 215 | 216 | # Load balancers 217 | # External 218 | resource "aws_security_group" "external-lb" { 219 | description = "Kong External Load Balancer" 220 | name = format("%s-%s-external-lb", var.service, var.environment) 221 | vpc_id = data.aws_vpc.vpc.id 222 | 223 | tags = merge( 224 | { 225 | "Name" = format("%s-%s-external-lb", var.service, var.environment), 226 | "Environment" = var.environment, 227 | "Description" = var.description, 228 | "Service" = var.service, 229 | }, 230 | var.tags 231 | ) 232 | } 233 | 234 | resource "aws_security_group_rule" "external-lb-ingress-proxy" { 235 | security_group_id = aws_security_group.external-lb.id 236 | 237 | type = "ingress" 238 | from_port = 443 239 | to_port = 443 240 | protocol = "tcp" 241 | 242 | cidr_blocks = var.external_cidr_blocks 243 | } 244 | 245 | resource "aws_security_group_rule" "external-lb-egress-proxy" { 246 | security_group_id = aws_security_group.external-lb.id 247 | 248 | type = "egress" 249 | from_port = 8000 250 | to_port = 8000 251 | protocol = "tcp" 252 | 253 | source_security_group_id = aws_security_group.kong.id 254 | } 255 | 256 | resource "aws_security_group_rule" "external-lb-egress-admin" { 257 | security_group_id = aws_security_group.external-lb.id 258 | 259 | type = "egress" 260 | from_port = 8001 261 | to_port = 8001 262 | protocol = "tcp" 263 | 264 | source_security_group_id = aws_security_group.kong.id 265 | } 266 | 267 | # Internal 268 | resource "aws_security_group" "internal-lb" { 269 | description = "Kong Internal Load Balancer" 270 | name = format("%s-%s-internal-lb", var.service, var.environment) 271 | vpc_id = data.aws_vpc.vpc.id 272 | 273 | tags = merge( 274 | { 275 | "Name" = format("%s-%s-internal-lb", var.service, var.environment), 276 | "Environment" = var.environment, 277 | "Description" = var.description, 278 | "Service" = var.service, 279 | }, 280 | var.tags 281 | ) 282 | } 283 | 284 | resource "aws_security_group_rule" "internal-lb-ingress-proxy-http" { 285 | security_group_id = aws_security_group.internal-lb.id 286 | 287 | type = "ingress" 288 | from_port = 80 289 | to_port = 80 290 | protocol = "tcp" 291 | 292 | cidr_blocks = var.internal_http_cidr_blocks 293 | } 294 | 295 | resource "aws_security_group_rule" "internal-lb-ingress-proxy-https" { 296 | security_group_id = aws_security_group.internal-lb.id 297 | 298 | type = "ingress" 299 | from_port = 443 300 | to_port = 443 301 | protocol = "tcp" 302 | 303 | cidr_blocks = var.internal_https_cidr_blocks 304 | } 305 | 306 | resource "aws_security_group_rule" "internal-lb-ingress-admin" { 307 | count = var.enable_ee ? 1 : 0 308 | 309 | security_group_id = aws_security_group.internal-lb.id 310 | 311 | type = "ingress" 312 | from_port = 8444 313 | to_port = 8444 314 | protocol = "tcp" 315 | 316 | cidr_blocks = var.admin_cidr_blocks 317 | } 318 | 319 | resource "aws_security_group_rule" "internal-lb-ingress-manager" { 320 | count = var.enable_ee ? 1 : 0 321 | 322 | security_group_id = aws_security_group.internal-lb.id 323 | 324 | type = "ingress" 325 | from_port = 8445 326 | to_port = 8445 327 | protocol = "tcp" 328 | 329 | cidr_blocks = var.manager_cidr_blocks 330 | } 331 | 332 | resource "aws_security_group_rule" "internal-lb-ingress-portal-gui" { 333 | count = var.enable_ee ? 1 : 0 334 | 335 | security_group_id = aws_security_group.internal-lb.id 336 | 337 | type = "ingress" 338 | from_port = 8446 339 | to_port = 8446 340 | protocol = "tcp" 341 | 342 | cidr_blocks = var.portal_cidr_blocks 343 | } 344 | 345 | resource "aws_security_group_rule" "internal-lb-ingress-portal" { 346 | count = var.enable_ee ? 1 : 0 347 | 348 | security_group_id = aws_security_group.internal-lb.id 349 | 350 | type = "ingress" 351 | from_port = 8447 352 | to_port = 8447 353 | protocol = "tcp" 354 | 355 | cidr_blocks = var.portal_cidr_blocks 356 | } 357 | 358 | resource "aws_security_group_rule" "internal-lb-egress-proxy" { 359 | security_group_id = aws_security_group.internal-lb.id 360 | 361 | type = "egress" 362 | from_port = 8000 363 | to_port = 8000 364 | protocol = "tcp" 365 | 366 | source_security_group_id = aws_security_group.kong.id 367 | } 368 | 369 | resource "aws_security_group_rule" "internal-lb-egress-admin" { 370 | security_group_id = aws_security_group.internal-lb.id 371 | 372 | type = "egress" 373 | from_port = 8001 374 | to_port = 8001 375 | protocol = "tcp" 376 | 377 | source_security_group_id = aws_security_group.kong.id 378 | } 379 | 380 | resource "aws_security_group_rule" "internal-lb-egress-manager" { 381 | count = var.enable_ee ? 1 : 0 382 | 383 | security_group_id = aws_security_group.internal-lb.id 384 | 385 | type = "egress" 386 | from_port = 8002 387 | to_port = 8002 388 | protocol = "tcp" 389 | 390 | source_security_group_id = aws_security_group.kong.id 391 | } 392 | 393 | resource "aws_security_group_rule" "internal-lb-egress-portal-gui" { 394 | count = var.enable_ee ? 1 : 0 395 | 396 | security_group_id = aws_security_group.internal-lb.id 397 | 398 | type = "egress" 399 | from_port = 8003 400 | to_port = 8003 401 | protocol = "tcp" 402 | 403 | source_security_group_id = aws_security_group.kong.id 404 | } 405 | 406 | resource "aws_security_group_rule" "internal-lb-egress-portal" { 407 | count = var.enable_ee ? 1 : 0 408 | 409 | security_group_id = aws_security_group.internal-lb.id 410 | 411 | type = "egress" 412 | from_port = 8004 413 | to_port = 8004 414 | protocol = "tcp" 415 | 416 | source_security_group_id = aws_security_group.kong.id 417 | } 418 | -------------------------------------------------------------------------------- /ssm.tf: -------------------------------------------------------------------------------- 1 | resource "aws_kms_key" "kong" { 2 | description = format("%s-%s", var.service, var.environment) 3 | 4 | tags = merge( 5 | { 6 | "Name" = format("%s-%s", var.service, var.environment), 7 | "Environment" = var.environment, 8 | "Description" = var.description, 9 | "Service" = var.service, 10 | }, 11 | var.tags 12 | ) 13 | } 14 | 15 | resource "aws_kms_alias" "kong" { 16 | name = format("alias/%s-%s", var.service, var.environment) 17 | target_key_id = aws_kms_key.kong.key_id 18 | } 19 | 20 | resource "aws_ssm_parameter" "ee-license" { 21 | name = format("/%s/%s/ee/license", var.service, var.environment) 22 | type = "SecureString" 23 | value = var.ee_license 24 | 25 | key_id = aws_kms_alias.kong.target_key_arn 26 | 27 | lifecycle { 28 | ignore_changes = [value] 29 | } 30 | } 31 | 32 | resource "aws_ssm_parameter" "ee-admin-token" { 33 | name = format("/%s/%s/ee/admin/token", var.service, var.environment) 34 | type = "SecureString" 35 | value = random_string.admin_token.result 36 | 37 | key_id = aws_kms_alias.kong.target_key_arn 38 | 39 | lifecycle { 40 | ignore_changes = [value] 41 | } 42 | } 43 | 44 | resource "aws_ssm_parameter" "db-host" { 45 | name = format("/%s/%s/db/host", var.service, var.environment) 46 | type = "String" 47 | value = coalesce( 48 | join("", aws_db_instance.kong.*.address), 49 | join("", aws_rds_cluster.kong.*.endpoint), 50 | "none" 51 | ) 52 | } 53 | 54 | resource "aws_ssm_parameter" "db-name" { 55 | name = format("/%s/%s/db/name", var.service, var.environment) 56 | type = "String" 57 | value = replace(format("%s_%s", var.service, var.environment), "-", "_") 58 | } 59 | 60 | resource "aws_ssm_parameter" "db-password" { 61 | name = format("/%s/%s/db/password", var.service, var.environment) 62 | type = "SecureString" 63 | value = random_string.db_password.result 64 | 65 | key_id = aws_kms_alias.kong.target_key_arn 66 | 67 | lifecycle { 68 | ignore_changes = [value] 69 | } 70 | 71 | overwrite = true 72 | } 73 | 74 | resource "aws_ssm_parameter" "db-master-password" { 75 | name = format("/%s/%s/db/password/master", var.service, var.environment) 76 | type = "SecureString" 77 | value = random_string.master_password.result 78 | 79 | key_id = aws_kms_alias.kong.target_key_arn 80 | 81 | lifecycle { 82 | ignore_changes = [value] 83 | } 84 | 85 | overwrite = true 86 | } 87 | 88 | resource "aws_ssm_parameter" "redis-primary-endpoint" { 89 | name = format("/%s/%s/redis/primary-endpoint", var.service, var.environment) 90 | type = "String" 91 | value = coalesce( 92 | join("", aws_elasticache_replication_group.kong.*.primary_endpoint_address), 93 | "none" 94 | ) 95 | } 96 | -------------------------------------------------------------------------------- /variables.tf: -------------------------------------------------------------------------------- 1 | # Network settings 2 | variable "vpc_id" { 3 | description = "VPC ID for the AWS account and region specified" 4 | type = string 5 | } 6 | 7 | variable "subnet_tag" { 8 | description = "Tag used on subnets to define Tier" 9 | type = string 10 | 11 | default = "Tier" 12 | } 13 | 14 | variable "private_subnets" { 15 | description = "Subnet tag on private subnets" 16 | type = string 17 | 18 | default = "private" 19 | } 20 | 21 | variable "public_subnets" { 22 | description = "Subnet tag on public subnets for external load balancers" 23 | type = string 24 | 25 | default = "public" 26 | } 27 | 28 | variable "additional_security_groups" { 29 | description = "IDs of the additional security groups attached to Kong EC2 instance" 30 | type = list(string) 31 | 32 | default = [] 33 | } 34 | 35 | # Access control 36 | variable "bastion_cidr_blocks" { 37 | description = "Bastion hosts allowed access to PostgreSQL and Kong Admin" 38 | type = list(string) 39 | 40 | default = [ 41 | "127.0.0.1/32", 42 | ] 43 | } 44 | 45 | variable "external_cidr_blocks" { 46 | description = "External ingress access to Kong Proxy via the load balancer" 47 | type = list(string) 48 | 49 | default = [ 50 | "0.0.0.0/0", 51 | ] 52 | } 53 | 54 | variable "internal_http_cidr_blocks" { 55 | description = "Internal ingress access to Kong Proxy via the load balancer (HTTP)" 56 | type = list(string) 57 | 58 | default = [ 59 | "0.0.0.0/0", 60 | ] 61 | } 62 | 63 | variable "internal_https_cidr_blocks" { 64 | description = "Internal ingress access to Kong Proxy via the load balancer (HTTPS)" 65 | type = list(string) 66 | 67 | default = [ 68 | "0.0.0.0/0", 69 | ] 70 | } 71 | 72 | variable "admin_cidr_blocks" { 73 | description = "Access to Kong Admin API (Enterprise Edition only)" 74 | type = list(string) 75 | 76 | default = [ 77 | "0.0.0.0/0", 78 | ] 79 | } 80 | 81 | variable "manager_cidr_blocks" { 82 | description = "Access to Kong Manager (Enterprise Edition only)" 83 | type = list(string) 84 | 85 | default = [ 86 | "0.0.0.0/0", 87 | ] 88 | } 89 | 90 | variable "portal_cidr_blocks" { 91 | description = "Access to Portal (Enterprise Edition only)" 92 | type = list(string) 93 | 94 | default = [ 95 | "0.0.0.0/0", 96 | ] 97 | } 98 | 99 | variable "manager_host" { 100 | description = "Hostname to access Kong Manager (Enterprise Edition only)" 101 | type = string 102 | 103 | default = "default" 104 | } 105 | 106 | variable "portal_host" { 107 | description = "Hostname to access Portal (Enterprise Edition only)" 108 | type = string 109 | 110 | default = "default" 111 | } 112 | 113 | # Required tags 114 | variable "description" { 115 | description = "Resource description tag" 116 | type = string 117 | 118 | default = "Kong API Gateway" 119 | } 120 | 121 | variable "environment" { 122 | description = "Resource environment tag (i.e. dev, stage, prod)" 123 | type = string 124 | } 125 | 126 | variable "service" { 127 | description = "Resource service tag" 128 | type = string 129 | 130 | default = "kong" 131 | } 132 | 133 | # Additional tags 134 | variable "tags" { 135 | description = "Tags to apply to resources" 136 | type = map(string) 137 | 138 | default = {} 139 | } 140 | 141 | # Enterprise Edition 142 | variable "enable_ee" { 143 | description = "Boolean to enable Kong Enterprise Edition settings" 144 | type = string 145 | 146 | default = false 147 | } 148 | 149 | variable "ee_license" { 150 | description = "Enterprise Edition license key (JSON format)" 151 | type = string 152 | 153 | default = "placeholder" 154 | } 155 | 156 | # EC2 settings 157 | 158 | # https://wiki.ubuntu.com/Minimal 159 | variable "ec2_ami" { 160 | description = "Map of Ubuntu Minimal AMIs by region" 161 | type = map(string) 162 | 163 | default = { 164 | us-east-1 = "ami-097f2dec72be3d174" 165 | us-east-2 = "ami-0ba142a7063a73767" 166 | us-west-1 = "ami-07b69f5dcdbb4abaf" 167 | us-west-2 = "ami-028b81a9f357b2b96" 168 | eu-central-1 = "ami-0cbcfdbe2416ea8df" 169 | eu-west-1 = "ami-0eb00845cbc30b475" 170 | } 171 | } 172 | 173 | variable "ec2_instance_type" { 174 | description = "EC2 instance type" 175 | type = string 176 | 177 | default = "t2.micro" 178 | } 179 | 180 | variable "ec2_root_volume_size" { 181 | description = "Size of the root volume (in Gigabytes)" 182 | type = string 183 | 184 | default = 8 185 | } 186 | 187 | variable "ec2_root_volume_type" { 188 | description = "Type of the root volume (standard, gp2, or io)" 189 | type = string 190 | 191 | default = "gp2" 192 | } 193 | 194 | variable "ec2_key_name" { 195 | description = "AWS SSH Key" 196 | type = string 197 | 198 | default = "" 199 | } 200 | 201 | variable "asg_max_size" { 202 | description = "The maximum size of the auto scale group" 203 | type = string 204 | 205 | default = 3 206 | } 207 | 208 | variable "asg_min_size" { 209 | description = "The minimum size of the auto scale group" 210 | type = string 211 | 212 | default = 1 213 | } 214 | 215 | variable "asg_desired_capacity" { 216 | description = "The number of instances that should be running in the group" 217 | type = string 218 | 219 | default = 2 220 | } 221 | 222 | variable "asg_health_check_grace_period" { 223 | description = "Time in seconds after instance comes into service before checking health" 224 | type = string 225 | 226 | # Terraform default is 300 227 | default = 300 228 | } 229 | 230 | # Kong packages 231 | variable "ee_pkg" { 232 | description = "Filename of the Enterprise Edition package" 233 | type = string 234 | 235 | default = "kong-enterprise-edition_2.8.1.4_all.deb" 236 | } 237 | 238 | variable "ce_pkg" { 239 | description = "Filename of the Community Edition package" 240 | type = string 241 | 242 | default = "kong_2.8.1_amd64.deb" 243 | } 244 | 245 | # Load Balancer settings 246 | variable "enable_external_lb" { 247 | description = "Boolean to enable/create the external load balancer, exposing Kong to the Internet" 248 | type = string 249 | 250 | default = true 251 | } 252 | 253 | variable "enable_internal_lb" { 254 | description = "Boolean to enable/create the internal load balancer for the forward proxy" 255 | type = string 256 | 257 | default = true 258 | } 259 | 260 | variable "deregistration_delay" { 261 | description = "Seconds to wait before changing the state of a deregistering target from draining to unused" 262 | type = string 263 | 264 | # Terraform default is 300 265 | default = 300 266 | } 267 | 268 | variable "enable_deletion_protection" { 269 | description = "Boolean to enable delete protection on the ALB" 270 | type = string 271 | 272 | # Terraform default is false 273 | default = true 274 | } 275 | 276 | variable "health_check_healthy_threshold" { 277 | description = "Number of consecutives checks before a unhealthy target is considered healthy" 278 | type = string 279 | 280 | # Terraform default is 5 281 | default = 5 282 | } 283 | 284 | variable "health_check_interval" { 285 | description = "Seconds between health checks" 286 | type = string 287 | 288 | # Terraform default is 30 289 | default = 5 290 | } 291 | 292 | variable "health_check_matcher" { 293 | description = "HTTP Code(s) that result in a successful response from a target (comma delimited)" 294 | type = string 295 | 296 | default = 200 297 | } 298 | 299 | variable "health_check_timeout" { 300 | description = "Seconds waited before a health check fails" 301 | type = string 302 | 303 | # Terraform default is 5 304 | default = 3 305 | } 306 | 307 | variable "health_check_unhealthy_threshold" { 308 | description = "Number of consecutive checks before considering a target unhealthy" 309 | type = string 310 | 311 | # Terraform default is 2 312 | default = 2 313 | } 314 | 315 | variable "idle_timeout" { 316 | description = "Seconds a connection can idle before being disconnected" 317 | type = string 318 | 319 | # Terraform default is 60 320 | default = 60 321 | } 322 | 323 | variable "ssl_cert_external" { 324 | description = "SSL certificate domain name for the external Kong Proxy HTTPS listener" 325 | type = string 326 | default = null 327 | } 328 | 329 | variable "ssl_cert_internal" { 330 | description = "SSL certificate domain name for the internal Kong Proxy HTTPS listener" 331 | type = string 332 | default = null 333 | } 334 | 335 | variable "ssl_cert_admin" { 336 | description = "SSL certificate domain name for the Kong Admin API HTTPS listener" 337 | type = string 338 | default = null 339 | } 340 | 341 | variable "ssl_cert_manager" { 342 | description = "SSL certificate domain name for the Kong Manager HTTPS listener" 343 | type = string 344 | default = null 345 | } 346 | 347 | variable "ssl_cert_portal" { 348 | description = "SSL certificate domain name for the Dev Portal listener" 349 | type = string 350 | default = null 351 | } 352 | 353 | variable "ssl_policy" { 354 | description = "SSL Policy for HTTPS Listeners" 355 | type = string 356 | 357 | default = "ELBSecurityPolicy-TLS-1-2-2017-01" 358 | } 359 | 360 | # Cloudwatch alarms 361 | variable "cloudwatch_actions" { 362 | description = "List of cloudwatch actions for Alert/Ok" 363 | type = list(string) 364 | 365 | default = [] 366 | } 367 | 368 | variable "http_4xx_count" { 369 | description = "HTTP Code 4xx count threshhold" 370 | type = string 371 | 372 | default = 50 373 | } 374 | 375 | variable "http_5xx_count" { 376 | description = "HTTP Code 5xx count threshhold" 377 | type = string 378 | 379 | default = 50 380 | } 381 | 382 | # Datastore settings 383 | variable "enable_aurora" { 384 | description = "Boolean to enable Aurora" 385 | type = string 386 | 387 | default = "false" 388 | } 389 | 390 | variable "db_engine_version" { 391 | description = "Database engine version" 392 | type = string 393 | 394 | default = "11.14" 395 | } 396 | 397 | variable "db_engine_mode" { 398 | description = "Engine mode for Aurora" 399 | type = string 400 | 401 | default = "provisioned" 402 | } 403 | 404 | variable "db_family" { 405 | description = "Database parameter group family" 406 | type = string 407 | 408 | default = "postgres11" 409 | } 410 | 411 | variable "db_instance_class" { 412 | description = "Database instance class" 413 | type = string 414 | 415 | default = "db.t2.micro" 416 | } 417 | 418 | variable "db_instance_count" { 419 | description = "Number of database instances (0 to leverage an existing db)" 420 | type = string 421 | 422 | default = 1 423 | } 424 | 425 | variable "db_storage_size" { 426 | description = "Size of the database storage in Gigabytes" 427 | type = string 428 | 429 | # 100 is the recommended AWS minimum 430 | default = 100 431 | } 432 | 433 | variable "db_storage_type" { 434 | description = "Type of the database storage" 435 | type = string 436 | 437 | default = "gp2" 438 | } 439 | 440 | variable "db_username" { 441 | description = "Database master username" 442 | type = string 443 | 444 | default = "root" 445 | } 446 | 447 | variable "db_subnets" { 448 | description = "Database instance subnet group name" 449 | type = string 450 | 451 | default = "db-subnets" 452 | } 453 | 454 | variable "db_multi_az" { 455 | description = "Boolean to specify if RDS is multi-AZ" 456 | type = string 457 | 458 | default = false 459 | } 460 | 461 | variable "db_backup_retention_period" { 462 | description = "The number of days to retain backups" 463 | type = string 464 | 465 | default = 7 466 | } 467 | 468 | # Redis settings (for rate_limiting only) 469 | variable "enable_redis" { 470 | description = "Boolean to enable redis AWS resource" 471 | type = string 472 | 473 | default = false 474 | } 475 | 476 | variable "redis_instance_type" { 477 | description = "Redis node instance type" 478 | type = string 479 | 480 | default = "cache.t2.small" 481 | } 482 | 483 | variable "redis_engine_version" { 484 | description = "Redis engine version" 485 | type = string 486 | 487 | default = "5.0.5" 488 | } 489 | 490 | variable "redis_family" { 491 | description = "Redis parameter group family" 492 | type = string 493 | 494 | default = "redis5.0" 495 | } 496 | 497 | variable "redis_instance_count" { 498 | description = "Number of redis nodes" 499 | type = string 500 | 501 | default = 2 502 | } 503 | 504 | variable "redis_subnets" { 505 | description = "Redis cluster subnet group name" 506 | type = string 507 | 508 | default = "cache-subnets" 509 | } 510 | 511 | variable "deck_version" { 512 | description = "Version of decK to install" 513 | type = string 514 | 515 | default = "1.14.0" 516 | } 517 | 518 | variable "db_final_snapshot_identifier" { 519 | description = "The final snapshot name of the RDS/Aurora instance when it gets destroyed" 520 | type = string 521 | default = "" 522 | } 523 | --------------------------------------------------------------------------------