Skip to content

Commit ad65113

Browse files
authored
Add support redis mtls (#358)
1 parent ca03c73 commit ad65113

File tree

15 files changed

+673
-38
lines changed

15 files changed

+673
-38
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,4 @@
2424
# Local Working dir
2525
work
2626
backends.tf
27+
*.pem

locals.tf

Lines changed: 16 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -47,42 +47,22 @@ locals {
4747
}
4848
}
4949
)
50-
51-
redis = var.enable_redis_sentinel ? try(
52-
module.redis_sentinel[0],
53-
{
54-
hostname = null
55-
password = null
56-
username = null
57-
redis_port = null
58-
use_password_auth = null
59-
use_tls = null
60-
sentinel_enabled = var.enable_redis_sentinel
61-
sentinel_hosts = []
62-
sentinel_leader = null
63-
sentinel_username = null
64-
sentinel_password = null
65-
aws_elasticache_subnet_group_name = null
66-
aws_security_group_redis = null
67-
}
68-
) : try(
69-
module.redis[0],
70-
{
71-
hostname = null
72-
password = null
73-
username = null
74-
redis_port = null
75-
use_password_auth = null
76-
use_tls = null
77-
sentinel_enabled = var.enable_redis_sentinel
78-
sentinel_hosts = []
79-
sentinel_leader = null
80-
sentinel_username = null
81-
sentinel_password = null
82-
aws_elasticache_subnet_group_name = null
83-
aws_security_group_redis = null
84-
}
85-
)
50+
redis_default = {
51+
hostname = null
52+
password = null
53+
username = null
54+
redis_port = null
55+
use_password_auth = null
56+
use_tls = null
57+
sentinel_enabled = var.enable_redis_sentinel
58+
sentinel_hosts = []
59+
sentinel_leader = null
60+
sentinel_username = null
61+
sentinel_password = null
62+
aws_elasticache_subnet_group_name = null
63+
aws_security_group_redis = null
64+
}
65+
redis = var.enable_redis_sentinel ? module.redis_sentinel[0] : var.enable_redis_mtls ? module.redis_mtls[0] : try(module.redis[0], local.redis_default)
8666

8767
no_proxy = concat([
8868
"127.0.0.1",

main.tf

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ module "service_accounts" {
3838
tfe_license_secret_id = var.tfe_license_secret_id
3939
kms_key_arn = local.kms_key_arn
4040
vm_certificate_secret_id = var.vm_certificate_secret_id
41+
redis_ca_certificate_secret_id = var.redis_ca_certificate_secret_id
42+
redis_client_certificate_secret_id = var.redis_client_certificate_secret_id
43+
redis_client_key_secret_id = var.redis_client_key_secret_id
4144
vm_key_secret_id = var.vm_key_secret_id
4245
}
4346

@@ -71,7 +74,7 @@ module "networking" {
7174
# -----------------------------------------------------------------------------
7275
module "redis" {
7376
source = "./modules/redis"
74-
count = local.enable_redis_module && var.enable_redis_sentinel == false ? 1 : 0
77+
count = local.enable_redis_module && var.enable_redis_sentinel == false || local.enable_redis_module && var.enable_redis_mtls == false ? 1 : 0
7578

7679
active_active = var.operational_mode == "active-active"
7780
friendly_name_prefix = var.friendly_name_prefix
@@ -115,6 +118,34 @@ module "redis_sentinel" {
115118
network_private_subnet_cidrs = local.network_private_subnet_cidrs
116119
}
117120

121+
# -----------------------------------------------------------------------------
122+
# Redis mTLS
123+
# -----------------------------------------------------------------------------
124+
125+
module "redis_mtls" {
126+
count = var.enable_redis_mtls ? 1 : 0
127+
source = "./modules/redis-standalone-mtls"
128+
# This module is used to deploy a Redis instance with mTLS enabled.
129+
130+
domain_name = var.domain_name
131+
redis_ca_certificate_secret_id = var.redis_ca_certificate_secret_id
132+
redis_client_certificate_secret_id = var.redis_client_certificate_secret_id
133+
redis_client_key_secret_id = var.redis_client_key_secret_id
134+
# mTLS does not use password authentication
135+
redis_authentication_mode = "NONE"
136+
aws_iam_instance_profile = module.service_accounts.iam_instance_profile.name
137+
asg_tags = var.asg_tags
138+
ec2_launch_template_tag_specifications = var.ec2_launch_template_tag_specifications
139+
friendly_name_prefix = var.friendly_name_prefix
140+
health_check_grace_period = var.health_check_grace_period
141+
health_check_type = var.health_check_type
142+
instance_type = var.instance_type
143+
key_name = var.key_name
144+
network_id = local.network_id
145+
network_subnets_private = local.network_private_subnets
146+
network_private_subnet_cidrs = local.network_private_subnet_cidrs
147+
}
148+
118149
# -----------------------------------------------------------------------------
119150
# AWS PostreSQL Database
120151
# -----------------------------------------------------------------------------
@@ -229,6 +260,11 @@ module "runtime_container_engine_config" {
229260
redis_sentinel_leader_name = local.redis.sentinel_leader
230261
redis_sentinel_user = local.redis.sentinel_username
231262
redis_sentinel_password = local.redis.sentinel_password
263+
redis_use_mtls = var.enable_redis_mtls
264+
redis_ca_cert_path = "/etc/ssl/private/terraform-enterprise/redis/cacert.pem"
265+
redis_client_cert_path = "/etc/ssl/private/terraform-enterprise/redis/cert.pem"
266+
redis_client_key_path = "/etc/ssl/private/terraform-enterprise/redis/key.pem"
267+
232268

233269
trusted_proxies = local.trusted_proxies
234270

@@ -260,6 +296,11 @@ module "tfe_init_fdo" {
260296
certificate_secret_id = var.vm_certificate_secret_id == null ? null : var.vm_certificate_secret_id
261297
key_secret_id = var.vm_key_secret_id == null ? null : var.vm_key_secret_id
262298

299+
enable_redis_mtls = var.enable_redis_mtls
300+
redis_ca_certificate_secret_id = var.redis_ca_certificate_secret_id == null ? null : var.redis_ca_certificate_secret_id
301+
redis_client_certificate_secret_id = var.redis_client_certificate_secret_id == null ? null : var.redis_client_certificate_secret_id
302+
redis_client_key_secret_id = var.redis_client_key_secret_id == null ? null : var.redis_client_key_secret_id
303+
263304
proxy_ip = var.proxy_ip != null ? var.proxy_ip : null
264305
proxy_port = var.proxy_ip != null ? var.proxy_port : null
265306
extra_no_proxy = var.proxy_ip != null ? local.no_proxy : null
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Copyright (c) HashiCorp, Inc.
2+
# SPDX-License-Identifier: MPL-2.0
3+
4+
# Description: This file contains the docker-compose configuration for the redis OSS module.
5+
services:
6+
redis:
7+
image: redis:7
8+
command: [
9+
"redis-server",
10+
# disable all ports
11+
"--port", "0",
12+
"--tls-port 6379",
13+
"--tls-cert-file", "/certs/fullchain.pem",
14+
"--tls-key-file", "/certs/privkey.pem",
15+
"--tls-ca-cert-file", "/certs/isrgrootx1.pem",
16+
"--tls-auth-clients", "yes"
17+
]
18+
ports:
19+
- "${redis_port}:${redis_port}"
20+
volumes:
21+
# For Redis TLS certificates.
22+
- $${FULLCHAIN}:/certs/fullchain.pem
23+
- $${PRIVKEY}:/certs/privkey.pem
24+
- $${ISRGROOTX1}:/certs/isrgrootx1.pem
25+
26+
27+
28+
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
#!/usr/bin/env bash
2+
# Copyright (c) HashiCorp, Inc.
3+
# SPDX-License-Identifier: MPL-2.0
4+
5+
set -eu pipefail
6+
7+
function get_base64_secrets {
8+
local secret_id=$1
9+
# OS: Agnostic
10+
# Description: Pull the Base 64 encoded secrets from AWS Secrets Manager
11+
12+
/usr/local/bin/aws secretsmanager get-secret-value --secret-id $secret_id | jq --raw-output '.SecretBinary,.SecretString | select(. != null)'
13+
}
14+
15+
function retry {
16+
local retries=$1
17+
shift
18+
19+
local count=0
20+
until "$@"; do
21+
exit=$?
22+
wait=$((2 ** $count))
23+
count=$(($count + 1))
24+
if [ $count -lt $retries ]; then
25+
echo "Retry $count/$retries exited $exit, retrying in $wait seconds..."
26+
sleep $wait
27+
else
28+
echo "Retry $count/$retries exited $exit, no more retries left."
29+
return $exit
30+
fi
31+
done
32+
return 0
33+
34+
}
35+
36+
curl --noproxy '*' --fail --silent --show-error --location https://download.docker.com/linux/ubuntu/gpg \
37+
| gpg --dearmor --output /usr/share/keyrings/docker-archive-keyring.gpg
38+
echo \
39+
"deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] \
40+
https://download.docker.com/linux/ubuntu $(lsb_release --codename --short) stable" \
41+
| sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
42+
retry 10 apt-get --assume-yes update
43+
retry 10 apt-get --assume-yes install docker-ce docker-ce-cli containerd.io redis-tools unzip jq
44+
retry 10 apt-get --assume-yes autoremove
45+
46+
47+
echo "[$(date +"%FT%T")] [Terraform Enterprise] Install AWS CLI"
48+
curl --noproxy '*' "https://awscli.amazonaws.com/awscli-exe-linux-$(uname -m | grep -q "arm\|aarch" && echo "aarch64" || echo "x86_64").zip" -o "awscliv2.zip"
49+
unzip awscliv2.zip
50+
./aws/install
51+
rm -f ./awscliv2.zip
52+
rm -rf ./aws
53+
54+
tfe_dir="/etc/redis"
55+
mkdir -p $tfe_dir
56+
57+
export FULLCHAIN=$tfe_dir/fullchain.pem
58+
export PRIVKEY=$tfe_dir/privkey.pem
59+
export ISRGROOTX1=$tfe_dir/isrgrootx1.pem
60+
echo ${compose} | base64 -d > $tfe_dir/compose.yaml
61+
62+
echo $(get_base64_secrets ${redis_client_cert}) | base64 -d > $FULLCHAIN
63+
echo $(get_base64_secrets ${redis_client_key}) | base64 -d > $PRIVKEY
64+
echo $(get_base64_secrets ${redis_client_ca}) | base64 -d > $ISRGROOTX1
65+
66+
chmod a+r $FULLCHAIN
67+
chmod a+r $PRIVKEY
68+
chmod a+r $ISRGROOTX1
69+
docker compose -f $tfe_dir/compose.yaml up -d
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Copyright (c) HashiCorp, Inc.
2+
# SPDX-License-Identifier: MPL-2.0
3+
4+
locals {
5+
redis_user_data_template = "${path.module}/files/script.sh"
6+
redis_user_data = templatefile(local.redis_user_data_template, {
7+
redis_client_cert = var.redis_client_certificate_secret_id
8+
redis_client_key = var.redis_client_key_secret_id
9+
redis_client_ca = var.redis_ca_certificate_secret_id
10+
compose = base64encode(templatefile(local.compose_path, {
11+
redis_port = var.redis_port
12+
}))
13+
})
14+
compose_path = "${path.module}/files/compose.yaml"
15+
tags = concat(
16+
[
17+
{
18+
key = "Name"
19+
value = "${var.friendly_name_prefix}-tfe"
20+
propagate_at_launch = true
21+
},
22+
],
23+
[
24+
for k, v in var.asg_tags : {
25+
key = k
26+
value = v
27+
propagate_at_launch = true
28+
}
29+
]
30+
)
31+
default_health_check_grace_period = 1500
32+
health_check_grace_period = var.health_check_grace_period != null ? var.health_check_grace_period : local.default_health_check_grace_period
33+
}
34+
35+
data "aws_ami" "ubuntu" {
36+
most_recent = true
37+
38+
filter {
39+
name = "name"
40+
values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
41+
}
42+
43+
filter {
44+
name = "virtualization-type"
45+
values = ["hvm"]
46+
}
47+
48+
owners = ["099720109477"] # Canonical
49+
}

modules/redis-standalone-mtls/main.tf

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# Copyright (c) HashiCorp, Inc.
2+
# SPDX-License-Identifier: MPL-2.0
3+
4+
5+
# Launch Template for Redis
6+
# -------------------------
7+
8+
resource "random_password" "redis_password" {
9+
count = contains(["USER_AND_PASSWORD", "PASSWORD"], var.redis_authentication_mode) ? 1 : 0
10+
length = 16
11+
special = true
12+
override_special = "#$%&*()-_=+[]{}<>:?"
13+
}
14+
15+
resource "random_pet" "redis_username" {
16+
count = var.redis_authentication_mode == "USER_AND_PASSWORD" ? 1 : 0
17+
}
18+
19+
resource "aws_launch_template" "redis" {
20+
name_prefix = "${var.friendly_name_prefix}-redis"
21+
image_id = data.aws_ami.ubuntu.id
22+
instance_type = var.instance_type
23+
user_data = base64encode(local.redis_user_data)
24+
key_name = var.key_name
25+
vpc_security_group_ids = [aws_security_group.redis_inbound_allow.id, aws_security_group.redis_outbound_allow.id]
26+
27+
dynamic "tag_specifications" {
28+
for_each = var.ec2_launch_template_tag_specifications
29+
30+
content {
31+
resource_type = tag_specifications.value["resource_type"]
32+
tags = tag_specifications.value["tags"]
33+
}
34+
}
35+
36+
iam_instance_profile {
37+
name = var.aws_iam_instance_profile
38+
}
39+
40+
metadata_options {
41+
http_endpoint = "enabled"
42+
http_put_response_hop_limit = 2
43+
http_tokens = "required"
44+
}
45+
46+
block_device_mappings {
47+
device_name = "/dev/sda1"
48+
ebs {
49+
encrypted = true
50+
volume_type = "gp2"
51+
volume_size = 50
52+
delete_on_termination = true
53+
}
54+
}
55+
56+
lifecycle {
57+
create_before_destroy = true
58+
}
59+
}
60+
61+
# Autoscaling Group for Redis
62+
# ---------------------------
63+
64+
resource "aws_autoscaling_group" "redis" {
65+
name = "${var.friendly_name_prefix}-redis-asg"
66+
min_size = 1
67+
max_size = 1
68+
desired_capacity = 1
69+
vpc_zone_identifier = var.network_subnets_private
70+
target_group_arns = [aws_lb_target_group.redis_tg.arn]
71+
72+
# Increases grace period for any AMI that is not the default Ubuntu
73+
# since RHEL has longer startup time
74+
health_check_grace_period = local.health_check_grace_period
75+
health_check_type = var.health_check_type
76+
77+
launch_template {
78+
id = aws_launch_template.redis.id
79+
version = "$Latest"
80+
}
81+
82+
dynamic "tag" {
83+
for_each = local.tags
84+
85+
content {
86+
key = tag.value["key"]
87+
value = tag.value["value"]
88+
propagate_at_launch = tag.value["propagate_at_launch"]
89+
}
90+
}
91+
92+
lifecycle {
93+
create_before_destroy = true
94+
}
95+
}

0 commit comments

Comments
 (0)