mirror of
https://github.com/hashicorp/vault.git
synced 2026-04-05 05:41:56 +02:00
* license: update headers to IBM Corp. * `make proto` * update offset because source file changed Signed-off-by: Ryan Cragun <me@ryan.ec> Co-authored-by: Ryan Cragun <me@ryan.ec>
292 lines
11 KiB
HCL
292 lines
11 KiB
HCL
# Copyright IBM Corp. 2016, 2025
|
|
# SPDX-License-Identifier: BUSL-1.1
|
|
|
|
terraform {
|
|
required_providers {
|
|
# We need to specify the provider source in each module until we publish it
|
|
# to the public registry
|
|
enos = {
|
|
source = "registry.terraform.io/hashicorp-forge/enos"
|
|
version = ">= 0.5.3"
|
|
}
|
|
}
|
|
}
|
|
|
|
locals {
|
|
api_addr_localhost = var.ip_version == 4 ? "http://127.0.0.1:${var.listener_port}" : "http://[::1]:${var.listener_port}"
|
|
api_addrs = tolist([for h in var.hosts : {
|
|
4 : "http://${h.public_ip}:${var.listener_port}",
|
|
6 : "http://[${h.ipv6}]:${var.listener_port}",
|
|
}])
|
|
api_addrs_internal = tolist([for h in var.hosts : {
|
|
4 : "http://${h.private_ip}:${var.listener_port}",
|
|
6 : "http://[${h.ipv6}]:${var.listener_port}",
|
|
}])
|
|
bin_path = "${var.install_dir}/vault"
|
|
cluster_addrs = tolist([for h in var.hosts : {
|
|
4 : "http://${h.public_ip}:${var.cluster_port}",
|
|
6 : "http://[${h.ipv6}]:${var.cluster_port}",
|
|
}])
|
|
cluster_addrs_internal = tolist([for h in var.hosts : {
|
|
4 : "http://${h.private_ip}:${var.cluster_port}",
|
|
6 : "http://[${h.ipv6}]:${var.cluster_port}",
|
|
}])
|
|
// In order to get Terraform to plan we have to use collections with keys that are known at plan
|
|
// time. Here we're creating locals that keep track of index values that point to our target hosts.
|
|
followers = toset(slice(local.instances, 1, length(local.instances)))
|
|
instances = [for idx in range(length(var.hosts)) : tostring(idx)]
|
|
leader = toset(slice(local.instances, 0, 1))
|
|
listener_address = var.ip_version == 4 ? "0.0.0.0:${var.listener_port}" : "[::]:${var.listener_port}"
|
|
prometheus_retention_time = var.enable_telemetry ? "24h" : "0"
|
|
// Handle cases where we might have to distribute HSM tokens for the pkcs11 seal before starting
|
|
// vault.
|
|
token_base64 = try(lookup(var.seal_attributes, "token_base64", ""), "")
|
|
token_base64_secondary = try(lookup(var.seal_attributes_secondary, "token_base64", ""), "")
|
|
// This module currently supports up to two defined seals. Most of our locals logic here is for
|
|
// creating the correct seal configuration.
|
|
seals = {
|
|
primary = local.seal_primary
|
|
secondary = local.seal_secondary
|
|
}
|
|
seals_primary = {
|
|
awskms = {
|
|
type = "awskms"
|
|
attributes = merge(
|
|
{
|
|
name = var.seal_alias
|
|
priority = var.seal_priority
|
|
}, var.seal_attributes
|
|
)
|
|
}
|
|
pkcs11 = {
|
|
type = "pkcs11"
|
|
attributes = merge(
|
|
{
|
|
name = var.seal_alias
|
|
priority = var.seal_priority
|
|
},
|
|
// Strip out attributes that aren't supposed to be in seal stanza like our base64 encoded
|
|
// softhsm blob and the token directory. We'll also inject the shared object library
|
|
// location that we detect on the target machines. This allows use to create the token and
|
|
// keys on a machines that have different shared object locations.
|
|
merge(
|
|
try({ for key, val in var.seal_attributes : key => val if key != "token_base64" && key != "token_dir" }, {}),
|
|
# Note: the below reference has to point to a specific instance of the maybe_configure_hsm
|
|
# module (in this case [0]) due to the maybe_configure_hsm module call using `count` to control whether it runs or not.
|
|
try({ lib = module.maybe_configure_hsm[0].lib }, {})
|
|
),
|
|
)
|
|
}
|
|
shamir = {
|
|
type = "shamir"
|
|
attributes = null
|
|
}
|
|
}
|
|
seal_primary = local.seals_primary[var.seal_type]
|
|
seals_secondary = {
|
|
awskms = {
|
|
type = "awskms"
|
|
attributes = merge(
|
|
{
|
|
name = var.seal_alias_secondary
|
|
priority = var.seal_priority_secondary
|
|
}, var.seal_attributes_secondary
|
|
)
|
|
}
|
|
pkcs11 = {
|
|
type = "pkcs11"
|
|
attributes = merge(
|
|
{
|
|
name = var.seal_alias_secondary
|
|
priority = var.seal_priority_secondary
|
|
},
|
|
merge(
|
|
try({ for key, val in var.seal_attributes_secondary : key => val if key != "token_base64" && key != "token_dir" }, {}),
|
|
# Note: the below reference has to point to a specific instance of the maybe_configure_hsm_secondary
|
|
# module (in this case [0]) due to the maybe_configure_hsm_secondary module call using `count` to control whether it runs or not.
|
|
try({ lib = module.maybe_configure_hsm_secondary[0].lib }, {})
|
|
),
|
|
)
|
|
}
|
|
none = {
|
|
type = "none"
|
|
attributes = null
|
|
}
|
|
}
|
|
seal_secondary = local.seals_secondary[var.seal_type_secondary]
|
|
storage_address = var.ip_version == 4 ? "0.0.0.0:${var.external_storage_port}" : "[::]:${var.external_storage_port}"
|
|
storage_attributes = [for idx, host in var.hosts : (var.storage_backend == "raft" ?
|
|
merge(
|
|
{
|
|
node_id = "${var.storage_node_prefix}_${idx}"
|
|
},
|
|
var.storage_backend_attrs
|
|
) :
|
|
{
|
|
address = local.storage_address
|
|
path = "vault"
|
|
})
|
|
]
|
|
storage_retry_join = {
|
|
"raft" : {
|
|
auto_join : "provider=aws addr_type=${var.ip_version == 4 ? "private_v4" : "public_v6"} tag_key=${var.cluster_tag_key} tag_value=${var.cluster_name}",
|
|
auto_join_scheme : "http",
|
|
},
|
|
}
|
|
}
|
|
|
|
# You might be wondering why our start_vault module, which supports shamir, awskms, and pkcs11 seal
|
|
# types, contains sub-modules that are only used for HSM. Well, each of those seal devices has
|
|
# different requirements and as such we have some seal specific requirements before starting Vault.
|
|
#
|
|
# A Shamir seal key cannot exist until Vault has already started, so this modules responsibility for
|
|
# shamir seals is ensuring that the seal type is passed to the enos_vault_start resource. That's it.
|
|
#
|
|
# Auto-unseal with a KMS requires that we configure the enos_vault_start resource with the correct
|
|
# seal type and the attributes necessary to know which KMS key to use. Vault should automatically
|
|
# unseal if we've given it the correct configuration. As long as Vault is able to access the key
|
|
# in the KMS it should be able to start. That's normally done via roles associated to the target
|
|
# machines, which is outside the scope of this module.
|
|
#
|
|
# Auto-unseal with an HSM and PKCS#11 is more complicated because a shared object library, which is
|
|
# how we interface with the HSM, must be present on each node in order to start Vault. In the real
|
|
# world this means an actual HSM in the same rack or data center as every node in the Vault cluster,
|
|
# but in our case we're creating ephemeral infrastructure for these test scenarios and don't have a
|
|
# real HSM available. We could use CloudHSM or the like, but at the time of writing CloudHSM
|
|
# provisioning takes anywhere from 30 to 60 minutes and costs upwards of $2 dollars an hour. That's
|
|
# far too long and expensive for scenarios we'll run fairly frequently. Instead, we test using a
|
|
# software HSM. Using a software HSM solves the cost and speed problems but creates new set of
|
|
# problems. We need to ensure every node in the cluster has access to the same "HSM" and with
|
|
# softhsm that means the same software, configuration, tokens and keys. Our `seal_pkcs11` module
|
|
# takes care of creating the token and keys, but that's the end of the road for that module. It's
|
|
# our job to ensure that when we're starting Vault with a software HSM that we'll ensure the correct
|
|
# software, configuration and data are available on the nodes. That's where the following two
|
|
# modules come in. They handle installing the required software, configuring it, and distributing
|
|
# the key data that was passed in via seal attributes.
|
|
module "maybe_configure_hsm" {
|
|
source = "../softhsm_distribute_vault_keys"
|
|
count = (var.seal_type == "pkcs11" || var.seal_type_secondary == "pkcs11") ? 1 : 0
|
|
|
|
hosts = var.hosts
|
|
token_base64 = local.token_base64
|
|
}
|
|
|
|
module "maybe_configure_hsm_secondary" {
|
|
source = "../softhsm_distribute_vault_keys"
|
|
depends_on = [module.maybe_configure_hsm]
|
|
count = (var.seal_type == "pkcs11" || var.seal_type_secondary == "pkcs11") ? 1 : 0
|
|
|
|
hosts = var.hosts
|
|
token_base64 = local.token_base64_secondary
|
|
}
|
|
|
|
resource "enos_vault_start" "leader" {
|
|
for_each = local.leader
|
|
depends_on = [
|
|
module.maybe_configure_hsm_secondary,
|
|
]
|
|
|
|
bin_path = local.bin_path
|
|
config_dir = var.config_dir
|
|
config_mode = var.config_mode
|
|
environment = merge(var.environment, {
|
|
VAULT_DISABLE_MLOCK = var.disable_mlock
|
|
})
|
|
config = {
|
|
api_addr = local.api_addrs_internal[tonumber(each.value)][var.ip_version]
|
|
cluster_addr = local.cluster_addrs_internal[tonumber(each.value)][var.ip_version]
|
|
cluster_name = var.cluster_name
|
|
listener = {
|
|
type = "tcp"
|
|
attributes = {
|
|
address = local.listener_address
|
|
tls_disable = "true"
|
|
}
|
|
telemetry = {
|
|
unauthenticated_metrics_access = var.enable_telemetry
|
|
}
|
|
}
|
|
log_level = var.log_level
|
|
storage = {
|
|
type = var.storage_backend
|
|
attributes = local.storage_attributes[each.key]
|
|
retry_join = try(local.storage_retry_join[var.storage_backend], null)
|
|
}
|
|
seals = local.seals
|
|
ui = true
|
|
telemetry = {
|
|
prometheus_retention_time = local.prometheus_retention_time
|
|
disable_hostname = true
|
|
}
|
|
}
|
|
license = var.license
|
|
manage_service = var.manage_service
|
|
username = var.service_username
|
|
unit_name = "vault"
|
|
|
|
transport = {
|
|
ssh = {
|
|
host = var.hosts[each.value].public_ip
|
|
}
|
|
}
|
|
}
|
|
|
|
resource "enos_vault_start" "followers" {
|
|
depends_on = [
|
|
enos_vault_start.leader,
|
|
]
|
|
for_each = local.followers
|
|
|
|
bin_path = local.bin_path
|
|
config_dir = var.config_dir
|
|
config_mode = var.config_mode
|
|
environment = merge(var.environment, {
|
|
VAULT_DISABLE_MLOCK = var.disable_mlock
|
|
})
|
|
config = {
|
|
api_addr = local.api_addrs_internal[tonumber(each.value)][var.ip_version]
|
|
cluster_addr = local.cluster_addrs_internal[tonumber(each.value)][var.ip_version]
|
|
cluster_name = var.cluster_name
|
|
listener = {
|
|
type = "tcp"
|
|
attributes = {
|
|
address = local.listener_address
|
|
tls_disable = "true"
|
|
}
|
|
telemetry = {
|
|
unauthenticated_metrics_access = var.enable_telemetry
|
|
}
|
|
}
|
|
log_level = var.log_level
|
|
storage = {
|
|
type = var.storage_backend
|
|
attributes = { for key, value in local.storage_attributes[each.key] : key => value }
|
|
retry_join = try(local.storage_retry_join[var.storage_backend], null)
|
|
}
|
|
seals = local.seals
|
|
ui = true
|
|
telemetry = {
|
|
prometheus_retention_time = local.prometheus_retention_time
|
|
disable_hostname = true
|
|
}
|
|
}
|
|
license = var.license
|
|
manage_service = var.manage_service
|
|
username = var.service_username
|
|
unit_name = "vault"
|
|
|
|
transport = {
|
|
ssh = {
|
|
host = var.hosts[each.value].public_ip
|
|
}
|
|
}
|
|
}
|
|
|
|
output "token_base64" {
|
|
value = local.token_base64
|
|
}
|
|
|
|
output "token_base64_secondary" {
|
|
value = local.token_base64_secondary
|
|
}
|