From 66b48ad94652763dcfc7f2d0add0d0622c8e3cbb Mon Sep 17 00:00:00 2001
From: Sotiris Tsepelakis <sotirios.tsepelakis@tuwien.ac.at>
Date: Thu, 10 Apr 2025 19:00:00 +0200
Subject: [PATCH] =?UTF-8?q?=F0=9F=94=90=20Add=20initial=20Vault=20setup?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .gitignore                    |  6 +++
 docker-compose.yml            | 26 +++++++++++++
 docker/vault/config/vault.hcl | 17 +++++++++
 docker/vault/logs/.gitkeep    |  0
 scripts/init.sh               | 69 +++++++++++++++++++++++++++++++++++
 5 files changed, 118 insertions(+)
 create mode 100644 docker-compose.yml
 create mode 100644 docker/vault/config/vault.hcl
 create mode 100644 docker/vault/logs/.gitkeep
 create mode 100755 scripts/init.sh

diff --git a/.gitignore b/.gitignore
index e69de29..9cf0015 100644
--- a/.gitignore
+++ b/.gitignore
@@ -0,0 +1,6 @@
+# The secrets file
+.env
+
+# Logs
+docker/vault/logs/*
+!docker/vault/logs/.gitkeep
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..beb3945
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,26 @@
+services:
+  vault:
+    image: hashicorp/vault:1.19
+    container_name: "crdm-vault"
+    restart: "unless-stopped"
+    cap_add:
+      - IPC_LOCK
+    environment:
+      VAULT_ADDR: ${VAULT_ADDR:-http://vault:8200}
+      VAULT_API_ADDR: ${VAULT_API_ADDR:-${VAULT_ADDR:-http://vault:8200}}
+      VAULT_LOG_LEVEL: ${VAULT_LOG_LEVEL:-INFO}
+    ports:
+      - "80:8200"
+    volumes:
+      - vault-data:/vault/file
+      - ./docker/vault/config:/vault/config
+      - ./docker/vault/logs:/vault/logs
+    entrypoint: ["/bin/sh", "-c", "vault server -config=/vault/config/vault.hcl"]
+    healthcheck:
+      interval: 30s
+      timeout: 15s
+      retries: 5
+      start_period: 5s
+
+volumes:
+  vault-data:
diff --git a/docker/vault/config/vault.hcl b/docker/vault/config/vault.hcl
new file mode 100644
index 0000000..80f87f6
--- /dev/null
+++ b/docker/vault/config/vault.hcl
@@ -0,0 +1,17 @@
+storage "file" {
+  path = "/vault/file" # This volume is defined in the base image
+}
+
+listener "tcp" {
+  address       = "0.0.0.0:8200"
+  tls_disable   = "true"
+}
+
+ui = true
+disable_mlock = true
+
+# Logging
+log_file = "/vault/logs/vault.log"
+log_rotate_bytes = "200000000"     # 200 MB per file
+log_rotate_duration = "168h"       # 1 week per file (whichever comes first: size or time)
+log_rotate_max_files = "6"         # Keep the latest 6 files
diff --git a/docker/vault/logs/.gitkeep b/docker/vault/logs/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/scripts/init.sh b/scripts/init.sh
new file mode 100755
index 0000000..6e3717a
--- /dev/null
+++ b/scripts/init.sh
@@ -0,0 +1,69 @@
+#!/bin/bash
+#
+# Script for initializing and unsealing the Vault.
+#
+# Usage: ./scripts/init.sh
+#
+
+set -euo pipefail
+
+# The guard below ensures, in most cases, that the script is executed from the correct directory (project directory).
+# So it should be safe to assume that we can extract the path from the parent of the 'scripts' directory.
+project_dir="$(dirname "$(dirname "$(realpath "$0")")")"
+
+# ---------------- #
+# Execution checks #
+# ---------------- #
+if [[ ! -f "docker-compose.yml" ]]; then
+    echo >&2 "[ERROR] This script needs to be executed from the project directory!"
+    exit 1
+fi
+
+if docker compose exec vault vault status | grep -q "Initialized.*true"; then
+    echo "[INFO] Vault is already initialized. Skipping setup."
+    exit 0
+fi
+
+# --------------------- #
+# Environment variables #
+# --------------------- #
+env_file="${project_dir}/.env"
+
+# Check if the .env file exists and source it.
+if [[ -f "${env_file}" ]]; then
+    echo "[INFO] Detected .env file, sourcing it."
+    source "${env_file}"
+fi
+
+key_shares="${VAULT_KEY_SHARES:-7}"
+key_threshold="${VAULT_KEY_THRESHOLD:-5}"
+
+# ------------ #
+# Run sequence #
+# ------------ #
+vault_dir="${project_dir}/docker/vault"
+logs_dir="${vault_dir}/logs"
+scripts_dir="${vault_dir}/scripts"
+
+echo "[INFO] Initializing Vault."
+docker compose exec vault vault operator init -key-shares="${key_shares}" -key-threshold="${key_threshold}" > "${logs_dir}/init.txt"
+
+# Extract Unseal Keys & Root Token.
+awk '/Unseal Key [0-9]+:/ {print $NF}' "${logs_dir}/init.txt" > "${logs_dir}/unseal-keys.txt"
+root_token=$(awk '/Initial Root Token:/ {print $NF}' "${logs_dir}/init.txt")
+echo "${root_token} # Root Token: Keep this secure!" > "${logs_dir}/tokens.txt"
+
+# Unseal Vault.
+count=1
+for i in $(head -n "${key_threshold}" "${logs_dir}/unseal-keys.txt"); do
+    echo "[INFO] Unsealing Vault (${count}/${key_threshold})..."
+    docker compose exec vault vault operator unseal "$i" >/dev/null 2>&1
+    ((count++))
+done
+
+# Secure permissions for the files containing the secrets.
+for file in "${logs_dir}"/*.txt; do
+  [[ -e "$file" ]] && chmod 600 "$file"
+done
+
+echo "[INFO] Vault Initialization complete."
-- 
GitLab