feat(vault): add vault

This commit is contained in:
Masaki Yatsu
2025-08-14 21:19:00 +09:00
parent 8f6720117f
commit 43581c15bb
11 changed files with 3980 additions and 0 deletions

137
.gitignore vendored
View File

@@ -1,3 +1,140 @@
### Generated by gibo (https://github.com/simonwhitaker/gibo)
### https://raw.github.com/github/gitignore/6eeebe6f49678aacd8311ce079842c971b3ebe96/Node.gitignore
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
.env.*.local
.env.secrets
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.cache
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
# Our rules
/.env.local* /.env.local*
/custom.just /custom.just
/custom/ /custom/

View File

@@ -1,5 +1,7 @@
set dotenv-filename := ".env.local" set dotenv-filename := ".env.local"
export PATH := "./node_modules/.bin:" + env_var('PATH')
[private] [private]
default: default:
@just --list --unsorted --list-submodules @just --list --unsorted --list-submodules
@@ -7,5 +9,7 @@ default:
mod env mod env
mod k8s mod k8s
mod longhorn mod longhorn
mod utils
mod vault
import? "custom.just" import? "custom.just"

View File

@@ -5,4 +5,5 @@ helm = "3.17.4"
just = "1.42.4" just = "1.42.4"
k3sup = "0.13.10" k3sup = "0.13.10"
kubelogin = "1.34.0" kubelogin = "1.34.0"
node = "22.18.0"
vault = "1.20.2" vault = "1.20.2"

3592
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

22
package.json Normal file
View File

@@ -0,0 +1,22 @@
{
"name": "buun-stack",
"version": "0.1.0",
"description": "Buun stack: Kubernetes home lab",
"author": "Buun ch.",
"license": "MIT",
"dependencies": {
"@dotenvx/dotenvx": "^1.31.3",
"@keycloak/keycloak-admin-client": "^26.0.6",
"tiny-invariant": "^1.3.3",
"tsx": "^4.19.2",
"zx": "^8.2.4"
},
"devDependencies": {
"@fsouza/prettierd": "^0.25.4",
"@types/node": "^22.10.1",
"eslint": "^9.16.0",
"eslint-config-unjs": "^0.4.2",
"prettier": "^3.4.1",
"typescript": "^5.7.2"
}
}

1
vault/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
vault-values.yaml

View File

@@ -0,0 +1,8 @@
apiVersion: v1
kind: Secret
metadata:
name: vault-auth-token
annotations:
kubernetes.io/service-account.name: vault-auth
type: kubernetes.io/service-account-token

181
vault/justfile Normal file
View File

@@ -0,0 +1,181 @@
set fallback := true
export VAULT_NAMESPACE := env("VAULT_NAMESPACE", "vault")
export VAULT_CHART_VERSION := env("VAULT_CHART_VERSION", "0.29.1")
export VAULT_HOST := env("VAULT_HOST", "")
export VAULT_ADDR := "https://" + VAULT_HOST
SECRET_PATH := "secret"
[private]
default:
@just --list --unsorted --list-submodules
# Add Helm repository
add-helm-repo:
helm repo add hashicorp https://helm.releases.hashicorp.com
helm repo update
# Remove Helm repository
remove-helm-repo:
helm repo remove hashicorp
# Create Keycloak namespace
create-namespace:
@kubectl get namespace ${VAULT_NAMESPACE} &>/dev/null || \
kubectl create namespace ${VAULT_NAMESPACE}
# Delete Keycloak namespace
delete-namespace:
@kubectl delete namespace ${VAULT_NAMESPACE} --ignore-not-found
# Install Vault
install: check-env
#!/bin/bash
set -euo pipefail
just create-namespace
just add-helm-repo
gomplate -f vault-values.gomplate.yaml -o vault-values.yaml
helm upgrade --cleanup-on-fail --install vault hashicorp/vault \
--version ${VAULT_CHART_VERSION} -n ${VAULT_NAMESPACE} --wait -f vault-values.yaml
# Wait for the primary vault pod to complete init containers and be ready to accept commands
kubectl wait pod --for=condition=PodReadyToStartContainers \
-n ${VAULT_NAMESPACE} vault-0 --timeout=5m
init_output=$(kubectl exec -n ${VAULT_NAMESPACE} vault-0 -- \
vault operator init -key-shares=1 -key-threshold=1 -format=json || true)
root_token=""
if echo "${init_output}" | grep -q "Vault is already initialized"; then
echo "Vault is already initialized"
while [ -z "${root_token}" ]; do
root_token=$(gum input --prompt="Vault root token: " --password --width=100)
done
else
unseal_key=$(echo "${init_output}" | jq -r '.unseal_keys_b64[0]')
root_token=$(echo "${init_output}" | jq -r '.root_token')
kubectl exec -n ${VAULT_NAMESPACE} vault-0 -- \
vault operator unseal "${unseal_key}"
echo "Vault initialized and unsealed successfully"
echo "Root Token: ${root_token}"
echo "Unseal Key: ${unseal_key}"
echo "Please save these credentials securely!"
fi
# Wait for all vault instances to pass readiness checks and be ready to serve requests
kubectl wait pod --for=condition=ready -n ${VAULT_NAMESPACE} \
-l app.kubernetes.io/name=vault --timeout=5m
just configure-kubernetes-auth "${root_token}"
just create-secrets-engine {{ SECRET_PATH }} "${root_token}"
# Uninstall Vault
uninstall delete-ns='false':
#!/bin/bash
set -euo pipefail
helm uninstall vault -n ${VAULT_NAMESPACE} --ignore-not-found --wait
just delete-namespace
# Create admin token
create-admin-token root_token='': check-env
#!/bin/bash
set -euo pipefail
export VAULT_TOKEN="{{ root_token }}"
while [ -z "${VAULT_TOKEN}" ]; do
VAULT_TOKEN=$(gum input --prompt="Vault root token: " --password --width=100)
done
vault policy write admin - <<EOF
path "sys/auth" {
capabilities = ["read", "list", "sudo"]
}
path "sys/auth/*" {
capabilities = ["create", "read", "update", "delete", "list", "sudo"]
}
path "secret/*" {
capabilities = ["create", "read", "update", "delete", "list"]
}
path "auth/*" {
capabilities = ["create", "read", "update", "delete", "list"]
}
path "sys/policy/*" {
capabilities = ["create", "read", "update", "delete", "list"]
}
path "sys/policies/acl/*" {
capabilities = ["create", "read", "update", "delete", "list"]
}
EOF
vault token create -policy=admin
# Create secrets engine
create-secrets-engine path root_token='':
#!/bin/bash
set -euo pipefail
export VAULT_TOKEN="{{ root_token }}"
while [ -z "${VAULT_TOKEN}" ]; do
VAULT_TOKEN=$(gum input --prompt="Vault root token: " --password --width=100)
done
# Use kubectl exec during initial setup to avoid DNS dependency
kubectl exec -n ${VAULT_NAMESPACE} vault-0 -- sh <<EOF
export VAULT_TOKEN='${VAULT_TOKEN}'
vault secrets enable -path='{{ path }}' kv-v2
EOF
# Configure Kubernetes authentication for Vault
configure-kubernetes-auth root_token='':
#!/bin/bash
set -euo pipefail
export VAULT_TOKEN="{{ root_token }}"
while [ -z "${VAULT_TOKEN}" ]; do
VAULT_TOKEN=$(gum input --prompt="Vault root token: " --password --width=100)
done
gomplate -f ./serviceaccount.gomplate.yaml | kubectl apply -n "${VAULT_NAMESPACE}" -f -
gomplate -f ./rolebinding.gomplate.yaml | kubectl apply -n "${VAULT_NAMESPACE}" -f -
kubectl apply -n "${VAULT_NAMESPACE}" -f ./auth-token-secret.yaml
SA_SECRET="vault-auth-token"
SA_JWT=$(kubectl get secret -n ${VAULT_NAMESPACE} ${SA_SECRET} -o jsonpath='{.data.token}' | \
base64 --decode)
SA_CA=$(kubectl get secret -n ${VAULT_NAMESPACE} ${SA_SECRET} -o jsonpath='{.data.ca\.crt}' | \
base64 --decode)
# Use kubectl exec to run vault commands directly in the pod during initial setup
kubectl exec -n ${VAULT_NAMESPACE} vault-0 -- sh <<EOF
export VAULT_TOKEN='${VAULT_TOKEN}'
vault auth list -format=json | jq -e '.["kubernetes/"]' >/dev/null 2>&1 || \
vault auth enable kubernetes
vault write auth/kubernetes/config \
token_reviewer_jwt='${SA_JWT}' \
kubernetes_host='https://kubernetes.default.svc' \
kubernetes_ca_cert='${SA_CA}'
EOF
# Get key value
get path field: check-env
@vault kv get -mount=secret -field={{ field }} {{ path }}
# Put key value
put path *args: check-env
@vault kv put -mount=secret {{ path }} {{ args }}
# Delete key value
delete path: check-env
@vault kv delete -mount=secret {{ path }}
# Check if key exists
exist path: check-env
@vault kv get -mount=secret {{ path }} &>/dev/null
# Check the environment
[private]
check-env:
#!/bin/bash
set -euo pipefail
if [ -z "${VAULT_HOST}" ]; then
while [ -z "${VAULT_HOST}" ]; do
VAULT_HOST=$(
gum input --prompt="Vault host: " --width=100 --placeholder="vault.example.com"
)
done
just env::set VAULT_HOST "${VAULT_HOST}"
fi

View File

@@ -0,0 +1,12 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: vault-auth-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:auth-delegator
subjects:
- kind: ServiceAccount
name: vault-auth
namespace: {{ .Env.VAULT_NAMESPACE }}

View File

@@ -0,0 +1,5 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: vault-auth
namespace: {{ .Env.VAULT_NAMESPACE }}

View File

@@ -0,0 +1,17 @@
csi:
enabled: true
server:
ingress:
enabled: true
annotations:
kubernetes.io/ingress.class: traefik
traefik.ingress.kubernetes.io/router.entrypoints: websecure
ingressClassName: traefik
hosts:
- host: {{ .Env.VAULT_HOST }}
paths:
- /
tls:
- hosts:
- {{ .Env.VAULT_HOST }}