Deploying Vault with a Custom AMI

2022-04-02

In this article, we'll walk through creating a custom Amazon Machine Image (AMI) with HashiCorp Vault baked in using Packer, launching an EC2 instance with the generated AMI, and then configuring Vault to store and access secrets.

Overview

This guide covers:

  1. Baking the Vault AMI with Packer: Cloning the repository and running Packer to generate the AMI.
  2. Launching the Vault EC2 Instance: Using the created AMI to spin up an EC2 instance.
  3. Configuring Vault: Initializing, unsealing, and verifying Vault on the instance.
  4. Storing and Accessing Secrets: A brief walkthrough on adding and retrieving secrets with Vault.

Prerequisites

  • An AWS account with appropriate IAM permissions.
  • Packer installed locally.
  • Optionally, the AWS CLI or Terraform to launch EC2 instances.
  • Basic familiarity with HashiCorp Vault and AWS concepts.

Step 1: Baking the Vault AMI with Packer

touch vault-ami.pkr.hcl

Step 2: Build the AMI

packer {
  required_plugins {
    amazon = {
      version = ">= 1.0.0"
      source  = "github.com/hashicorp/amazon"
    }
  }
}


variable "aws_region" {
  default = "us-east-1"
}

variable "ami_name" {
  default = "vault-server-ami"
}

variable "instance_type" {
  default = "t3.micro"
}

variable "source_ami" {
  default = "ami-090fa75af13c156b4" # Amazon Linux 2 (Change if using Ubuntu)
}

variable "ssh_username" {
  default = "ec2-user" # Use "ubuntu" for Ubuntu AMIs
}

# AWS Builder Configuration
source "amazon-ebs" "vault_ami" {
  region                      = var.aws_region
  source_ami                  = var.source_ami
  instance_type               = var.instance_type
  ssh_username                = var.ssh_username
  ami_name                    = var.ami_name
  ami_description             = "Pre-configured Vault AMI"
  associate_public_ip_address = true
  tags = {
    Name = "Vault AMI"
  }
}

# Provisioners (Runs the Vault install script)
build {
  sources = ["source.amazon-ebs.vault_ami"]

  provisioner "file" {
    source      = "install_vault.sh"
    destination = "/tmp/install_vault.sh"
  }

  provisioner "shell" {
    inline = [
      "chmod +x /tmp/install_vault.sh",
      "sudo /tmp/install_vault.sh"
    ]
  }
}

Create the install script

#!/bin/sh

# Download and move latest Vault release to bin.
printf "\n\nFetching Vault binary"
cd /opt/ && sudo curl -o vault.zip https://releases.hashicorp.com/vault/1.13.1/vault_1.13.1_linux_amd64.zip
sudo unzip vault.zip
sudo mv vault /usr/bin/

# Create a user named vault to be run as a service.
printf "\n\nCreating vault user"
sudo useradd --system --home /etc/vault.d --shell /bin/false vault

# Configure Vault as a System Service
printf "\n\nConfiguring vault as system service"
sudo wget https://raw.githubusercontent.com/moabukar/vault-ami/main/non-tls/non-tls-vault.service
sudo mv  non-tls-vault.service /etc/systemd/system/vault.service
sudo mkdir /etc/vault.d
sudo chown -R vault:vault /etc/vault.d
sudo mkdir /vault-data
sudo chown -R vault:vault /vault-data
sudo mkdir -p /logs/vault/

sudo wget https://raw.githubusercontent.com/moabukar/vault-ami/main/non-tls/non-tls.hcl
sudo mv non-tls.hcl /etc/vault.d/vault.hcl

# Start vault as a service
printf "\n\nEnabling vault as a service"
sudo systemctl enable vault
sudo systemctl start vault
sudo systemctl status vault

Review the Packer configuration file (for example, vault-pkr.hcl) to see how Vault is installed and configured. Then, build the AMI using Packer:

packer build vault-ami.pkr.hcl

Note: If the configuration file has a different name (for example, packer.hcl), use that filename.

Packer will run through the steps (e.g., installing Vault, applying configuration, etc.) and output an AMI ID in your AWS account.

Step 2: Launching the Vault EC2 Instance

With the AMI baked, launch an EC2 instance using the new AMI. You can use the AWS Management Console, CLI, or Terraform. For example, using Terraform

provider "aws" {
  region = "eu-west-2"
}

variable "ami_id" {
  default = "ami-010b1877662d8747c"
}

resource "aws_instance" "vault" {
  ami                    = var.ami_id
  instance_type          = "t3.micro"
  key_name               = "vault-dev"
  vpc_security_group_ids = ["sg-06d59bbad808fc1ee"]


  tags = {
    Name = "Vault Server"
  }
}

output "vault_public_ip" {
  value = aws_instance.vault.public_ip
}

output "ssh_command" {
  description = "SSH into your Vault server"
  value       = format("ssh -i vault-dev.pem ec2-user@%s", aws_instance.vault.public_ip)
}

Replace ami-xxxxxxxx with the AMI ID from the Packer build, and update the key name, security group, and subnet ID as appropriate.

Step 3: Configuring Vault on the Instance

Once your EC2 instance is up, SSH into it:

ssh -i your-keypair.pem ec2-user@<instance-public-ip>

Verify that Vault is installed:

vault --version

Next, initialize Vault. Important: The output will include your unseal key and root token—store these securely.

vault operator init -key-shares=1 -key-threshold=1

Then, unseal Vault using the unseal key provided:

vault operator unseal <unseal-key>

Once unsealed, you can log in with the root token:

vault login <root-token>

Step 4: Storing and Accessing Secrets Now that Vault is running, you can store secrets. For example, to store a database password:

vault kv put secret/db password="SuperSecret123!"

To retrieve the secret:

vault kv get secret/db

This demonstrates how you can securely manage secrets using Vault.

Conclusion

You now have a full walkthrough to bake a Vault AMI using Packer, launch an EC2 instance with that AMI, configure Vault and start managing secrets securely. This approach streamlines deploying Vault in AWS and is a great addition to any DevOps workflow.

Related Posts