Deploying HashiCorp Vault to Various Cloud Using Terraform

28 Dec
  1. Introduction and Fundamentals of HashiCorp Vault
  2. Advanced Configuration and Management of HashiCorp Vault
  3. Security Best Practices and Compliance: Safeguarding HashiCorp Vault Deployments
  4. Integration, Automation, and DevOps: Elevating HashiCorp Vault Deployments
  5. Deploying HashiCorp Vault to Various Cloud Using Terraform

Introduction

In today’s rapidly evolving cloud-centric landscape, managing and securing sensitive data is paramount. HashiCorp Vault emerges as a cornerstone in this arena, providing a robust solution for secret management, data encryption, and identity-based access control. Leveraging Infrastructure as Code (IaC) methodologies, Terraform offers a streamlined approach to automate and orchestrate Vault deployments across various cloud platforms. This integration not only ensures consistency and scalability but also fortifies the security posture by reducing human error and enhancing auditability. In this article, we will delve into the intricacies of deploying HashiCorp Vault to diverse cloud environments, harnessing the power and flexibility of Terraform.

Deploy on AWS EC2 Instance

Following is an example of a Vault deployment configuration tailored for a global bank, emphasizing high availability, secure secrets management, and compliance with financial regulations such as PCI DSS, SWIFT CSP, and FISMA.

# Define Vault AWS EC2 Instance Configuration
resource "aws_instance" "vault_server" {
  ami           = "ami-0c55b159cbfafe1f0" # Replace with the appropriate AMI ID
  instance_type = "t2.medium"
  subnet_id     = "subnet-12345678"      # Replace with the appropriate subnet ID
  key_name      = "vault_key_pair"      # Replace with your SSH key pair
  tags = {
    Name = "VaultServer"
  }
}

# Define Security Group Rules for Vault Server
resource "aws_security_group" "vault_sg" {
  name        = "vault_sg"
  description = "Security group for Vault Server"

  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    from_port   = 8200
    to_port     = 8200
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  # Additional ingress/egress rules can be added as needed
}

# Provision Vault Server
resource "null_resource" "initialize_vault" {
  provisioner "remote-exec" {
    inline = [
      "curl -o /tmp/vault.zip https://releases.hashicorp.com/vault/1.8.0/vault_1.8.0_linux_amd64.zip",
      "unzip /tmp/vault.zip -d /tmp",
      "sudo mv /tmp/vault /usr/local/bin/",
      "sudo chmod 755 /usr/local/bin/vault",
      "vault --version"
    ]

    connection {
      type     = "ssh"
      user     = "ec2-user"
      private_key = file("~/.ssh/vault_key.pem")
      host     = aws_instance.vault_server.public_ip
    }
  }
}

# Initialize and Unseal Vault
resource "null_resource" "initialize_and_unseal_vault" {
  depends_on = [null_resource.initialize_vault]

  provisioner "remote-exec" {
    inline = [
      "export VAULT_ADDR='http://localhost:8200'",
      "vault operator init -key-shares=1 -key-threshold=1 > /tmp/init_output.txt",
      "UNSEAL_KEY=$(grep 'Unseal Key 1' /tmp/init_output.txt | awk '{print $NF}')",
      "ROOT_TOKEN=$(grep 'Initial Root Token' /tmp/init_output.txt | awk '{print $NF}')",
      "vault operator unseal ${UNSEAL_KEY}",
      "vault login ${ROOT_TOKEN}"
    ]

    connection {
      type     = "ssh"
      user     = "ec2-user"
      private_key = file("~/.ssh/vault_key.pem")
      host     = aws_instance.vault_server.public_ip
    }
  }
}

This configuration sets up an AWS EC2 instance, provisions a security group allowing SSH and Vault’s API port (8200), and initializes a Vault server with the required unseal key and root token. You’ll need to replace placeholder values like AMI ID, subnet ID, and key pair name with actual values relevant to your AWS environment.

Deploy on Google Cloud Compute Engine

Configuring Vault on Google Cloud Platform (GCP) involves provisioning a Compute Engine instance, setting up necessary firewall rules, and initializing Vault with relevant configurations. Below is a Terraform configuration that demonstrates how to set up a Vault instance on GCP.

First, make sure you have the necessary provider configuration for GCP in your Terraform setup. Here’s a basic example of how to set up the provider:

provider "google" {
  credentials = file("<PATH_TO_YOUR_SERVICE_ACCOUNT_JSON>")
  project     = "<YOUR_PROJECT_ID>"
  region      = "us-central1"
}

Now, let’s proceed with the Vault configuration:

# Define Vault GCP Compute Engine Instance Configuration
resource "google_compute_instance" "vault_server" {
  name         = "vault-instance"
  machine_type = "n1-standard-1"
  zone         = "us-central1-a"

  boot_disk {
    initialize_params {
      image = "ubuntu-1804-lts"
    }
  }

  network_interface {
    network = "default"

    access_config {
      // Ephemeral IP
    }
  }

  tags = ["vault-server"]
}

# Define Firewall Rule for Vault Server
resource "google_compute_firewall" "vault_fw" {
  name    = "allow-vault"
  network = "default"

  allow {
    protocol = "tcp"
    ports    = ["22", "8200"]
  }

  source_ranges = ["0.0.0.0/0"]

  target_tags = ["vault-server"]
}

# Provision Vault Server
resource "null_resource" "initialize_vault" {
  provisioner "remote-exec" {
    inline = [
      "curl -o /tmp/vault.zip https://releases.hashicorp.com/vault/1.8.0/vault_1.8.0_linux_amd64.zip",
      "unzip /tmp/vault.zip -d /tmp",
      "sudo mv /tmp/vault /usr/local/bin/",
      "sudo chmod 755 /usr/local/bin/vault",
      "vault --version"
    ]

    connection {
      type        = "ssh"
      user        = "ubuntu"
      private_key = file("~/.ssh/google_compute_engine")
      host        = google_compute_instance.vault_server.network_interface[0].access_config[0].nat_ip
    }
  }
}

# Initialize and Unseal Vault
resource "null_resource" "initialize_and_unseal_vault" {
  depends_on = [null_resource.initialize_vault]

  provisioner "remote-exec" {
    inline = [
      "export VAULT_ADDR='http://localhost:8200'",
      "vault operator init -key-shares=1 -key-threshold=1 > /tmp/init_output.txt",
      "UNSEAL_KEY=$(grep 'Unseal Key 1' /tmp/init_output.txt | awk '{print $NF}')",
      "ROOT_TOKEN=$(grep 'Initial Root Token' /tmp/init_output.txt | awk '{print $NF}')",
      "vault operator unseal ${UNSEAL_KEY}",
      "vault login ${ROOT_TOKEN}"
    ]

    connection {
      type        = "ssh"
      user        = "ubuntu"
      private_key = file("~/.ssh/google_compute_engine")
      host        = google_compute_instance.vault_server.network_interface[0].access_config[0].nat_ip
    }
  }
}

This configuration sets up a Vault instance on GCP using a Compute Engine instance. It creates a firewall rule to allow SSH and Vault API (8200) traffic and then initializes Vault with the necessary configurations. Make sure to replace placeholder values like service account JSON path, project ID, and any others as necessary.

Deploy on Google Cloud GKE

Configuring Vault on Google Kubernetes Engine (GKE) involves deploying Vault as a Pod, setting up necessary Kubernetes resources, and initializing Vault with relevant configurations. Below is a sample Terraform configuration that demonstrates how to set up Vault on GKE:

First, make sure you have the necessary provider configuration for GCP and Kubernetes in your Terraform setup. Here’s an example of setting up the providers:

provider "google" {
  credentials = file("<PATH_TO_YOUR_SERVICE_ACCOUNT_JSON>")
  project     = "<YOUR_PROJECT_ID>"
  region      = "us-central1"
}

provider "kubernetes" {
  config_path = "~/.kube/config" # Replace with the path to your kubeconfig file
}

Now, let’s proceed with the Vault configuration on GKE:

# Define Google Kubernetes Engine Cluster Configuration
resource "google_container_cluster" "primary" {
  name     = "vault-cluster"
  location = "us-central1-a"

  remove_default_node_pool = true

  initial_node_count = 1

  master_auth {
    username = ""
    password = ""

    client_certificate_config {
      issue_client_certificate = false
    }
  }
}

# Define Kubernetes Namespace for Vault
resource "kubernetes_namespace" "vault_namespace" {
  metadata {
    name = "vault-namespace"
  }
}

# Define Vault Deployment
resource "kubernetes_deployment" "vault" {
  metadata {
    name      = "vault"
    namespace = kubernetes_namespace.vault_namespace.metadata[0].name
  }

  spec {
    replicas = 1

    selector {
      match_labels = {
        app = "vault"
      }
    }

    template {
      metadata {
        labels = {
          app = "vault"
        }
      }

      spec {
        container {
          image = "vault:1.8.0" # Replace with the desired Vault version
          name  = "vault"

          env {
            name  = "VAULT_ADDR"
            value = "http://localhost:8200"
          }

          ports {
            container_port = 8200
          }

          # Add additional Vault configuration as needed
        }
      }
    }
  }
}

# Define Vault Service
resource "kubernetes_service" "vault_service" {
  metadata {
    name      = "vault-service"
    namespace = kubernetes_namespace.vault_namespace.metadata[0].name
  }

  spec {
    selector = {
      app = "vault"
    }

    port {
      port        = 8200
      target_port = 8200
    }
  }
}

# Define Terraform null_resource for Vault initialization and unsealing
resource "null_resource" "vault_init_unseal" {
  provisioner "local-exec" {
    command = <<-EOT
      #!/bin/bash

      # Initialize Vault
      init_response=$(kubectl exec -n vault-namespace $(kubectl get pods -n vault-namespace -l app=vault -o jsonpath='{.items[0].metadata.name}') -- vault operator init -key-shares=1 -key-threshold=1)

      # Extract Unseal Key and Initial Root Token
      unseal_key=$(echo "$init_response" | grep 'Unseal Key 1:' | awk '{print $NF}')
      root_token=$(echo "$init_response" | grep 'Initial Root Token:' | awk '{print $NF}')

      echo "Unseal Key: $unseal_key"
      echo "Root Token: $root_token"

      # Unseal Vault
      kubectl exec -n vault-namespace $(kubectl get pods -n vault-namespace -l app=vault -o jsonpath='{.items[0].metadata.name}') -- vault operator unseal $unseal_key

      # Login to Vault
      kubectl exec -n vault-namespace $(kubectl get pods -n vault-namespace -l app=vault -o jsonpath='{.items[0].metadata.name}') -- vault login $root_token

      echo "Vault initialized and unsealed successfully!"
    EOT
  }

  depends_on = [kubernetes_deployment.vault] # Ensure the Vault Pod is running before executing the script
}

This Terraform configuration sets up a GKE cluster, deploys a Vault Pod within a specified namespace, and exposes Vault through a Kubernetes service. Note that initializing and unsealing Vault on GKE might require additional steps and considerations, such as storage backends, secret engines, and security configurations. Make sure to replace placeholder values like service account JSON path, project ID, and Vault image version as necessary. By adding the null_resource with the local-exec provisioner, Terraform will execute the embedded Bash script to initialize and unseal Vault whenever the associated resources (kubernetes_deployment.vault in this example) are created or modified.

Conclusion

Deploying HashiCorp Vault to multiple cloud platforms using Terraform encapsulates the synergy of cutting-edge technologies and best practices in cloud orchestration and security. By adopting this approach, organizations can achieve a unified, automated, and scalable Vault deployment strategy that aligns with modern DevOps and cloud-native paradigms. However, it is imperative to continuously evaluate and adapt the deployment strategies to cater to evolving cloud services, compliance requirements, and security threats. As HashiCorp Vault and Terraform continue to evolve, staying abreast of the latest features, best practices, and community insights will be crucial for harnessing the full potential of this powerful combination in securing the digital landscape.



Leave a Reply

Your email address will not be published. Required fields are marked *