Skip to content

Add support redis mtls #358

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 28 commits into from
Jun 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@
# Local Working dir
work
backends.tf
*.pem
52 changes: 16 additions & 36 deletions locals.tf
Original file line number Diff line number Diff line change
Expand Up @@ -47,42 +47,22 @@ locals {
}
}
)

redis = var.enable_redis_sentinel ? try(
module.redis_sentinel[0],
{
hostname = null
password = null
username = null
redis_port = null
use_password_auth = null
use_tls = null
sentinel_enabled = var.enable_redis_sentinel
sentinel_hosts = []
sentinel_leader = null
sentinel_username = null
sentinel_password = null
aws_elasticache_subnet_group_name = null
aws_security_group_redis = null
}
) : try(
module.redis[0],
{
hostname = null
password = null
username = null
redis_port = null
use_password_auth = null
use_tls = null
sentinel_enabled = var.enable_redis_sentinel
sentinel_hosts = []
sentinel_leader = null
sentinel_username = null
sentinel_password = null
aws_elasticache_subnet_group_name = null
aws_security_group_redis = null
}
)
redis_default = {
hostname = null
password = null
username = null
redis_port = null
use_password_auth = null
use_tls = null
sentinel_enabled = var.enable_redis_sentinel
sentinel_hosts = []
sentinel_leader = null
sentinel_username = null
sentinel_password = null
aws_elasticache_subnet_group_name = null
aws_security_group_redis = null
}
redis = var.enable_redis_sentinel ? module.redis_sentinel[0] : var.enable_redis_mtls ? module.redis_mtls[0] : try(module.redis[0], local.redis_default)

no_proxy = concat([
"127.0.0.1",
Expand Down
43 changes: 42 additions & 1 deletion main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ module "service_accounts" {
tfe_license_secret_id = var.tfe_license_secret_id
kms_key_arn = local.kms_key_arn
vm_certificate_secret_id = var.vm_certificate_secret_id
redis_ca_certificate_secret_id = var.redis_ca_certificate_secret_id
redis_client_certificate_secret_id = var.redis_client_certificate_secret_id
redis_client_key_secret_id = var.redis_client_key_secret_id
vm_key_secret_id = var.vm_key_secret_id
}

Expand Down Expand Up @@ -71,7 +74,7 @@ module "networking" {
# -----------------------------------------------------------------------------
module "redis" {
source = "./modules/redis"
count = local.enable_redis_module && var.enable_redis_sentinel == false ? 1 : 0
count = local.enable_redis_module && var.enable_redis_sentinel == false || local.enable_redis_module && var.enable_redis_mtls == false ? 1 : 0

active_active = var.operational_mode == "active-active"
friendly_name_prefix = var.friendly_name_prefix
Expand Down Expand Up @@ -115,6 +118,34 @@ module "redis_sentinel" {
network_private_subnet_cidrs = local.network_private_subnet_cidrs
}

# -----------------------------------------------------------------------------
# Redis mTLS
# -----------------------------------------------------------------------------

module "redis_mtls" {
count = var.enable_redis_mtls ? 1 : 0
source = "./modules/redis-standalone-mtls"
# This module is used to deploy a Redis instance with mTLS enabled.

domain_name = var.domain_name
redis_ca_certificate_secret_id = var.redis_ca_certificate_secret_id
redis_client_certificate_secret_id = var.redis_client_certificate_secret_id
redis_client_key_secret_id = var.redis_client_key_secret_id
# mTLS does not use password authentication
redis_authentication_mode = "NONE"
aws_iam_instance_profile = module.service_accounts.iam_instance_profile.name
asg_tags = var.asg_tags
ec2_launch_template_tag_specifications = var.ec2_launch_template_tag_specifications
friendly_name_prefix = var.friendly_name_prefix
health_check_grace_period = var.health_check_grace_period
health_check_type = var.health_check_type
instance_type = var.instance_type
key_name = var.key_name
network_id = local.network_id
network_subnets_private = local.network_private_subnets
network_private_subnet_cidrs = local.network_private_subnet_cidrs
}

# -----------------------------------------------------------------------------
# AWS PostreSQL Database
# -----------------------------------------------------------------------------
Expand Down Expand Up @@ -229,6 +260,11 @@ module "runtime_container_engine_config" {
redis_sentinel_leader_name = local.redis.sentinel_leader
redis_sentinel_user = local.redis.sentinel_username
redis_sentinel_password = local.redis.sentinel_password
redis_use_mtls = var.enable_redis_mtls
redis_ca_cert_path = "/etc/ssl/private/terraform-enterprise/redis/cacert.pem"
redis_client_cert_path = "/etc/ssl/private/terraform-enterprise/redis/cert.pem"
redis_client_key_path = "/etc/ssl/private/terraform-enterprise/redis/key.pem"


trusted_proxies = local.trusted_proxies

Expand Down Expand Up @@ -260,6 +296,11 @@ module "tfe_init_fdo" {
certificate_secret_id = var.vm_certificate_secret_id == null ? null : var.vm_certificate_secret_id
key_secret_id = var.vm_key_secret_id == null ? null : var.vm_key_secret_id

enable_redis_mtls = var.enable_redis_mtls
redis_ca_certificate_secret_id = var.redis_ca_certificate_secret_id == null ? null : var.redis_ca_certificate_secret_id
redis_client_certificate_secret_id = var.redis_client_certificate_secret_id == null ? null : var.redis_client_certificate_secret_id
redis_client_key_secret_id = var.redis_client_key_secret_id == null ? null : var.redis_client_key_secret_id

proxy_ip = var.proxy_ip != null ? var.proxy_ip : null
proxy_port = var.proxy_ip != null ? var.proxy_port : null
extra_no_proxy = var.proxy_ip != null ? local.no_proxy : null
Expand Down
28 changes: 28 additions & 0 deletions modules/redis-standalone-mtls/files/compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: MPL-2.0

# Description: This file contains the docker-compose configuration for the redis OSS module.
services:
redis:
image: redis:7
command: [
"redis-server",
# disable all ports
"--port", "0",
"--tls-port 6379",
"--tls-cert-file", "/certs/fullchain.pem",
"--tls-key-file", "/certs/privkey.pem",
"--tls-ca-cert-file", "/certs/isrgrootx1.pem",
"--tls-auth-clients", "yes"
]
ports:
- "${redis_port}:${redis_port}"
volumes:
# For Redis TLS certificates.
- $${FULLCHAIN}:/certs/fullchain.pem
- $${PRIVKEY}:/certs/privkey.pem
- $${ISRGROOTX1}:/certs/isrgrootx1.pem




69 changes: 69 additions & 0 deletions modules/redis-standalone-mtls/files/script.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#!/usr/bin/env bash
# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: MPL-2.0

set -eu pipefail

function get_base64_secrets {
local secret_id=$1
# OS: Agnostic
# Description: Pull the Base 64 encoded secrets from AWS Secrets Manager

/usr/local/bin/aws secretsmanager get-secret-value --secret-id $secret_id | jq --raw-output '.SecretBinary,.SecretString | select(. != null)'
}

function retry {
local retries=$1
shift

local count=0
until "$@"; do
exit=$?
wait=$((2 ** $count))
count=$(($count + 1))
if [ $count -lt $retries ]; then
echo "Retry $count/$retries exited $exit, retrying in $wait seconds..."
sleep $wait
else
echo "Retry $count/$retries exited $exit, no more retries left."
return $exit
fi
done
return 0

}

curl --noproxy '*' --fail --silent --show-error --location https://download.docker.com/linux/ubuntu/gpg \
| gpg --dearmor --output /usr/share/keyrings/docker-archive-keyring.gpg
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] \
https://download.docker.com/linux/ubuntu $(lsb_release --codename --short) stable" \
| sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
retry 10 apt-get --assume-yes update
retry 10 apt-get --assume-yes install docker-ce docker-ce-cli containerd.io redis-tools unzip jq
retry 10 apt-get --assume-yes autoremove


echo "[$(date +"%FT%T")] [Terraform Enterprise] Install AWS CLI"
curl --noproxy '*' "https://awscli.amazonaws.com/awscli-exe-linux-$(uname -m | grep -q "arm\|aarch" && echo "aarch64" || echo "x86_64").zip" -o "awscliv2.zip"
unzip awscliv2.zip
./aws/install
rm -f ./awscliv2.zip
rm -rf ./aws

tfe_dir="/etc/redis"
mkdir -p $tfe_dir

export FULLCHAIN=$tfe_dir/fullchain.pem
export PRIVKEY=$tfe_dir/privkey.pem
export ISRGROOTX1=$tfe_dir/isrgrootx1.pem
echo ${compose} | base64 -d > $tfe_dir/compose.yaml

echo $(get_base64_secrets ${redis_client_cert}) | base64 -d > $FULLCHAIN
echo $(get_base64_secrets ${redis_client_key}) | base64 -d > $PRIVKEY
echo $(get_base64_secrets ${redis_client_ca}) | base64 -d > $ISRGROOTX1

chmod a+r $FULLCHAIN
chmod a+r $PRIVKEY
chmod a+r $ISRGROOTX1
docker compose -f $tfe_dir/compose.yaml up -d
49 changes: 49 additions & 0 deletions modules/redis-standalone-mtls/locals.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: MPL-2.0

locals {
redis_user_data_template = "${path.module}/files/script.sh"
redis_user_data = templatefile(local.redis_user_data_template, {
redis_client_cert = var.redis_client_certificate_secret_id
redis_client_key = var.redis_client_key_secret_id
redis_client_ca = var.redis_ca_certificate_secret_id
compose = base64encode(templatefile(local.compose_path, {
redis_port = var.redis_port
}))
})
compose_path = "${path.module}/files/compose.yaml"
tags = concat(
[
{
key = "Name"
value = "${var.friendly_name_prefix}-tfe"
propagate_at_launch = true
},
],
[
for k, v in var.asg_tags : {
key = k
value = v
propagate_at_launch = true
}
]
)
default_health_check_grace_period = 1500
health_check_grace_period = var.health_check_grace_period != null ? var.health_check_grace_period : local.default_health_check_grace_period
}

data "aws_ami" "ubuntu" {
most_recent = true

filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
}

filter {
name = "virtualization-type"
values = ["hvm"]
}

owners = ["099720109477"] # Canonical
}
95 changes: 95 additions & 0 deletions modules/redis-standalone-mtls/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: MPL-2.0


# Launch Template for Redis
# -------------------------

resource "random_password" "redis_password" {
count = contains(["USER_AND_PASSWORD", "PASSWORD"], var.redis_authentication_mode) ? 1 : 0
length = 16
special = true
override_special = "#$%&*()-_=+[]{}<>:?"
}

resource "random_pet" "redis_username" {
count = var.redis_authentication_mode == "USER_AND_PASSWORD" ? 1 : 0
}

resource "aws_launch_template" "redis" {
name_prefix = "${var.friendly_name_prefix}-redis"
image_id = data.aws_ami.ubuntu.id
instance_type = var.instance_type
user_data = base64encode(local.redis_user_data)
key_name = var.key_name
vpc_security_group_ids = [aws_security_group.redis_inbound_allow.id, aws_security_group.redis_outbound_allow.id]

dynamic "tag_specifications" {
for_each = var.ec2_launch_template_tag_specifications

content {
resource_type = tag_specifications.value["resource_type"]
tags = tag_specifications.value["tags"]
}
}

iam_instance_profile {
name = var.aws_iam_instance_profile
}

metadata_options {
http_endpoint = "enabled"
http_put_response_hop_limit = 2
http_tokens = "required"
}

block_device_mappings {
device_name = "/dev/sda1"
ebs {
encrypted = true
volume_type = "gp2"
volume_size = 50
delete_on_termination = true
}
}

lifecycle {
create_before_destroy = true
}
}

# Autoscaling Group for Redis
# ---------------------------

resource "aws_autoscaling_group" "redis" {
name = "${var.friendly_name_prefix}-redis-asg"
min_size = 1
max_size = 1
desired_capacity = 1
vpc_zone_identifier = var.network_subnets_private
target_group_arns = [aws_lb_target_group.redis_tg.arn]

# Increases grace period for any AMI that is not the default Ubuntu
# since RHEL has longer startup time
health_check_grace_period = local.health_check_grace_period
health_check_type = var.health_check_type

launch_template {
id = aws_launch_template.redis.id
version = "$Latest"
}

dynamic "tag" {
for_each = local.tags

content {
key = tag.value["key"]
value = tag.value["value"]
propagate_at_launch = tag.value["propagate_at_launch"]
}
}

lifecycle {
create_before_destroy = true
}
}
Loading
Loading