From 90a44bd59f24dd7fcbca240d6bbe12a49eaf5056 Mon Sep 17 00:00:00 2001 From: Jan Novak Date: Wed, 14 Jan 2026 14:48:09 +0100 Subject: [PATCH] vault: deployment manifest, some docs, backup script - expected to run on docker host --- docker-30/vault/backup.md | 35 +++++++++++++ docker-30/vault/config.json | 20 ++++++++ docker-30/vault/docker-compose.yaml | 20 ++++++++ docker-30/vault/readme.md | 38 ++++++++++++++ docker-30/vault/terraform/main.tf | 49 +++++++++++++++++++ docker-30/vault/terraform/terraform.tfstate | 1 + .../vault/terraform/terraform.tfstate.backup | 1 + docker-30/vault/terraform/versions.tf | 12 +++++ docker-30/vault/vault-backup.sh | 38 ++++++++++++++ 9 files changed, 214 insertions(+) create mode 100644 docker-30/vault/backup.md create mode 100644 docker-30/vault/config.json create mode 100644 docker-30/vault/docker-compose.yaml create mode 100644 docker-30/vault/readme.md create mode 100644 docker-30/vault/terraform/main.tf create mode 100644 docker-30/vault/terraform/terraform.tfstate create mode 100644 docker-30/vault/terraform/terraform.tfstate.backup create mode 100644 docker-30/vault/terraform/versions.tf create mode 100644 docker-30/vault/vault-backup.sh diff --git a/docker-30/vault/backup.md b/docker-30/vault/backup.md new file mode 100644 index 0000000..a0b591a --- /dev/null +++ b/docker-30/vault/backup.md @@ -0,0 +1,35 @@ +## vault-cli install + +```bash +VAULT_VERSION="1.21.2" +wget https://releases.hashicorp.com/vault/${VAULT_VERSION}/vault_${VAULT_VERSION}_linux_amd64.zip +unzip vault_${VAULT_VERSION}_linux_amd64.zip +sudo mv vault /usr/local/bin/ +``` + +## minio-cli + +```bash +wget https://dl.min.io/client/mc/release/linux-amd64/mc -O /tmp/minio-cli +chmod +x /tmp/minio-cli +sudo mv /tmp/minio-cli /usr/local/bin/minio-cli + +minio-cli alias set synology http://192.168.0.2:9000 k8s ----proper secret here---- + +``` + + +## backup token + +```bash +mkdir -p /etc/vault.d/ +vault policy write backup - < /etc/vault.d/backup-token +chmod 600 /etc/vault.d/backup-token + +``` \ No newline at end of file diff --git a/docker-30/vault/config.json b/docker-30/vault/config.json new file mode 100644 index 0000000..5d63ddc --- /dev/null +++ b/docker-30/vault/config.json @@ -0,0 +1,20 @@ +{ + "ui": true, + "listener": { + "tcp": { + "address": "0.0.0.0:8200", + "tls_disable": "1", + "tls_cert_file": "/vault/certs/fullchain.pem", + "tls_key_file": "/vault/certs/privkey.pem" + } + }, + "backend": { + "file": { + "path": "/vault/data/file" + } + }, + "default_lease_ttl": "168h", + "max_lease_ttl": "0h", + "api_addr": "https://vault.hrajfrisbee.cz" + // "api_addr": "http://0.0.0.0:8200" +} \ No newline at end of file diff --git a/docker-30/vault/docker-compose.yaml b/docker-30/vault/docker-compose.yaml new file mode 100644 index 0000000..e04c960 --- /dev/null +++ b/docker-30/vault/docker-compose.yaml @@ -0,0 +1,20 @@ +services: + vault: + image: hashicorp/vault:1.21.1 + container_name: vault + restart: unless-stopped + cap_add: + - IPC_LOCK + ports: + - 8200:8200 + environment: + - VAULT_ADDR=http://0.0.0.0:8200 + - VAULT_API_ADDR=http://0.0.0.0:8200 + - VAULT_ADDRESS=http://0.0.0.0:8200 + volumes: + - ./data:/vault/data + - ./config:/vault/config + - ./logs:/vault/logs + - ./certs:/vault/certs + entrypoint: vault + command: server -config=/vault/config/vault.json -log-level=debug \ No newline at end of file diff --git a/docker-30/vault/readme.md b/docker-30/vault/readme.md new file mode 100644 index 0000000..6729589 --- /dev/null +++ b/docker-30/vault/readme.md @@ -0,0 +1,38 @@ +## deployment notes + +There was a problem with "production" deployment of Vault through docker container, because default `docker-entrypoint.sh` adds argument saying where dev instance is supposed to listen and then vault crashes because it tries to listen on same port twice. + +Solution: override default entrypoint + +```bash +# vault helpers +alias set-vault="export VAULT_ADDR=https://docker-30:8200" +alias set-vault-ignore-tls="export VAULT_ADDR=https://docker-30:8200; export VAULT_SKIP_VERIFY=true" + + +export VAULT_ADDR="https://vault.hrajfrisbee.cz" +export VAULT_SKIP_VERIFY=true +``` + +## backup + +Simple file copy initiated by cron, backend storage is minio (s3) running on synology + +```bash +echo '30 2 * * * root /root/bin/vault-backup.sh >> /var/log/vault-backup.log 2>&1' > /etc/cron.d/vault-backup +``` + +```bash +# output role info +tofu output -raw role_id +tofu output -raw secret_id + +``` + +## vault initialization + +```bash + +vault operator init -key-shares=1 -key-threshold=1 + +``` \ No newline at end of file diff --git a/docker-30/vault/terraform/main.tf b/docker-30/vault/terraform/main.tf new file mode 100644 index 0000000..f693e74 --- /dev/null +++ b/docker-30/vault/terraform/main.tf @@ -0,0 +1,49 @@ +resource "vault_mount" "kv" { + path = "secret" + type = "kv-v2" + description = "KV v2 secrets engine" +} + +resource "vault_policy" "eso_read" { + name = "external-secrets-read" + policy = <<-EOT + path "${vault_mount.kv.path}/data/*" { + capabilities = ["read"] + } + path "${vault_mount.kv.path}/metadata/*" { + capabilities = ["read", "list"] + } + EOT +} + +resource "vault_auth_backend" "approle" { + type = "approle" +} + +resource "vault_approle_auth_backend_role" "eso" { + backend = vault_auth_backend.approle.path + role_name = "external-secrets" + token_policies = [vault_policy.eso_read.name] + token_ttl = 3600 + token_max_ttl = 14400 +} + +data "vault_approle_auth_backend_role_id" "eso" { + backend = vault_auth_backend.approle.path + role_name = vault_approle_auth_backend_role.eso.role_name +} + +resource "vault_approle_auth_backend_role_secret_id" "eso" { + backend = vault_auth_backend.approle.path + role_name = vault_approle_auth_backend_role.eso.role_name +} + +output "role_id" { + value = data.vault_approle_auth_backend_role_id.eso.role_id + sensitive = true +} + +output "secret_id" { + value = vault_approle_auth_backend_role_secret_id.eso.secret_id + sensitive = true +} \ No newline at end of file diff --git a/docker-30/vault/terraform/terraform.tfstate b/docker-30/vault/terraform/terraform.tfstate new file mode 100644 index 0000000..f82de51 --- /dev/null +++ b/docker-30/vault/terraform/terraform.tfstate @@ -0,0 +1 @@ +{"version":4,"terraform_version":"1.11.2","serial":2,"lineage":"88d0da45-267c-24b8-34e1-c9a1c58ab70f","outputs":{"role_id":{"value":"864e352d-2064-2bf9-2c73-dbd676a95368","type":"string","sensitive":true},"secret_id":{"value":"8dd0e675-f4dc-50ba-6665-3db5ae423702","type":"string","sensitive":true}},"resources":[{"mode":"data","type":"vault_approle_auth_backend_role_id","name":"eso","provider":"provider[\"registry.opentofu.org/hashicorp/vault\"]","instances":[{"schema_version":0,"attributes":{"backend":"approle","id":"auth/approle/role/external-secrets/role-id","namespace":null,"role_id":"864e352d-2064-2bf9-2c73-dbd676a95368","role_name":"external-secrets"},"sensitive_attributes":[]}]},{"mode":"managed","type":"vault_approle_auth_backend_role","name":"eso","provider":"provider[\"registry.opentofu.org/hashicorp/vault\"]","instances":[{"schema_version":0,"attributes":{"backend":"approle","bind_secret_id":true,"id":"auth/approle/role/external-secrets","namespace":null,"role_id":"864e352d-2064-2bf9-2c73-dbd676a95368","role_name":"external-secrets","secret_id_bound_cidrs":null,"secret_id_num_uses":0,"secret_id_ttl":0,"token_bound_cidrs":null,"token_explicit_max_ttl":0,"token_max_ttl":14400,"token_no_default_policy":false,"token_num_uses":0,"token_period":0,"token_policies":["external-secrets-read"],"token_ttl":3600,"token_type":"default"},"sensitive_attributes":[],"private":"bnVsbA==","dependencies":["vault_auth_backend.approle","vault_mount.kv","vault_policy.eso_read"]}]},{"mode":"managed","type":"vault_approle_auth_backend_role_secret_id","name":"eso","provider":"provider[\"registry.opentofu.org/hashicorp/vault\"]","instances":[{"schema_version":0,"attributes":{"accessor":"f20ef8a0-f21f-8c9b-fc38-887a005af763","backend":"approle","cidr_list":null,"id":"backend=approle::role=external-secrets::accessor=f20ef8a0-f21f-8c9b-fc38-887a005af763","metadata":"{}","namespace":null,"num_uses":0,"role_name":"external-secrets","secret_id":"8dd0e675-f4dc-50ba-6665-3db5ae423702","ttl":0,"with_wrapped_accessor":null,"wrapping_accessor":null,"wrapping_token":null,"wrapping_ttl":null},"sensitive_attributes":[[{"type":"get_attr","value":"secret_id"}],[{"type":"get_attr","value":"wrapping_token"}]],"private":"bnVsbA==","dependencies":["vault_approle_auth_backend_role.eso","vault_auth_backend.approle","vault_mount.kv","vault_policy.eso_read"]}]},{"mode":"managed","type":"vault_auth_backend","name":"approle","provider":"provider[\"registry.opentofu.org/hashicorp/vault\"]","instances":[{"schema_version":1,"attributes":{"accessor":"auth_approle_409190cb","description":"","disable_remount":false,"id":"approle","identity_token_key":null,"local":false,"namespace":null,"path":"approle","tune":[],"type":"approle"},"sensitive_attributes":[],"private":"eyJzY2hlbWFfdmVyc2lvbiI6IjEifQ=="}]},{"mode":"managed","type":"vault_mount","name":"kv","provider":"provider[\"registry.opentofu.org/hashicorp/vault\"]","instances":[{"schema_version":0,"attributes":{"accessor":"kv_d207dd40","allowed_managed_keys":null,"allowed_response_headers":null,"audit_non_hmac_request_keys":[],"audit_non_hmac_response_keys":[],"default_lease_ttl_seconds":0,"delegated_auth_accessors":null,"description":"KV v2 secrets engine","external_entropy_access":false,"id":"secret","identity_token_key":"","listing_visibility":"","local":false,"max_lease_ttl_seconds":0,"namespace":null,"options":null,"passthrough_request_headers":null,"path":"secret","plugin_version":null,"seal_wrap":false,"type":"kv-v2"},"sensitive_attributes":[],"private":"bnVsbA=="}]},{"mode":"managed","type":"vault_policy","name":"eso_read","provider":"provider[\"registry.opentofu.org/hashicorp/vault\"]","instances":[{"schema_version":0,"attributes":{"id":"external-secrets-read","name":"external-secrets-read","namespace":null,"policy":"path \"secret/data/*\" {\n capabilities = [\"read\"]\n}\npath \"secret/metadata/*\" {\n capabilities = [\"read\", \"list\"]\n}\n"},"sensitive_attributes":[],"private":"bnVsbA==","dependencies":["vault_mount.kv"]}]}],"check_results":null} diff --git a/docker-30/vault/terraform/terraform.tfstate.backup b/docker-30/vault/terraform/terraform.tfstate.backup new file mode 100644 index 0000000..3dd2334 --- /dev/null +++ b/docker-30/vault/terraform/terraform.tfstate.backup @@ -0,0 +1 @@ +{"version":4,"terraform_version":"1.11.2","serial":1,"lineage":"88d0da45-267c-24b8-34e1-c9a1c58ab70f","outputs":{"role_id":{"value":"8833d0f8-d35d-d7ea-658b-c27837d121ab","type":"string","sensitive":true},"secret_id":{"value":"1791bfd9-5dc6-406a-3960-ba8fcad4a5a9","type":"string","sensitive":true}},"resources":[{"mode":"data","type":"vault_approle_auth_backend_role_id","name":"eso","provider":"provider[\"registry.opentofu.org/hashicorp/vault\"]","instances":[{"schema_version":0,"attributes":{"backend":"approle","id":"auth/approle/role/external-secrets/role-id","namespace":null,"role_id":"8833d0f8-d35d-d7ea-658b-c27837d121ab","role_name":"external-secrets"},"sensitive_attributes":[]}]},{"mode":"managed","type":"vault_approle_auth_backend_role","name":"eso","provider":"provider[\"registry.opentofu.org/hashicorp/vault\"]","instances":[{"schema_version":0,"attributes":{"backend":"approle","bind_secret_id":true,"id":"auth/approle/role/external-secrets","namespace":null,"role_id":"8833d0f8-d35d-d7ea-658b-c27837d121ab","role_name":"external-secrets","secret_id_bound_cidrs":null,"secret_id_num_uses":0,"secret_id_ttl":0,"token_bound_cidrs":null,"token_explicit_max_ttl":0,"token_max_ttl":14400,"token_no_default_policy":false,"token_num_uses":0,"token_period":0,"token_policies":["external-secrets-read"],"token_ttl":3600,"token_type":"default"},"sensitive_attributes":[],"private":"bnVsbA==","dependencies":["vault_auth_backend.approle","vault_mount.kv","vault_policy.eso_read"]}]},{"mode":"managed","type":"vault_approle_auth_backend_role_secret_id","name":"eso","provider":"provider[\"registry.opentofu.org/hashicorp/vault\"]","instances":[{"schema_version":0,"attributes":{"accessor":"bcc08746-6bea-8df2-02da-f6a697bceb59","backend":"approle","cidr_list":null,"id":"backend=approle::role=external-secrets::accessor=bcc08746-6bea-8df2-02da-f6a697bceb59","metadata":"{}","namespace":null,"num_uses":0,"role_name":"external-secrets","secret_id":"1791bfd9-5dc6-406a-3960-ba8fcad4a5a9","ttl":0,"with_wrapped_accessor":null,"wrapping_accessor":null,"wrapping_token":null,"wrapping_ttl":null},"sensitive_attributes":[[{"type":"get_attr","value":"secret_id"}],[{"type":"get_attr","value":"wrapping_token"}]],"private":"bnVsbA==","dependencies":["vault_approle_auth_backend_role.eso","vault_auth_backend.approle","vault_mount.kv","vault_policy.eso_read"]}]},{"mode":"managed","type":"vault_auth_backend","name":"approle","provider":"provider[\"registry.opentofu.org/hashicorp/vault\"]","instances":[{"schema_version":1,"attributes":{"accessor":"auth_approle_c6cd7bc1","description":"","disable_remount":false,"id":"approle","identity_token_key":null,"local":false,"namespace":null,"path":"approle","tune":[],"type":"approle"},"sensitive_attributes":[],"private":"eyJzY2hlbWFfdmVyc2lvbiI6IjEifQ=="}]},{"mode":"managed","type":"vault_mount","name":"kv","provider":"provider[\"registry.opentofu.org/hashicorp/vault\"]","instances":[{"schema_version":0,"attributes":{"accessor":"kv_8285fbfc","allowed_managed_keys":null,"allowed_response_headers":null,"audit_non_hmac_request_keys":[],"audit_non_hmac_response_keys":[],"default_lease_ttl_seconds":0,"delegated_auth_accessors":null,"description":"KV v2 secrets engine","external_entropy_access":false,"id":"secret","identity_token_key":"","listing_visibility":"","local":false,"max_lease_ttl_seconds":0,"namespace":null,"options":null,"passthrough_request_headers":null,"path":"secret","plugin_version":null,"seal_wrap":false,"type":"kv-v2"},"sensitive_attributes":[],"private":"bnVsbA=="}]},{"mode":"managed","type":"vault_policy","name":"eso_read","provider":"provider[\"registry.opentofu.org/hashicorp/vault\"]","instances":[{"schema_version":0,"attributes":{"id":"external-secrets-read","name":"external-secrets-read","namespace":null,"policy":"path \"secret/data/*\" {\n capabilities = [\"read\"]\n}\npath \"secret/metadata/*\" {\n capabilities = [\"read\", \"list\"]\n}\n"},"sensitive_attributes":[],"private":"bnVsbA==","dependencies":["vault_mount.kv"]}]}],"check_results":null} diff --git a/docker-30/vault/terraform/versions.tf b/docker-30/vault/terraform/versions.tf new file mode 100644 index 0000000..779d7d4 --- /dev/null +++ b/docker-30/vault/terraform/versions.tf @@ -0,0 +1,12 @@ +terraform { + required_providers { + vault = { + source = "hashicorp/vault" + version = "~> 4.0" + } + } +} + +provider "vault" { + # Uses VAULT_ADDR and VAULT_TOKEN from env +} diff --git a/docker-30/vault/vault-backup.sh b/docker-30/vault/vault-backup.sh new file mode 100644 index 0000000..67286cd --- /dev/null +++ b/docker-30/vault/vault-backup.sh @@ -0,0 +1,38 @@ +#!/bin/bash +set -euo pipefail + +# set -x # Enable debug output + +# --- Configuration --- +VAULT_DATA_DIR="${VAULT_DATA_DIR:-/srv/docker/vault/data/}" +S3_BUCKET="${S3_BUCKET:-vault-backup}" +MC_ALIAS="${MC_ALIAS:-synology}" # Pre-configured mc alias +RETENTION_DAYS="${RETENTION_DAYS:-60}" + +TIMESTAMP=$(date +%Y%m%d-%H%M%S) +BACKUP_FILE="/tmp/vault-backup-${TIMESTAMP}.tar.gz" + +log() { echo "[$(date -Iseconds)] $*"; } + +cleanup() { + rm -f "${BACKUP_FILE}" +} +trap cleanup EXIT + +# --- Create backup --- +log "Backing up ${VAULT_DATA_DIR}..." +tar -czf "${BACKUP_FILE}" -C "$(dirname "${VAULT_DATA_DIR}")" "$(basename "${VAULT_DATA_DIR}")" + +BACKUP_SIZE=$(stat -c%s "${BACKUP_FILE}") +log "Backup size: ${BACKUP_SIZE} bytes" + +# --- Upload to MinIO --- +log "Uploading to ${MC_ALIAS}/${S3_BUCKET}..." +set -x +mc cp --quiet "${BACKUP_FILE}" "${MC_ALIAS}/${S3_BUCKET}/vault-backup-${TIMESTAMP}.tar.gz" + +# --- Prune old backups --- +log "Pruning backups older than ${RETENTION_DAYS} days..." +mc rm --quiet --recursive --force --older-than "${RETENTION_DAYS}d" "${MC_ALIAS}/${S3_BUCKET}/" + +log "Backup complete: vault-backup-${TIMESTAMP}.tar.gz" \ No newline at end of file