VAULT-9688 Vault Agent Enos test (#17837)

* VAULT-9688 First attempt at Vault Agent Enos test

* VAULT-9688 remove TODO, correct indentation

* VAULT-9688 enos fmt

* VAULT-9688 terraform fmt

* VAULT-9688 small updates

* VAULT-9688 add extra comment
This commit is contained in:
Violet Hynes 2022-11-08 10:08:43 -05:00 committed by GitHub
parent d2e0f771ef
commit a7028a9d64
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 458 additions and 0 deletions

View File

@ -51,6 +51,20 @@ module "read_license" {
source = "./modules/read_license"
}
module "vault_agent" {
source = "./modules/vault_agent"
vault_install_dir = var.vault_install_dir
vault_instance_count = var.vault_instance_count
}
module "vault_verify_agent_output" {
source = "./modules/vault_verify_agent_output"
vault_instance_count = var.vault_instance_count
}
module "vault_cluster" {
source = "app.terraform.io/hashicorp-qti/aws-vault/enos"
# source = "../../terraform-enos-aws-vault"

View File

@ -0,0 +1,219 @@
scenario "agent" {
matrix {
arch = ["amd64", "arm64"]
artifact_source = ["local", "crt", "artifactory"]
distro = ["ubuntu", "rhel"]
edition = ["oss", "ent"]
}
terraform_cli = terraform_cli.default
terraform = terraform.default
providers = [
provider.aws.default,
provider.enos.ubuntu,
provider.enos.rhel
]
locals {
build_tags = {
"oss" = ["ui"]
"ent" = ["enterprise", "ent"]
}
bundle_path = matrix.artifact_source != "artifactory" ? abspath(var.vault_bundle_path) : null
dependencies_to_install = ["jq"]
enos_provider = {
rhel = provider.enos.rhel
ubuntu = provider.enos.ubuntu
}
install_artifactory_artifact = local.bundle_path == null
tags = merge({
"Project Name" : var.project_name
"Project" : "Enos",
"Environment" : "ci"
}, var.tags)
vault_instance_types = {
amd64 = "t3a.small"
arm64 = "t4g.small"
}
vault_instance_type = coalesce(var.vault_instance_type, local.vault_instance_types[matrix.arch])
}
step "get_local_metadata" {
skip_step = matrix.artifact_source != "local"
module = module.get_local_metadata
}
step "build_vault" {
module = "build_${matrix.artifact_source}"
variables {
build_tags = try(var.vault_local_build_tags, local.build_tags[matrix.edition])
bundle_path = local.bundle_path
goarch = matrix.arch
goos = "linux"
artifactory_host = matrix.artifact_source == "artifactory" ? var.artifactory_host : null
artifactory_repo = matrix.artifact_source == "artifactory" ? var.artifactory_repo : null
artifactory_username = matrix.artifact_source == "artifactory" ? var.artifactory_username : null
artifactory_token = matrix.artifact_source == "artifactory" ? var.artifactory_token : null
arch = matrix.artifact_source == "artifactory" ? matrix.arch : null
vault_product_version = var.vault_product_version
artifact_type = matrix.artifact_source == "artifactory" ? var.vault_artifact_type : null
distro = matrix.artifact_source == "artifactory" ? matrix.distro : null
edition = matrix.artifact_source == "artifactory" ? matrix.edition : null
instance_type = matrix.artifact_source == "artifactory" ? local.vault_instance_type : null
revision = var.vault_revision
}
}
step "find_azs" {
module = module.az_finder
variables {
instance_type = [
var.backend_instance_type,
local.vault_instance_type
]
}
}
step "create_vpc" {
module = module.create_vpc
variables {
ami_architectures = [matrix.arch]
availability_zones = step.find_azs.availability_zones
common_tags = local.tags
}
}
step "read_license" {
skip_step = matrix.edition == "oss"
module = module.read_license
variables {
file_name = abspath(joinpath(path.root, "./support/vault.hclic"))
}
}
step "create_backend_cluster" {
module = "backend_raft"
depends_on = [step.create_vpc]
providers = {
enos = provider.enos.ubuntu
}
variables {
ami_id = step.create_vpc.ami_ids["ubuntu"][matrix.arch]
common_tags = local.tags
instance_type = var.backend_instance_type
kms_key_arn = step.create_vpc.kms_key_arn
vpc_id = step.create_vpc.vpc_id
}
}
step "create_vault_cluster" {
module = module.vault_cluster
depends_on = [
step.create_backend_cluster,
step.build_vault,
]
providers = {
enos = local.enos_provider[matrix.distro]
}
variables {
ami_id = step.create_vpc.ami_ids[matrix.distro][matrix.arch]
common_tags = local.tags
consul_cluster_tag = step.create_backend_cluster.consul_cluster_tag
dependencies_to_install = local.dependencies_to_install
instance_type = local.vault_instance_type
kms_key_arn = step.create_vpc.kms_key_arn
storage_backend = "raft"
unseal_method = "shamir"
vault_local_artifact_path = local.bundle_path
vault_artifactory_release = local.install_artifactory_artifact ? step.build_vault.vault_artifactory_release : null
vault_license = matrix.edition != "oss" ? step.read_license.license : null
vpc_id = step.create_vpc.vpc_id
}
}
step "start_vault_agent" {
module = "vault_agent"
depends_on = [
step.create_backend_cluster,
step.build_vault,
step.create_vault_cluster,
]
providers = {
enos = local.enos_provider[matrix.distro]
}
variables {
vault_instances = step.create_vault_cluster.vault_instances
vault_root_token = step.create_vault_cluster.vault_root_token
vault_agent_template_destination = "/tmp/agent_output.txt"
vault_agent_template_contents = "{{ with secret \\\"auth/token/lookup-self\\\" }}orphan={{ .Data.orphan }} display_name={{ .Data.display_name }}{{ end }}"
}
}
step "verify_vault_agent_output" {
module = module.vault_verify_agent_output
depends_on = [
step.create_vault_cluster,
step.start_vault_agent,
]
providers = {
enos = local.enos_provider[matrix.distro]
}
variables {
vault_instances = step.create_vault_cluster.vault_instances
vault_agent_template_destination = "/tmp/agent_output.txt"
vault_agent_expected_output = "orphan=true display_name=approle"
}
}
output "vault_cluster_instance_ids" {
description = "The Vault cluster instance IDs"
value = step.create_vault_cluster.instance_ids
}
output "vault_cluster_pub_ips" {
description = "The Vault cluster public IPs"
value = step.create_vault_cluster.instance_public_ips
}
output "vault_cluster_priv_ips" {
description = "The Vault cluster private IPs"
value = step.create_vault_cluster.instance_private_ips
}
output "vault_cluster_key_id" {
description = "The Vault cluster Key ID"
value = step.create_vault_cluster.key_id
}
output "vault_cluster_root_token" {
description = "The Vault cluster root token"
value = step.create_vault_cluster.vault_root_token
}
output "vault_cluster_unseal_keys_b64" {
description = "The Vault cluster unseal keys"
value = step.create_vault_cluster.vault_unseal_keys_b64
}
output "vault_cluster_unseal_keys_hex" {
description = "The Vault cluster unseal keys hex"
value = step.create_vault_cluster.vault_unseal_keys_hex
}
output "vault_cluster_tag" {
description = "The Vault cluster tag"
value = step.create_vault_cluster.vault_cluster_tag
}
}

View File

@ -0,0 +1,67 @@
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
}
enos = {
source = "app.terraform.io/hashicorp-qti/enos"
}
}
}
variable "vault_agent_template_destination" {
type = string
description = "The destination of the template rendered by Agent"
}
variable "vault_agent_template_contents" {
type = string
description = "The template contents to be rendered by Agent"
}
variable "vault_root_token" {
type = string
description = "The Vault root token"
}
variable "vault_instances" {
type = map(object({
private_ip = string
public_ip = string
}))
description = "The Vault cluster instances that were created"
}
variable "vault_instance_count" {
type = number
description = "How many vault instances are in the cluster"
}
variable "vault_install_dir" {
type = string
description = "The directory where the Vault binary will be installed"
}
locals {
vault_instances = {
for idx in range(var.vault_instance_count) : idx => {
public_ip = values(var.vault_instances)[idx].public_ip
private_ip = values(var.vault_instances)[idx].private_ip
}
}
}
resource "enos_remote_exec" "set_up_approle_auth_and_agent" {
content = templatefile("${path.module}/templates/set-up-approle-and-agent.sh", {
vault_install_dir = var.vault_install_dir
vault_token = var.vault_root_token
vault_agent_template_destination = var.vault_agent_template_destination
vault_agent_template_contents = var.vault_agent_template_contents
})
transport = {
ssh = {
host = local.vault_instances[0].public_ip
}
}
}

View File

@ -0,0 +1,92 @@
#!/usr/bin/env bash
set -e
binpath=${vault_install_dir}/vault
fail() {
echo "$1" 1>&2
return 1
}
test -x "$binpath" || fail "unable to locate vault binary at $binpath"
export VAULT_ADDR='http://127.0.0.1:8200'
export VAULT_TOKEN='${vault_token}'
# If approle was already enabled, disable it as we're about to re-enable it (the || true is so we don't fail if it doesn't already exist)
$binpath auth disable approle || true
approle_create_status=$($binpath auth enable approle)
approle_status=$($binpath write auth/approle/role/agent-role secret_id_ttl=700h token_num_uses=1000 token_ttl=600h token_max_ttl=700h secret_id_num_uses=1000)
ROLEID=$($binpath read --format=json auth/approle/role/agent-role/role-id | jq -r '.data.role_id')
if [[ "$ROLEID" == '' ]]; then
fail "expected ROLEID to be nonempty, but it is empty"
fi
SECRETID=$($binpath write -f --format=json auth/approle/role/agent-role/secret-id | jq -r '.data.secret_id')
if [[ "$SECRETID" == '' ]]; then
fail "expected SECRETID to be nonempty, but it is empty"
fi
echo $ROLEID > /tmp/role-id
echo $SECRETID > /tmp/secret-id
cat > /tmp/vault-agent.hcl <<- EOM
pid_file = "/tmp/pidfile"
vault {
address = "http://127.0.0.1:8200"
tls_skip_verify = true
retry {
num_retries = 10
}
}
cache {
enforce_consistency = "always"
use_auto_auth_token = true
}
listener "tcp" {
address = "127.0.0.1:8100"
tls_disable = true
}
template {
destination = "${vault_agent_template_destination}"
contents = "${vault_agent_template_contents}"
exec {
command = "pkill -F /tmp/pidfile"
}
}
auto_auth {
method {
type = "approle"
config = {
role_id_file_path = "/tmp/role-id"
secret_id_file_path = "/tmp/secret-id"
}
}
sink {
type = "file"
config = {
path = "/tmp/token"
}
}
}
EOM
# If Agent is still running from a previous run, kill it
pkill -F /tmp/pidfile || true
# If the template file already exists, remove it
rm ${vault_agent_template_destination} || true
# Run agent (it will kill itself when it finishes rendering the template)
$binpath agent -config=/tmp/vault-agent.hcl > /tmp/agent-logs.txt 2>&1

View File

@ -0,0 +1,53 @@
terraform {
required_providers {
enos = {
source = "app.terraform.io/hashicorp-qti/enos"
}
}
}
variable "vault_agent_template_destination" {
type = string
description = "The destination of the template rendered by Agent"
}
variable "vault_agent_expected_output" {
type = string
description = "The output that's expected in the rendered template at vault_agent_template_destination"
}
variable "vault_instance_count" {
type = number
description = "How many vault instances are in the cluster"
}
variable "vault_instances" {
type = map(object({
private_ip = string
public_ip = string
}))
description = "The vault cluster instances that were created"
}
locals {
vault_instances = {
for idx in range(var.vault_instance_count) : idx => {
public_ip = values(var.vault_instances)[idx].public_ip
private_ip = values(var.vault_instances)[idx].private_ip
}
}
}
resource "enos_remote_exec" "verify_vault_agent_output" {
content = templatefile("${path.module}/templates/verify-vault-agent-output.sh", {
vault_agent_template_destination = var.vault_agent_template_destination
vault_agent_expected_output = var.vault_agent_expected_output
vault_instances = jsonencode(local.vault_instances)
})
transport = {
ssh = {
host = local.vault_instances[0].public_ip
}
}
}

View File

@ -0,0 +1,13 @@
#!/usr/bin/env bash
set -e
fail() {
echo "$1" 1>&2
return 1
}
actual_output=$(cat ${vault_agent_template_destination})
if [[ "$actual_output" != "${vault_agent_expected_output}" ]]; then
fail "expected '${vault_agent_expected_output}' to be the Agent output, but got: '$actual_output'"
fi