From b9b97721e26aac52e59dbdd2d4fad2293408b994 Mon Sep 17 00:00:00 2001 From: Pablo Loschi Date: Fri, 11 Oct 2019 14:32:13 +0200 Subject: [PATCH 1/6] add certs for consul in ami and agent.hcl --- examples/vault-consul-ami/tls/agent.hcl | 5 +++++ examples/vault-consul-ami/vault-consul.json | 16 +++++++++++++--- 2 files changed, 18 insertions(+), 3 deletions(-) create mode 100644 examples/vault-consul-ami/tls/agent.hcl diff --git a/examples/vault-consul-ami/tls/agent.hcl b/examples/vault-consul-ami/tls/agent.hcl new file mode 100644 index 00000000..0277c0a4 --- /dev/null +++ b/examples/vault-consul-ami/tls/agent.hcl @@ -0,0 +1,5 @@ + acl = { + enabled = true, + default_policy = "deny", + enable_token_persistence = true + } diff --git a/examples/vault-consul-ami/vault-consul.json b/examples/vault-consul-ami/vault-consul.json index 34fc05d0..7429edfd 100644 --- a/examples/vault-consul-ami/vault-consul.json +++ b/examples/vault-consul-ami/vault-consul.json @@ -2,9 +2,9 @@ "min_packer_version": "0.12.0", "variables": { "aws_region": "us-east-1", - "vault_version": "1.1.0", + "vault_version": "1.2.3", "consul_module_version": "v0.7.3", - "consul_version": "1.5.3", + "consul_version": "1.6.1", "consul_download_url": "{{env `CONSUL_DOWNLOAD_URL`}}", "vault_download_url": "{{env `VAULT_DOWNLOAD_URL`}}", "install_auth_signing_script": "true", @@ -87,6 +87,10 @@ " /tmp/terraform-aws-vault/modules/install-vault/install-vault --version {{user `vault_version`}};", "fi" ] + },{ + "type": "file", + "source": "{{template_dir}}/tls/agent.hcl", + "destination": "/tmp/agent.hcl" },{ "type": "file", "source": "{{template_dir}}/auth/sign-request.py", @@ -149,7 +153,13 @@ " /tmp/terraform-aws-consul/modules/install-consul/install-consul --download-url {{user `consul_download_url`}};", "else", " /tmp/terraform-aws-consul/modules/install-consul/install-consul --version {{user `consul_version`}};", - "fi" + "fi", + "sudo cp /opt/vault/tls/ca.crt.pem /opt/consul/tls/ca/ca.crt.pem", + "sudo cp /opt/vault/tls/vault.crt.pem /opt/consul/tls/vault.crt.pem", + "sudo cp /opt/vault/tls/vault.key.pem /opt/consul/tls/vault.key.pem", + "sudo chown -R consul:consul /opt/consul/tls/", + "sudo chmod -R 600 /opt/consul/tls", + "sudo chmod 700 /opt/consul/tls" ], "pause_before": "30s" },{ From cc93b145e0f9109ddf0e78f4a16a8c6af9f953ae Mon Sep 17 00:00:00 2001 From: Pablo Loschi Date: Fri, 11 Oct 2019 14:38:31 +0200 Subject: [PATCH 2/6] fix string length base64 gossip enc key --- examples/vault-prod-ready/.README.md.swp | Bin 0 -> 20480 bytes examples/vault-prod-ready/README.md | 180 ++++++ examples/vault-prod-ready/dependencies.tf | 21 + examples/vault-prod-ready/main.tf | 533 ++++++++++++++++++ examples/vault-prod-ready/outputs.tf | 92 +++ examples/vault-prod-ready/user-data-consul.sh | 33 ++ examples/vault-prod-ready/user-data-vault.sh | 57 ++ examples/vault-prod-ready/variables.tf | 159 ++++++ 8 files changed, 1075 insertions(+) create mode 100644 examples/vault-prod-ready/.README.md.swp create mode 100644 examples/vault-prod-ready/README.md create mode 100644 examples/vault-prod-ready/dependencies.tf create mode 100644 examples/vault-prod-ready/main.tf create mode 100644 examples/vault-prod-ready/outputs.tf create mode 100644 examples/vault-prod-ready/user-data-consul.sh create mode 100644 examples/vault-prod-ready/user-data-vault.sh create mode 100644 examples/vault-prod-ready/variables.tf diff --git a/examples/vault-prod-ready/.README.md.swp b/examples/vault-prod-ready/.README.md.swp new file mode 100644 index 0000000000000000000000000000000000000000..0142c2dbee65d573a531e49458e2e5e4b264011f GIT binary patch literal 20480 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 qs?jpiIMruTsRQZ?YuQ4fQHJ7_MfOkFy^cTt literal 0 HcmV?d00001 diff --git a/examples/vault-prod-ready/README.md b/examples/vault-prod-ready/README.md new file mode 100644 index 00000000..0eaad652 --- /dev/null +++ b/examples/vault-prod-ready/README.md @@ -0,0 +1,180 @@ +# Vault auto unseal cluster private with kms and acl for consul with encryption. PROD READY + +This example icombines the followings: Vault auto unseal - Vault private cluster - Consul with encryption and also adds some stuff that we expect in a prod ready working state, mainly: +* Secure instances using security groups and remove [0.0.0.0/0] +* Enable ACL on consul and pass the token in a secure way using AWS Secrets manager +* Enable encryption on Consul (based on https://github.com/hashicorp/terraform-aws-consul/tree/master/examples/example-with-encryption) +* Auto generate gossip_encryption_key and store it in AWS Secrets manager +* Only allow ssh from Vpn server +* Creates an ELB in front of Consul instances +* Creates an ELB in front of Vault instances +* Removed the block for deploying cluster in default VPC and availability zones, this needs to be set explicitly to enforce security + +This example use Terragrunt to solve some dependencies but can by used with terraform also. + +Thanks @mkrull for the ACL token provision + +# HOWTO +* 1 - Run terragrunt +* 2 - run `vault operator init` on one of the vault servers +* 3 - To login in consul look for master token under aws secrets manager + +# Vault auto unseal example + +This folder shows an example of Terraform code that deploys a [Vault][vault] cluster +in AWS with [auto unseal][auto_unseal]. Auto unseal is a Vault feature +that automatically [unseals][seal] each node in the cluster at boot using [Amazon KMS][kms]. +Without auto unseal, Vault operators are expected to manually unseal each Vault node +after it boots, a cumbersome process that typically requires multiple Vault operators +to each enter a Vault master key shard. + +This example creates a private Vault cluster that is accessible only from within +the VPC within the AWS account in which it resides, or other VPCs that are peered +with the Vault VPC. The Vault cluster uses [Consul][consul] as the storage backend, +so this example also deploys a separate Consul server cluster using the +[consul-cluster module][consul_cluster] from the Consul AWS Module. Each of the +servers in this example has [Dnsmasq][dnsmasq] installed (via the [install-dnsmasq module][dnsmasq_module]) +or [setup-systemd-resolved][setup_systemd_resolved] (in the case of Ubuntu 18.04) +which allows them to use the Consul server cluster for service discovery and thereby +access Vault via DNS using the domain name `vault.service.consul`. + +For more info on how the Vault cluster works, check out the [vault-cluster][vault_cluster] +documentation. + +**Note**: To keep this example as simple to deploy and test as possible, it deploys +the Vault cluster into your default VPC and default subnets, all of which are publicly +accessible. This is OK for learning and experimenting, but for production usage, +we strongly recommend deploying the Vault cluster into the private subnets of a custom VPC. + +**Billing Warning**: Every time you create a KMS key, you're charged $1 for the month, +even if you immediately delete it. + + Private Vault Cluster Example + +This folder shows an example of Terraform code to deploy a [Vault](https://www.vaultproject.io/) cluster in +[AWS](https://aws.amazon.com/) using the [vault-cluster module](https://github.com/hashicorp/terraform-aws-vault/tree/master/modules/vault-cluster). The Vault cluster uses +[Consul](https://www.consul.io/) as a storage backend, so this example also deploys a separate Consul server cluster +using the [consul-cluster module](https://github.com/hashicorp/terraform-aws-consul/tree/master/modules/consul-cluster) +from the Consul AWS Module. + +This example creates a private Vault cluster, which is private in the sense that the EC2 Instances are not fronted by a +load balancer, as is the case in the [Vault Public Example](/examples/root-example). Keep in mind that if the Vault +nodes are deployed to public subnets (i.e. subnets that have a route to the public Internet), this "private" cluster will +still be accessible from the public Internet. + +Each of the servers in this example has [Dnsmasq](http://www.thekelleys.org.uk/dnsmasq/doc.html) installed (via the +[install-dnsmasq module](https://github.com/hashicorp/terraform-aws-consul/tree/master/modules/install-dnsmasq)) or +[setup-systemd-resolved](https://github.com/hashicorp/terraform-aws-consul/tree/master/modules/setup-systemd-resolved) +(in the case Ubuntu of 18.04) +which allows it to use the Consul server cluster for service discovery and thereby access Vault via DNS using the +domain name `vault.service.consul`. For an example of a Vault cluster +that is publicly accessible, see [the root example](https://github.com/hashicorp/terraform-aws-vault/tree/master/examples/root-example). + +![Vault architecture](https://github.com/hashicorp/terraform-aws-vault/blob/master/_docs/architecture.png?raw=true) + +You will need to create an [Amazon Machine Image (AMI)](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/AMIs.html) +that has Vault and Consul installed, which you can do using the [vault-consul-ami example](https://github.com/hashicorp/terraform-aws-vault/tree/master/examples/vault-consul-ami)). + +For more info on how the Vault cluster works, check out the [vault-cluster](https://github.com/hashicorp/terraform-aws-vault/tree/master/modules/vault-cluster) documentation. + +**Note**: To keep this example as simple to deploy and test as possible, it deploys the Vault cluster into your default +VPC and default subnets, all of which are publicly accessible. This is OK for learning and experimenting, but for +production usage, we strongly recommend deploying the Vault cluster into the private subnets of a custom VPC. + +### Quick start + +1. `git clone` this repo to your computer. +1. Build a Vault and Consul AMI. See the [vault-consul-ami example][vault_consul_ami] + documentation for instructions. Don't forget to set the variable `vault_download_url` + with the url of the enterprise version of Vault if you wish to use Vault Enterprise. + Make sure to note down the ID of the AMI. +1. Install [Terraform][terraform]. +1. [Create an AWS KMS key][key_creation]. Take note of the key alias. +1. Open `vars.tf`, set the environment variables specified at the top of the file, + and fill in any other variables that don't have a default. Put the AMI ID you + previously took note into the `ami_id` variable and the KMS key alias into + `auto_unseal_kms_key_alias`. +1. Run `terraform init`. +1. Run `terraform apply`. +1. Run the [vault-examples-helper.sh script][examples_helper] to + print out the IP addresses of the Vault server and some example commands you + can run to interact with the cluster: `../vault-examples-helper/vault-examples-helper.sh`. +1. Ssh to an instance in the vault cluster and run `vault operator init` to initialize + the cluster, then `vault status` to check that it is unsealed. If you ssh to a + different node in the cluster, you might have to restart Vault first with + `sudo systemctl restart vault.service` so it will rejoin the cluster and unseal. + To avoid doing that, you can start your cluster with initially just one node and + start the server, then change the `vault_cluster_size` variable back to 3 and and + run `terraform apply again`. The new nodes will join the cluster already unsealed + in this case. + +### Seal + +All data stored by Vault is encrypted with a Master Key which is not stored anywhere +and Vault only ever keeps in memory. When Vault first boots, it does not have the +Master Key in memory, and therefore it can access its storage, but it cannot decrypt +its own data. So you can't really do anything apart from unsealing it or checking +the server status. While Vault is at this state, we say it is "sealed". + +Since vault uses [Shamir's Secret Sharing][shamir], which splits the master key into +pieces, running `vault operator unseal ` adds piece by piece until there +are enough parts to reconstruct the master key. This is done on different machines in the +vault cluster for better security. When Vault is unsealed and it has the recreated +master key in memory, it can then be used to read the stored decryption keys, which +can decrypt the data, and then you can start performing other operations on Vault. +Vault remains unsealed until it reboots or until someone manually reseals it. + +### Auto-unseal + +Vault has a feature that allows automatic unsealing via Amazon KMS. It +allows operators to delegate the unsealing process to AWS, which is useful for failure +situations where the server has to restart and then it will be already unsealed or +for the creation of ephemeral clusters. This process uses an AWS KMS key as +a [seal wrap][seal_wrap] mechanism: it encrypts and decrypts Vault's master key +(and it does so with the whole key, replacing the Shamir's Secret Sharing method). + +This feature is enabled by adding a `awskms` stanza at Vault's configuration. This +module takes this into consideration on the [`run-vault`][run_vault] binary, allowing +you to pass the following flags to it: + * `--enable-auto-unseal`: Enables the AWS KMS Auto-unseal feature and adds the `awskms` + stanza to the configuration + * `--auto-unseal-kms-key-id`: The key id of the AWS KMS key to be used + * `--auto-unseal-region`: The AWS region where the KMS key lives + +In this example, like in other examples, we execute `run-vault` at the [`user-data` +script][user_data], which runs on boot for every node in the Vault cluster. The +`key-id` is passed to this script by Terraform, after Terraform reads this value from a +data source through the key alias. This means that the AWS key has to be previously +manually created and we are using Terraform just to find this resource, not to +create it. It is important to notice that AWS KMS keys have a [cost][kms_pricing] +per month per key, as well as an API usage cost. + +``` +data "aws_kms_alias" "vault-example" { + name = "alias/${var.auto_unseal_kms_key_alias}" +} +``` + +If you wish to use Vault Enterprise, you still need to apply your Vault +Enterprise License to the cluster with `vault write /sys/license "text=$LICENSE_KEY_TEXT"`. + +[ami]: http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/AMIs.html +[auto_unseal]: https://www.vaultproject.io/docs/enterprise/auto-unseal/index.html +[consul_cluster]: https://github.com/hashicorp/terraform-aws-consul/tree/master/modules/consul-cluster +[consul]: https://www.consul.io/ +[dnsmasq_module]: https://github.com/hashicorp/terraform-aws-consul/tree/master/modules/install-dnsmasq +[dnsmasq]: http://www.thekelleys.org.uk/dnsmasq/doc.html +[setup_systemd_resolved]: https://github.com/hashicorp/terraform-aws-consul/tree/master/modules/setup-systemd-resolved +[examples_helper]: https://github.com/hashicorp/terraform-aws-vault/tree/master/examples/vault-examples-helper/vault-examples-helper.sh +[key_creation]: https://docs.aws.amazon.com/kms/latest/developerguide/create-keys.html +[kms]: https://aws.amazon.com/kms/ +[kms_pricing]: https://aws.amazon.com/kms/pricing/ +[run_vault]: https://github.com/hashicorp/terraform-aws-vault/tree/master/modules/run-vault +[seal_wrap]: https://www.vaultproject.io/docs/enterprise/sealwrap/index.html +[seal]: https://www.vaultproject.io/docs/concepts/seal.html +[shamir]: https://en.wikipedia.org/wiki/Shamir%27s_Secret_Sharing +[terraform]: https://www.terraform.io/ +[user_data]: https://github.com/hashicorp/terraform-aws-vault/tree/master/examples/vault-auto-unseal/user-data-vault.sh +[vault_cluster]: https://github.com/hashicorp/terraform-aws-vault/tree/master/modules/vault-cluster +[vault_consul_ami]: https://github.com/hashicorp/terraform-aws-vault/tree/master/examples/vault-consul-ami +[vault]: https://www.vaultproject.io/ diff --git a/examples/vault-prod-ready/dependencies.tf b/examples/vault-prod-ready/dependencies.tf new file mode 100644 index 00000000..d8f8ec7a --- /dev/null +++ b/examples/vault-prod-ready/dependencies.tf @@ -0,0 +1,21 @@ +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# MODULE DEPENDENCIES AS DATA SOURCES +# These resources must already exist to deploy this module. +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +# --------------------------------------------------------------------------------------------------------------------- +# PULL DATA FROM OTHER TERRAFORM TEMPLATES USING TERRAFORM REMOTE STATE +# These templates use Terraform remote state to access data from a number of other Terraform templates, all of which +# store their state in S3 buckets. +# --------------------------------------------------------------------------------------------------------------------- + +# We need to know information about the VPC to deploy Consul+Vault into +data "terraform_remote_state" "vpc" { + backend = "s3" + config = { + region = var.terraform_state_aws_region + bucket = var.terraform_state_s3_bucket + key = "${var.aws_region}/${var.vpc_name}/vpc/terraform.tfstate" + } +} + diff --git a/examples/vault-prod-ready/main.tf b/examples/vault-prod-ready/main.tf new file mode 100644 index 00000000..b485472f --- /dev/null +++ b/examples/vault-prod-ready/main.tf @@ -0,0 +1,533 @@ +# --------------------------------------------------------------------------------------------------------------------- +# CONFIGURE OUR AWS CONNECTION +# --------------------------------------------------------------------------------------------------------------------- + +provider "aws" { + # The AWS region in which all resources will be created + region = var.aws_region + + # Provider version 2.X series is the latest, but has breaking changes with 1.X series. + version = "~> 2.29" + + # Only these AWS Account IDs may be operated on by this template + allowed_account_ids = [var.aws_account_id] +} + +# --------------------------------------------------------------------------------------------------------------------- +# CONFIGURE REMOTE STATE STORAGE +# --------------------------------------------------------------------------------------------------------------------- + +terraform { + # The configuration for this backend will be filled in by Terragrunt + backend "s3" {} + + # Only allow this Terraform version. Note that if you upgrade to a newer version, Terraform won't allow you to use an + # older version, so when you upgrade, you should upgrade everyone on your team and your CI servers all at once. + required_version = "= 0.12.4" +} +# ---------------------------------------------------------------------------------------------------------------------- +# EXTRA +# ---------------------------------------------------------------------------------------------------------------------- +# Only allow vault to be accessed from the OpenVPN server +resource "aws_security_group_rule" "allow_vault_inbound_ssh" { + type = "ingress" + from_port = 22 + to_port = 22 + protocol = "tcp" + source_security_group_id = var.security_group_id + security_group_id = module.vault_cluster.security_group_id +} + +# Only allow consul to be accessed from the OpenVPN server +resource "aws_security_group_rule" "allow_consul_inbound_ssh" { + type = "ingress" + from_port = 22 + to_port = 22 + protocol = "tcp" + source_security_group_id = var.security_group_id + security_group_id = module.consul_cluster.security_group_id +} + +# Only allow vault to be accessed from the OpenVPN server +resource "aws_security_group_rule" "allow_vault_inbound_http" { + type = "ingress" + from_port = 8200 + to_port = 8200 + protocol = "tcp" + source_security_group_id = var.security_group_id + security_group_id = module.vault_cluster.security_group_id +} +# Only allow vault to be accessed from the OpenVPN server +resource "aws_security_group_rule" "allow_consul_inbound_http" { + type = "ingress" + from_port = 8500 + to_port = 8500 + protocol = "tcp" + source_security_group_id = var.security_group_id + security_group_id = module.consul_cluster.security_group_id +} +resource "aws_security_group_rule" "allow_vault_inbound_http_from_elb" { + type = "ingress" + from_port = 8200 + to_port = 8200 + protocol = "tcp" + source_security_group_id = aws_security_group.elb_vault.id + security_group_id = module.vault_cluster.security_group_id +} +resource "aws_security_group_rule" "allow_consul_inbound_http_from_elb" { + type = "ingress" + from_port = 8500 + to_port = 8500 + protocol = "tcp" + source_security_group_id = aws_security_group.elb_consul.id + security_group_id = module.consul_cluster.security_group_id +} + +data "aws_kms_alias" "vault-example" { + name = "alias/${var.auto_unseal_kms_key_alias}" +} + +# --------------------------------------------------------------------------------------------------------------------- +# DEPLOY THE VAULT SERVER CLUSTER +# --------------------------------------------------------------------------------------------------------------------- + +module "vault_cluster" { + # When using these modules in your own templates, you will need to use a Git URL with a ref attribute that pins you + # to a specific version of the modules, such as the following example: + source = "github.com/hashicorp/terraform-aws-vault.git//modules/vault-cluster?ref=v0.13.3" + + cluster_name = var.vault_cluster_name + cluster_size = var.vault_cluster_size + instance_type = var.vault_instance_type + + ami_id = var.ami_id + user_data = data.template_file.user_data_vault_cluster.rendered + + vpc_id = var.vpc_id + subnet_ids = var.vpc_subnet_ids + + # This setting will create the AWS policy that allows the vault cluster to + # access KMS and use this key for encryption and decryption + enable_auto_unseal = true + + auto_unseal_kms_key_arn = data.aws_kms_alias.vault-example.target_key_arn + + # To make testing easier, we allow requests from any IP address here but in a production deployment, we *strongly* + # recommend you limit this to the IP address ranges of known, trusted servers inside your VPC. + + allowed_ssh_cidr_blocks = [] + allowed_inbound_cidr_blocks = [] + allowed_inbound_security_group_ids = [] + allowed_inbound_security_group_count = 0 + ssh_key_name = var.ssh_key_name +} + +# --------------------------------------------------------------------------------------------------------------------- +# ATTACH IAM POLICIES FOR CONSUL +# To allow our Vault servers to automatically discover the Consul servers, we need to give them the IAM permissions from +# the Consul AWS Module's consul-iam-policies module. +# --------------------------------------------------------------------------------------------------------------------- + +module "consul_iam_policies_servers" { + source = "github.com/hashicorp/terraform-aws-consul.git//modules/consul-iam-policies?ref=v0.7.0" + + iam_role_id = module.vault_cluster.iam_role_id +} + +# --------------------------------------------------------------------------------------------------------------------- +# GIVE SSH-GRUNT PERMISSIONS TO TALK TO IAM +# We add an IAM policy to Jenkins that allows ssh-grunt to make API calls to IAM to fetch IAM user and group data. +# --------------------------------------------------------------------------------------------------------------------- + +module "ssh_grunt_policies" { + source = "git::git@github.com:gruntwork-io/module-security.git//modules/iam-policies?ref=v0.19.1" + + aws_account_id = var.aws_account_id + + # ssh-grunt is an automated app, so we can't use MFA with it + iam_policy_should_require_mfa = false + trust_policy_should_require_mfa = false + + # Since our IAM users are defined in a separate AWS account, we need to give ssh-grunt permission to make API calls to + # that account. + allow_access_to_other_account_arns = [var.external_account_ssh_grunt_role_arn] +} + +resource "aws_iam_role_policy" "ssh_grunt_permissions_vault" { + name = "ssh-grunt-permissions" + role = module.vault_cluster.iam_role_id + policy = module.ssh_grunt_policies.allow_access_to_other_accounts[0] +} + +resource "aws_iam_role_policy" "ssh_grunt_permissions_consul" { + name = "ssh-grunt-permissions" + role = module.consul_cluster.iam_role_id + policy = module.ssh_grunt_policies.allow_access_to_other_accounts[0] +} + +# --------------------------------------------------------------------------------------------------------------------- +# ADD IAM POLICY THAT ALLOWS CLOUDWATCH LOG AGGREGATION +# --------------------------------------------------------------------------------------------------------------------- + +module "cloudwatch_log_aggregation" { + source = "git::git@github.com:gruntwork-io/module-aws-monitoring.git//modules/logs/cloudwatch-log-aggregation-iam-policy?ref=v0.14.0" + name_prefix = "cloudwatch_consul_vault-${var.vault_cluster_name}" +} + +resource "aws_iam_policy_attachment" "attach_cloudwatch_consul_log_aggregation_policy" { + name = "attach-cloudwatch-log-aggregation-policy" + roles = [module.consul_cluster.iam_role_id] + policy_arn = module.cloudwatch_log_aggregation.cloudwatch_log_aggregation_policy_arn +} +resource "aws_iam_policy_attachment" "attach_cloudwatch_vault_log_aggregation_policy" { + name = "attach-cloudwatch-log-aggregation-policy" + roles = [module.vault_cluster.iam_role_id] + policy_arn = module.cloudwatch_log_aggregation.cloudwatch_log_aggregation_policy_arn +} +# --------------------------------------------------------------------------------------------------------------------- +# ADD IAM POLICY THAT ALLOWS READING AND WRITING CLOUDWATCH METRICS +# --------------------------------------------------------------------------------------------------------------------- + +module "cloudwatch_metrics" { + source = "git::git@github.com:gruntwork-io/module-aws-monitoring.git//modules/metrics/cloudwatch-custom-metrics-iam-policy?ref=v0.14.0" + name_prefix = "cloudwatch_consul_vault-${var.consul_cluster_name}" +} + +resource "aws_iam_policy_attachment" "attach_cloudwatch_consul_metrics_policy" { + name = "attach-cloudwatch-log-aggregation-policy" + roles = [module.consul_cluster.iam_role_id] + policy_arn = module.cloudwatch_log_aggregation.cloudwatch_log_aggregation_policy_arn +} +resource "aws_iam_policy_attachment" "attach_cloudwatch_vault_metrics_policy" { + name = "attach-cloudwatch-log-aggregation-policy" + roles = [module.vault_cluster.iam_role_id] + policy_arn = module.cloudwatch_log_aggregation.cloudwatch_log_aggregation_policy_arn +} + +# --------------------------------------------------------------------------------------------------------------------- +# THE USER DATA SCRIPT THAT WILL RUN ON EACH VAULT SERVER WHEN IT'S BOOTING +# This script will configure and start Vault +# --------------------------------------------------------------------------------------------------------------------- + +data "template_file" "user_data_vault_cluster" { + template = file("${path.module}/user-data-vault.sh") + + vars = { + consul_cluster_tag_key = var.consul_cluster_tag_key + consul_cluster_tag_value = var.consul_cluster_name + kms_key_id = data.aws_kms_alias.vault-example.target_key_id + aws_region = var.aws_region + enable_gossip_encryption = var.enable_gossip_encryption + gossip_encryption_key = aws_secretsmanager_secret_version.gossip_encryption_key.secret_string + enable_rpc_encryption = var.enable_rpc_encryption + ca_path = var.ca_path + cert_file_path = var.cert_file_path + key_file_path = var.key_file_path + consul_token_secret = aws_secretsmanager_secret.consul_token.name + } +} + +# --------------------------------------------------------------------------------------------------------------------- +# PERMIT CONSUL SPECIFIC TRAFFIC IN VAULT CLUSTER +# To allow our Vault servers consul agents to communicate with other consul agents and participate in the LAN gossip, +# we open up the consul specific protocols and ports for consul traffic +# --------------------------------------------------------------------------------------------------------------------- + +module "security_group_rules" { + source = "github.com/hashicorp/terraform-aws-consul.git//modules/consul-client-security-group-rules?ref=v0.7.0" + + security_group_id = module.vault_cluster.security_group_id + + # To make testing easier, we allow requests from any IP address here but in a production deployment, we *strongly* + # recommend you limit this to the IP address ranges of known, trusted servers inside your VPC. + + allowed_inbound_cidr_blocks = [] + allowed_inbound_security_group_ids = [] +} + +# --------------------------------------------------------------------------------------------------------------------- +# DEPLOY THE CONSUL SERVER CLUSTER +# --------------------------------------------------------------------------------------------------------------------- + +module "consul_cluster" { + source = "github.com/hashicorp/terraform-aws-consul.git//modules/consul-cluster?ref=v0.7.0" + + cluster_name = var.consul_cluster_name + cluster_size = var.consul_cluster_size + instance_type = var.consul_instance_type + + # The EC2 Instances will use these tags to automatically discover each other and form a cluster + cluster_tag_key = var.consul_cluster_tag_key + cluster_tag_value = var.consul_cluster_name + + ami_id = var.ami_id + user_data = data.template_file.user_data_consul.rendered + + vpc_id = var.vpc_id + subnet_ids = var.vpc_subnet_ids + + # To make testing easier, we allow Consul and SSH requests from any IP address here but in a production + # deployment, we strongly recommend you limit this to the IP address ranges of known, trusted servers inside your VPC. + + allowed_ssh_cidr_blocks = [] + allowed_inbound_cidr_blocks = var.vpc_cidr_blocks +# https://github.com/hashicorp/terraform-aws-vault/pull/115/files allow vault servers by default #115 + allowed_inbound_security_group_count = 1 + allowed_inbound_security_group_ids = [module.vault_cluster.security_group_id] + ssh_key_name = var.ssh_key_name +} + +# --------------------------------------------------------------------------------------------------------------------- +# THE USER DATA SCRIPT THAT WILL RUN ON EACH CONSUL SERVER WHEN IT'S BOOTING +# This script will configure and start Consul +# --------------------------------------------------------------------------------------------------------------------- + +data "template_file" "user_data_consul" { + template = file("${path.module}/user-data-consul.sh") + + vars = { + consul_cluster_tag_key = var.consul_cluster_tag_key + consul_cluster_tag_value = var.consul_cluster_name + aws_region = var.aws_region + ca_path = var.ca_path + cert_file_path = var.cert_file_path + key_file_path = var.key_file_path + enable_gossip_encryption = var.enable_gossip_encryption + gossip_encryption_key = aws_secretsmanager_secret_version.gossip_encryption_key.secret_string + enable_rpc_encryption = var.enable_rpc_encryption + consul_token_secret = aws_secretsmanager_secret.consul_token.name + } +} + +#Add load balancers and dns +# --------------------------------------------------------------------------------------------------------------------- +# CREATE AN ELB TO PERFORM HEALTH CHECKS ON CONSUL +# Use an ELB for health checks. This is useful for doing zero-downtime deployments and making sure that failed nodes +# are automatically replaced. We also use it to expose the management UI. +# --------------------------------------------------------------------------------------------------------------------- + +resource "aws_elb" "load_balancer_consul" { + name = var.consul_cluster_name + subnets = var.vpc_subnet_ids + security_groups = [aws_security_group.elb_consul.id] + internal = true + + connection_draining = true + connection_draining_timeout = 60 + + # Perform TCP health checks on Consul's client port. + health_check { + target = "TCP:8500" + interval = 30 + timeout = 10 + healthy_threshold = 2 + unhealthy_threshold = 2 + } + + # The ELB can be used to reach the management interface + listener { + instance_port = 8500 + instance_protocol = "http" + lb_port = 8500 + lb_protocol = "http" + } +} + +# --------------------------------------------------------------------------------------------------------------------- +# CREATE A SECURITY GROUP THAT CONTROLS WHAT TRAFFIC CAN GO IN AND OUT OF THE ELB OF CONSUL +# --------------------------------------------------------------------------------------------------------------------- + +resource "aws_security_group" "elb_consul" { + name = var.consul_cluster_name + vpc_id = var.vpc_id +} + +resource "aws_security_group_rule" "allow_all_outbound_consul" { + type = "egress" + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + security_group_id = aws_security_group.elb_consul.id +} + +resource "aws_security_group_rule" "allow_consul_inbound_from_elb" { + type = "ingress" + from_port = 8500 + to_port = 8500 + protocol = "tcp" + security_group_id = aws_security_group.elb_consul.id + source_security_group_id = module.consul_cluster.security_group_id +} + +resource "aws_security_group_rule" "allow_openvpntoconsul_inbound_from_elb" { + type = "ingress" + from_port = 8500 + to_port = 8500 + protocol = "tcp" + security_group_id = aws_security_group.elb_consul.id + source_security_group_id = var.security_group_id +} +## Allow clients to connect from within the provided security group +resource "aws_security_group_rule" "allow_consul_inbound_from_subnets" { + type = "ingress" + from_port = 8500 + to_port = 8500 + protocol = "tcp" + security_group_id = aws_security_group.elb_consul.id + cidr_blocks = var.vpc_cidr_blocks +} +#Assign ASG to ELB +resource "aws_autoscaling_attachment" "elb_consul" { + autoscaling_group_name = module.consul_cluster.asg_name + elb = var.consul_cluster_name +} + +# --------------------------------------------------------------------------------------------------------------------- +# CREATE AN ELB TO PERFORM HEALTH CHECKS ON VAULT +# Use an ELB for health checks. This is useful for doing zero-downtime deployments and making sure that failed nodes +# are automatically replaced. We also use it to expose the management UI. +# --------------------------------------------------------------------------------------------------------------------- + +resource "aws_elb" "load_balancer_vault" { + name = var.vault_cluster_name + subnets = var.vpc_subnet_ids + security_groups = [aws_security_group.elb_vault.id] + internal = true + connection_draining = true + connection_draining_timeout = 60 + + # Perform TCP health checks on Consul's client port. + health_check { + target = "TCP:8200" + interval = 30 + timeout = 10 + healthy_threshold = 2 + unhealthy_threshold = 2 + } + + # The ELB can be used to reach the management interface + listener { + instance_port = 8200 + instance_protocol = "http" + lb_port = 8200 + lb_protocol = "http" + } +} + +# --------------------------------------------------------------------------------------------------------------------- +# CREATE A SECURITY GROUP THAT CONTROLS WHAT TRAFFIC CAN GO IN AND OUT OF THE ELB OF VAULT +# --------------------------------------------------------------------------------------------------------------------- + +resource "aws_security_group" "elb_vault" { + name = var.vault_cluster_name + vpc_id = var.vpc_id +} + +resource "aws_security_group_rule" "allow_all_outbound_vault" { + type = "egress" + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + security_group_id = aws_security_group.elb_vault.id +} + +resource "aws_security_group_rule" "allow_vault_inbound_from_elb" { + type = "ingress" + from_port = 8200 + to_port = 8200 + protocol = "tcp" + security_group_id = aws_security_group.elb_vault.id + source_security_group_id = module.vault_cluster.security_group_id +} + +resource "aws_security_group_rule" "allow_openvpntovault_inbound_from_elb" { + type = "ingress" + from_port = 8200 + to_port = 8200 + protocol = "tcp" + security_group_id = aws_security_group.elb_vault.id + source_security_group_id = var.security_group_id +} +## Allow clients to connect from within the provided security group +resource "aws_security_group_rule" "allow_vault_inbound_from_subnets" { + type = "ingress" + from_port = 8200 + to_port = 8200 + protocol = "tcp" + security_group_id = aws_security_group.elb_vault.id + cidr_blocks = var.vpc_cidr_blocks +} +#Assign ASG to ELB +resource "aws_autoscaling_attachment" "elb_vault" { + autoscaling_group_name = module.vault_cluster.asg_name + elb = var.vault_cluster_name +} + +# Secret that holds the Consul master token +resource "aws_secretsmanager_secret" "consul_token" { + name_prefix = "${var.consul_cluster_name}-token" +} + +# Random uuid used as master token +resource "random_uuid" "consul_token" {} + +# Secret version updated with the random uuid +resource "aws_secretsmanager_secret_version" "consul_token" { + secret_id = aws_secretsmanager_secret.consul_token.id + secret_string = random_uuid.consul_token.result +} + +# Secret that holds the gossip encryption key +resource "aws_secretsmanager_secret" "gossip_encryption_key" { + name_prefix = "gossip_encryption_key" +} + +# Random uuid used as gossip encryption key +resource "random_string" "gossip_encryption_key" { + length = 32 + +} + +# Secret version updated with the random uuid +resource "aws_secretsmanager_secret_version" "gossip_encryption_key" { + secret_id = aws_secretsmanager_secret.gossip_encryption_key.id + secret_string = base64encode(random_string.gossip_encryption_key.result) +} + +# Policy to allow Consul to write the consul_token secret and gossip encryption key +resource "aws_iam_policy" "secretsmanager_get_token" { + name = var.consul_cluster_name + policy = < >(tee /var/log/user-data.log|logger -t user-data -s 2>/dev/console) 2>&1 + +# These variables are passed in via Terraform template interpolation +#/opt/consul/bin/run-consul --server --cluster-tag-key "${consul_cluster_tag_key}" --cluster-tag-value "${consul_cluster_tag_value}" + +#From encryption example + +# These variables are passed in via Terraform template interplation +if [[ "${enable_gossip_encryption}" == "true" && ! -z "${gossip_encryption_key}" ]]; then + # Note that setting the encryption key in plain text here means that it will be readable from the Terraform state file + # and/or the EC2 API/console. We're doing this for simplicity, but in a real production environment you should pass an + # encrypted key to Terraform and decrypt it before passing it to run-consul with something like KMS. + gossip_encryption_configuration="--enable-gossip-encryption --gossip-encryption-key ${gossip_encryption_key}" +fi + +if [[ "${enable_rpc_encryption}" == "true" && ! -z "${ca_path}" && ! -z "${cert_file_path}" && ! -z "${key_file_path}" ]]; then + rpc_encryption_configuration="--enable-rpc-encryption --ca-path ${ca_path} --cert-file-path ${cert_file_path} --key-file-path ${key_file_path}" +fi + +# Create acl config including the master token from AWS SecretsManager +TOKEN=$(aws secretsmanager --region "${aws_region}" get-secret-value --secret-id ${consul_token_secret} | jq -r .SecretString) +echo '{"acl": {"tokens": {"master": "'$TOKEN'"}}}' > /opt/consul/config/acl.json + +/opt/consul/bin/run-consul --server --datacenter "${consul_cluster_tag_key}" --cluster-tag-key "${consul_cluster_tag_key}" --cluster-tag-value "${consul_cluster_tag_value}" $gossip_encryption_configuration $rpc_encryption_configuration diff --git a/examples/vault-prod-ready/user-data-vault.sh b/examples/vault-prod-ready/user-data-vault.sh new file mode 100644 index 00000000..9cf8d354 --- /dev/null +++ b/examples/vault-prod-ready/user-data-vault.sh @@ -0,0 +1,57 @@ +#!/bin/bash +# This script is meant to be run in the User Data of each EC2 Instance while it's booting. The script uses the +# run-consul script to configure and start Consul in client mode and then the run-vault script to configure +# the auto unsealing on server init + +set -e + +# Send the log output from this script to user-data.log, syslog, and the console +# From: https://alestic.com/2010/12/ec2-user-data-output/ +exec > >(tee /var/log/user-data.log|logger -t user-data -s 2>/dev/console) 2>&1 + +# The Packer template puts the TLS certs in these file paths +readonly VAULT_TLS_CERT_FILE="/opt/vault/tls/vault.crt.pem" +readonly VAULT_TLS_KEY_FILE="/opt/vault/tls/vault.key.pem" + +# The variables below are filled in via Terraform interpolation + +#/opt/consul/bin/run-consul --client --cluster-tag-key "${consul_cluster_tag_key}" --cluster-tag-value "${consul_cluster_tag_value}" +# These variables are passed in via Terraform template interplation +if [[ "${enable_gossip_encryption}" == "true" && ! -z "${gossip_encryption_key}" ]]; then + # Note that setting the encryption key in plain text here means that it will be readable from the Terraform state file + # and/or the EC2 API/console. We're doing this for simplicity, but in a real production environment you should pass an + # encrypted key to Terraform and decrypt it before passing it to run-consul with something like KMS. + gossip_encryption_configuration="--enable-gossip-encryption --gossip-encryption-key ${gossip_encryption_key}" +fi + +if [[ "${enable_rpc_encryption}" == "true" && ! -z "${ca_path}" && ! -z "${cert_file_path}" && ! -z "${key_file_path}" ]]; then + rpc_encryption_configuration="--enable-rpc-encryption --ca-path ${ca_path} --cert-file-path ${cert_file_path} --key-file-path ${key_file_path}" +fi + +# Create acl config including the master token from AWS SecretsManager +TOKEN=$(aws secretsmanager --region "${aws_region}" get-secret-value --secret-id ${consul_token_secret} | jq -r .SecretString) + +/opt/consul/bin/run-consul --client --datacenter "${consul_cluster_tag_key}" --cluster-tag-key "${consul_cluster_tag_key}" --cluster-tag-value "${consul_cluster_tag_value}" $gossip_encryption_configuration $rpc_encryption_configuration + +/opt/vault/bin/run-vault \ + --tls-cert-file "$VAULT_TLS_CERT_FILE" \ + --tls-key-file "$VAULT_TLS_KEY_FILE" \ + --enable-auto-unseal \ + --auto-unseal-kms-key-id "${kms_key_id}" \ + --auto-unseal-kms-key-region "${aws_region}" + +# Inject token into the consul storage block and restart vault to pick it up +sed -i '/storage "consul"/a token = "'$TOKEN'"' /opt/vault/config/default.hcl +systemctl restart vault + +#echo --tls-cert-file "$VAULT_TLS_CERT_FILE" --tls-key-file "$VAULT_TLS_KEY_FILE" --enable-auto-unseal --auto-unseal-kms-key-id "${kms_key_id}" --auto-unseal-kms-key-region "${aws_region}" > /tmp/log + +# When you ssh to one of the instances in the vault cluster and initialize the server +# You will notice it will now boot unsealed +# /opt/vault/bin/vault operator init +# /opt/vault/bin/vault status +# +# If the enterprise license isn't applied, it will however reseal after 30 minutes +# This is how you apply the license, please note that the VAULT_TOKEN environment +# variable needs to be set with the root token obtained when you initialized the server +# /opt/vault/bin/vault write /sys/license "text=" diff --git a/examples/vault-prod-ready/variables.tf b/examples/vault-prod-ready/variables.tf new file mode 100644 index 00000000..b0b53c2a --- /dev/null +++ b/examples/vault-prod-ready/variables.tf @@ -0,0 +1,159 @@ +# --------------------------------------------------------------------------------------------------------------------- +# REQUIRED PARAMETERS +# You must provide a value for each of these parameters. +# --------------------------------------------------------------------------------------------------------------------- + +variable "aws_region" { + description = "The AWS region in which all resources will be created" + type = string +} + +variable "aws_account_id" { + description = "The ID of the AWS Account in which to create resources." + type = string +} + +variable "ami_id" { + description = "The ID of the AMI to run in the cluster. This should be an AMI built from the Packer template under examples/vault-consul-ami/vault-consul.json." + type = string + default = null +} + +variable "ssh_key_name" { + description = "The name of an EC2 Key Pair that can be used to SSH to the EC2 Instances in this cluster. Set to an empty string to not associate a Key Pair." + type = string +} + +variable "auto_unseal_kms_key_alias" { + description = "The alias of AWS KMS key used for encryption and decryption" + type = string +} + +# --------------------------------------------------------------------------------------------------------------------- +# OPTIONAL PARAMETERS +# These parameters have reasonable defaults. +# --------------------------------------------------------------------------------------------------------------------- + +variable "vault_cluster_name" { + description = "What to name the Vault server cluster and all of its associated resources" + type = string + default = "vault-example" +} + +variable "consul_cluster_name" { + description = "What to name the Consul server cluster and all of its associated resources" + type = string + default = "consul-example" +} + +variable "auth_server_name" { + description = "What to name the server authenticating to vault" + type = string + default = "auth-example" +} + +variable "vault_cluster_size" { + description = "The number of Vault server nodes to deploy. We strongly recommend using 3 or 5." + type = number + default = 3 +} + +variable "consul_cluster_size" { + description = "The number of Consul server nodes to deploy. We strongly recommend using 3 or 5." + type = number + default = 3 +} + +variable "vault_instance_type" { + description = "The type of EC2 Instance to run in the Vault ASG" + type = string + default = "t2.micro" +} + +variable "consul_instance_type" { + description = "The type of EC2 Instance to run in the Consul ASG" + type = string + default = "t2.nano" +} + +variable "consul_cluster_tag_key" { + description = "The tag the Consul EC2 Instances will look for to automatically discover each other and form a cluster." + type = string + default = "consul-servers" +} + +variable "vpc_id" { + description = "The ID of the VPC to deploy into. Leave an empty string to use the Default VPC in this region." + type = string + default = null +} + +variable "vpc_subnet_ids" { + description = "Subnets for Consul and Vault" + type = list(string) +} + +variable "vpc_cidr_blocks" { + description = "CIDR blocks of the subnets in the VPC" + type = list(string) +} + +variable "vpc_name" { + description = "The name of the VPC in which to run the EKS cluster (e.g. stage, prod)" + type = string +} + +variable "security_group_id" { + description = "Security group ID of VPN Server" + type = string +} + +variable "external_account_ssh_grunt_role_arn" { + description = "Since our IAM users are defined in a separate AWS account, this variable is used to specify the ARN of an IAM role that allows ssh-grunt to retrieve IAM group and public SSH key info from that account." + type = string +} + +variable "terraform_state_aws_region" { + description = "The AWS region of the S3 bucket used to store Terraform remote state" + type = string +} + +variable "terraform_state_s3_bucket" { + description = "The name of the S3 bucket used to store Terraform remote state" + type = string +} +## +# --------------------------------------------------------------------------------------------------------------------- +# Encryption +# --------------------------------------------------------------------------------------------------------------------- + +variable "enable_gossip_encryption" { + description = "Encrypt gossip traffic between nodes. Must also specify encryption key." + type = bool + default = false +} + +variable "enable_rpc_encryption" { + description = "Encrypt RPC traffic between nodes. Must also specify TLS certificates and keys." + type = bool + default = false +} + +variable "ca_path" { + description = "Path to the directory of CA files used to verify outgoing connections." + type = string + default = "/opt/consul/tls/ca" +} + +variable "cert_file_path" { + description = "Path to the certificate file used to verify incoming connections." + type = string + default = "/opt/consul/tls/consul.crt.pem" +} + +variable "key_file_path" { + description = "Path to the certificate key used to verify incoming connections." + type = string + default = "/opt/consul/tls/consul.key.pem" +} + From 223bf5b21b06b5944dee0817901c857de584c10d Mon Sep 17 00:00:00 2001 From: Pablo Loschi Date: Tue, 15 Oct 2019 11:38:23 +0200 Subject: [PATCH 3/6] remove terragrunt deps --- examples/vault-prod-ready/.README.md.swp | Bin 20480 -> 0 bytes examples/vault-prod-ready/dependencies.tf | 21 -------- examples/vault-prod-ready/main.tf | 63 ++++++++++++---------- examples/vault-prod-ready/variables.tf | 47 +++++----------- 4 files changed, 48 insertions(+), 83 deletions(-) delete mode 100644 examples/vault-prod-ready/.README.md.swp delete mode 100644 examples/vault-prod-ready/dependencies.tf diff --git a/examples/vault-prod-ready/.README.md.swp b/examples/vault-prod-ready/.README.md.swp deleted file mode 100644 index 0142c2dbee65d573a531e49458e2e5e4b264011f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20480 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 qs?jpiIMruTsRQZ?YuQ4fQHJ7_MfOkFy^cTt diff --git a/examples/vault-prod-ready/dependencies.tf b/examples/vault-prod-ready/dependencies.tf deleted file mode 100644 index d8f8ec7a..00000000 --- a/examples/vault-prod-ready/dependencies.tf +++ /dev/null @@ -1,21 +0,0 @@ -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# MODULE DEPENDENCIES AS DATA SOURCES -# These resources must already exist to deploy this module. -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -# --------------------------------------------------------------------------------------------------------------------- -# PULL DATA FROM OTHER TERRAFORM TEMPLATES USING TERRAFORM REMOTE STATE -# These templates use Terraform remote state to access data from a number of other Terraform templates, all of which -# store their state in S3 buckets. -# --------------------------------------------------------------------------------------------------------------------- - -# We need to know information about the VPC to deploy Consul+Vault into -data "terraform_remote_state" "vpc" { - backend = "s3" - config = { - region = var.terraform_state_aws_region - bucket = var.terraform_state_s3_bucket - key = "${var.aws_region}/${var.vpc_name}/vpc/terraform.tfstate" - } -} - diff --git a/examples/vault-prod-ready/main.tf b/examples/vault-prod-ready/main.tf index b485472f..603d8fb3 100644 --- a/examples/vault-prod-ready/main.tf +++ b/examples/vault-prod-ready/main.tf @@ -1,32 +1,17 @@ -# --------------------------------------------------------------------------------------------------------------------- -# CONFIGURE OUR AWS CONNECTION -# --------------------------------------------------------------------------------------------------------------------- - -provider "aws" { - # The AWS region in which all resources will be created - region = var.aws_region - - # Provider version 2.X series is the latest, but has breaking changes with 1.X series. - version = "~> 2.29" - - # Only these AWS Account IDs may be operated on by this template - allowed_account_ids = [var.aws_account_id] -} - -# --------------------------------------------------------------------------------------------------------------------- -# CONFIGURE REMOTE STATE STORAGE -# --------------------------------------------------------------------------------------------------------------------- - +# ---------------------------------------------------------------------------------------------------------------------- +# REQUIRE A SPECIFIC TERRAFORM VERSION OR HIGHER +# This module has been updated with 0.12 syntax, which means it is no longer compatible with any versions below 0.12. +# ---------------------------------------------------------------------------------------------------------------------- terraform { - # The configuration for this backend will be filled in by Terragrunt - backend "s3" {} + required_version = ">= 0.12" +} - # Only allow this Terraform version. Note that if you upgrade to a newer version, Terraform won't allow you to use an - # older version, so when you upgrade, you should upgrade everyone on your team and your CI servers all at once. - required_version = "= 0.12.4" +data "aws_kms_alias" "vault-example" { + name = "alias/${var.auto_unseal_kms_key_alias}" } + # ---------------------------------------------------------------------------------------------------------------------- -# EXTRA +# SECURITY GROUPS # ---------------------------------------------------------------------------------------------------------------------- # Only allow vault to be accessed from the OpenVPN server resource "aws_security_group_rule" "allow_vault_inbound_ssh" { @@ -104,7 +89,7 @@ module "vault_cluster" { user_data = data.template_file.user_data_vault_cluster.rendered vpc_id = var.vpc_id - subnet_ids = var.vpc_subnet_ids +# subnet_ids = var.vpc_subnet_ids # This setting will create the AWS policy that allows the vault cluster to # access KMS and use this key for encryption and decryption @@ -264,7 +249,7 @@ module "consul_cluster" { user_data = data.template_file.user_data_consul.rendered vpc_id = var.vpc_id - subnet_ids = var.vpc_subnet_ids +# subnet_ids = var.vpc_subnet_ids # To make testing easier, we allow Consul and SSH requests from any IP address here but in a production # deployment, we strongly recommend you limit this to the IP address ranges of known, trusted servers inside your VPC. @@ -308,7 +293,7 @@ data "template_file" "user_data_consul" { resource "aws_elb" "load_balancer_consul" { name = var.consul_cluster_name - subnets = var.vpc_subnet_ids +# subnets = var.vpc_subnet_ids security_groups = [aws_security_group.elb_consul.id] internal = true @@ -391,7 +376,7 @@ resource "aws_autoscaling_attachment" "elb_consul" { resource "aws_elb" "load_balancer_vault" { name = var.vault_cluster_name - subnets = var.vpc_subnet_ids +# subnets = var.vpc_subnet_ids security_groups = [aws_security_group.elb_vault.id] internal = true connection_draining = true @@ -531,3 +516,23 @@ resource "aws_iam_role_policy_attachment" "vault_secretsmanager" { } data "aws_caller_identity" "current" {} + +# --------------------------------------------------------------------------------------------------------------------- +# DEPLOY THE CLUSTERS IN THE DEFAULT VPC AND AVAILABILITY ZONES +# Using the default VPC and subnets makes this example easy to run and test, but it means Consul and Vault are +# accessible from the public Internet. In a production deployment, we strongly recommend deploying into a custom VPC +# and private subnets. +# --------------------------------------------------------------------------------------------------------------------- + +data "aws_vpc" "default" { + default = var.vpc_id == null ? true : false + id = var.vpc_id +} + +data "aws_subnet_ids" "default" { + vpc_id = data.aws_vpc.default.id +} + +data "aws_region" "current" { +} + diff --git a/examples/vault-prod-ready/variables.tf b/examples/vault-prod-ready/variables.tf index b0b53c2a..d25cadd1 100644 --- a/examples/vault-prod-ready/variables.tf +++ b/examples/vault-prod-ready/variables.tf @@ -1,17 +1,17 @@ + # --------------------------------------------------------------------------------------------------------------------- -# REQUIRED PARAMETERS -# You must provide a value for each of these parameters. +# ENVIRONMENT VARIABLES +# Define these secrets as environment variables # --------------------------------------------------------------------------------------------------------------------- -variable "aws_region" { - description = "The AWS region in which all resources will be created" - type = string -} +# AWS_ACCESS_KEY_ID +# AWS_SECRET_ACCESS_KEY +# AWS_DEFAULT_REGION -variable "aws_account_id" { - description = "The ID of the AWS Account in which to create resources." - type = string -} +# --------------------------------------------------------------------------------------------------------------------- +# REQUIRED PARAMETERS +# You must provide a value for each of these parameters. +# --------------------------------------------------------------------------------------------------------------------- variable "ami_id" { description = "The ID of the AMI to run in the cluster. This should be an AMI built from the Packer template under examples/vault-consul-ami/vault-consul.json." @@ -88,20 +88,10 @@ variable "vpc_id" { default = null } -variable "vpc_subnet_ids" { - description = "Subnets for Consul and Vault" - type = list(string) -} - -variable "vpc_cidr_blocks" { - description = "CIDR blocks of the subnets in the VPC" - type = list(string) -} - -variable "vpc_name" { - description = "The name of the VPC in which to run the EKS cluster (e.g. stage, prod)" - type = string -} +#variable "vpc_subnet_ids" { +# description = "Subnets for Consul and Vault" +# type = list(string) +#} variable "security_group_id" { description = "Security group ID of VPN Server" @@ -113,15 +103,6 @@ variable "external_account_ssh_grunt_role_arn" { type = string } -variable "terraform_state_aws_region" { - description = "The AWS region of the S3 bucket used to store Terraform remote state" - type = string -} - -variable "terraform_state_s3_bucket" { - description = "The name of the S3 bucket used to store Terraform remote state" - type = string -} ## # --------------------------------------------------------------------------------------------------------------------- # Encryption From 79a4ec230282bb16d979e5eb1a41192eeed47cb4 Mon Sep 17 00:00:00 2001 From: Pablo Loschi Date: Tue, 15 Oct 2019 12:07:44 +0200 Subject: [PATCH 4/6] remove duplicated var unseal --- examples/vault-prod-ready/main.tf | 4 ---- 1 file changed, 4 deletions(-) diff --git a/examples/vault-prod-ready/main.tf b/examples/vault-prod-ready/main.tf index 603d8fb3..3bf22f0a 100644 --- a/examples/vault-prod-ready/main.tf +++ b/examples/vault-prod-ready/main.tf @@ -68,10 +68,6 @@ resource "aws_security_group_rule" "allow_consul_inbound_http_from_elb" { security_group_id = module.consul_cluster.security_group_id } -data "aws_kms_alias" "vault-example" { - name = "alias/${var.auto_unseal_kms_key_alias}" -} - # --------------------------------------------------------------------------------------------------------------------- # DEPLOY THE VAULT SERVER CLUSTER # --------------------------------------------------------------------------------------------------------------------- From d39adb5da2ba8c73fdeefaf228e31604355819b9 Mon Sep 17 00:00:00 2001 From: Pablo Loschi Date: Fri, 10 Jan 2020 10:13:47 +0100 Subject: [PATCH 5/6] Update examples/vault-prod-ready/README.md Co-Authored-By: Yevgeniy Brikman --- examples/vault-prod-ready/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/vault-prod-ready/README.md b/examples/vault-prod-ready/README.md index 0eaad652..e66b9348 100644 --- a/examples/vault-prod-ready/README.md +++ b/examples/vault-prod-ready/README.md @@ -1,6 +1,6 @@ # Vault auto unseal cluster private with kms and acl for consul with encryption. PROD READY -This example icombines the followings: Vault auto unseal - Vault private cluster - Consul with encryption and also adds some stuff that we expect in a prod ready working state, mainly: +This example combines the following: Vault auto unseal, Vault private cluster, and Consul with encryption and also adds a few items that we expect in a production Vault deployment, including: * Secure instances using security groups and remove [0.0.0.0/0] * Enable ACL on consul and pass the token in a secure way using AWS Secrets manager * Enable encryption on Consul (based on https://github.com/hashicorp/terraform-aws-consul/tree/master/examples/example-with-encryption) From a88afd839ef02b4f1b6638fef55da01f7823fe7c Mon Sep 17 00:00:00 2001 From: Pablo Loschi Date: Fri, 10 Jan 2020 10:19:21 +0100 Subject: [PATCH 6/6] remove stuff from readme from copy paste --- examples/vault-prod-ready/README.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/examples/vault-prod-ready/README.md b/examples/vault-prod-ready/README.md index e66b9348..22a45418 100644 --- a/examples/vault-prod-ready/README.md +++ b/examples/vault-prod-ready/README.md @@ -41,11 +41,6 @@ access Vault via DNS using the domain name `vault.service.consul`. For more info on how the Vault cluster works, check out the [vault-cluster][vault_cluster] documentation. -**Note**: To keep this example as simple to deploy and test as possible, it deploys -the Vault cluster into your default VPC and default subnets, all of which are publicly -accessible. This is OK for learning and experimenting, but for production usage, -we strongly recommend deploying the Vault cluster into the private subnets of a custom VPC. - **Billing Warning**: Every time you create a KMS key, you're charged $1 for the month, even if you immediately delete it.