mirror of
https://gitlab.kube.huskypup.net/Scooby/Homelabv4.git
synced 2026-08-21 05:26:49 +00:00
Initial commit
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
# manifests/clustersecretstore-vault.yaml
|
||||
apiVersion: external-secrets.io/v1
|
||||
kind: ClusterSecretStore
|
||||
metadata:
|
||||
name: vault-backend
|
||||
spec:
|
||||
provider:
|
||||
vault:
|
||||
server: http://vault.vault.svc:8200
|
||||
path: secret # KV v2 mount
|
||||
version: v2
|
||||
auth:
|
||||
kubernetes:
|
||||
# MUST be just the mount name; Vault shows this as "kubernetes/" in `vault auth list`
|
||||
mountPath: kubernetes
|
||||
role: eso-writer # role we create in Vault
|
||||
serviceAccountRef:
|
||||
name: external-secrets # SA created by the ESO chart
|
||||
namespace: external-secrets
|
||||
@@ -0,0 +1,20 @@
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
kind: HTTPRoute
|
||||
metadata:
|
||||
name: vault
|
||||
namespace: vault
|
||||
spec:
|
||||
parentRefs:
|
||||
- name: edge
|
||||
namespace: gateway
|
||||
sectionName: https
|
||||
hostnames:
|
||||
- vault.kube.huskypup.net
|
||||
rules:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: vault
|
||||
port: 8200
|
||||
@@ -0,0 +1,48 @@
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: vault-eso-bootstrap
|
||||
namespace: vault
|
||||
annotations:
|
||||
argocd.argoproj.io/hook: PostSync
|
||||
argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
|
||||
argocd.argoproj.io/sync-wave: "2"
|
||||
spec:
|
||||
backoffLimit: 5
|
||||
template:
|
||||
spec:
|
||||
serviceAccountName: argocd-hook-sa
|
||||
containers:
|
||||
- name: eso-bootstrap
|
||||
image: bitnami/kubectl:1.31
|
||||
command:
|
||||
- /bin/bash
|
||||
- -c
|
||||
- |
|
||||
set -euo pipefail
|
||||
echo "=== Vault ESO Bootstrap ==="
|
||||
|
||||
# Wait for ESO to be ready
|
||||
echo "Waiting for External Secrets Operator..."
|
||||
for i in $(seq 1 60); do
|
||||
if kubectl -n external-secrets get deploy/external-secrets >/dev/null 2>&1; then
|
||||
kubectl -n external-secrets rollout status deploy/external-secrets --timeout=180s && break
|
||||
fi
|
||||
echo " waiting for ESO deployment... (attempt $i/60)"
|
||||
sleep 5
|
||||
done
|
||||
|
||||
# Wait for ClusterSecretStore to be ready
|
||||
echo "Checking ClusterSecretStore vault-backend..."
|
||||
for i in $(seq 1 30); do
|
||||
STATUS=$(kubectl get clustersecretstore vault-backend -o jsonpath='{.status.conditions[0].status}' 2>/dev/null || echo "")
|
||||
if [ "$STATUS" = "True" ]; then
|
||||
echo " ClusterSecretStore vault-backend is ready"
|
||||
break
|
||||
fi
|
||||
echo " waiting for ClusterSecretStore... (attempt $i/30)"
|
||||
sleep 5
|
||||
done
|
||||
|
||||
echo "=== Vault ESO Bootstrap Complete ==="
|
||||
restartPolicy: Never
|
||||
@@ -0,0 +1,13 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: vault-policy-external-secrets
|
||||
namespace: vault
|
||||
data:
|
||||
external-secrets.hcl: |
|
||||
path "secret/data/*" {
|
||||
capabilities = ["create", "update", "read", "list"]
|
||||
}
|
||||
path "secret/metadata/*" {
|
||||
capabilities = ["create", "update", "read", "list"]
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
apiVersion: networking.istio.io/v1beta1
|
||||
kind: VirtualService
|
||||
metadata:
|
||||
name: vault
|
||||
namespace: vault
|
||||
spec:
|
||||
hosts:
|
||||
- vault.kube.huskypup.net
|
||||
gateways:
|
||||
- istio-system/edge
|
||||
http:
|
||||
- route:
|
||||
- destination:
|
||||
host: vault.vault.svc.cluster.local
|
||||
port:
|
||||
number: 8200
|
||||
@@ -0,0 +1,140 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: vault-init-script
|
||||
namespace: vault
|
||||
data:
|
||||
vault-init-and-store.sh: |
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# === Vault Auto-Init and Store Script (ArgoCD Job Version) ===
|
||||
# Adapted from Homelabv5/scripts/vault-init-and-store.sh
|
||||
# Runs inside a Job pod with kubectl access via ServiceAccount.
|
||||
# Uses kubectl exec to interact with the vault-0 pod directly
|
||||
# instead of port-forwarding + local vault CLI.
|
||||
|
||||
VAULT_NS="${VAULT_NS:-vault}"
|
||||
SECRET_NAME="${SECRET_NAME:-vault-init-keys}"
|
||||
KV_MOUNT="${KV_MOUNT:-secret}"
|
||||
JOB_NS_FOR_ROLE="${JOB_NS_FOR_ROLE:-auth-proxy}"
|
||||
JOB_SA_FOR_ROLE="${JOB_SA_FOR_ROLE:-oauth2-bootstrap}"
|
||||
POLICY_NAME="${POLICY_NAME:-oauth2-writer}"
|
||||
ROLE_NAME="${ROLE_NAME:-eso-writer}"
|
||||
|
||||
VAULT_POD="vault-0"
|
||||
|
||||
# Helper: run vault CLI inside the vault pod
|
||||
vault_exec() {
|
||||
kubectl -n "$VAULT_NS" exec "$VAULT_POD" -- env VAULT_ADDR=http://127.0.0.1:8200 vault "$@"
|
||||
}
|
||||
|
||||
# --- Wait for vault-0 pod to be ready ---
|
||||
echo "==> Waiting for vault-0 pod to be running..."
|
||||
for i in $(seq 1 60); do
|
||||
PHASE=$(kubectl -n "$VAULT_NS" get pod "$VAULT_POD" -o jsonpath='{.status.phase}' 2>/dev/null || echo "")
|
||||
if [ "$PHASE" = "Running" ]; then
|
||||
echo " vault-0 is running"
|
||||
break
|
||||
fi
|
||||
echo " waiting for vault-0... (attempt $i/60, phase=$PHASE)"
|
||||
sleep 5
|
||||
done
|
||||
|
||||
# Give Vault a moment to start its listener
|
||||
sleep 5
|
||||
|
||||
# --- Check init/seal status ---
|
||||
echo "==> Checking Vault status..."
|
||||
status_json="$(vault_exec status -format=json 2>/dev/null || echo '{}')"
|
||||
initialized="$(echo "$status_json" | grep -o '"initialized":[a-z]*' | cut -d: -f2 || echo "")"
|
||||
sealed="$(echo "$status_json" | grep -o '"sealed":[a-z]*' | cut -d: -f2 || echo "")"
|
||||
|
||||
root_token=""
|
||||
unseal_key=""
|
||||
|
||||
# Pull existing secret if present
|
||||
if kubectl -n "$VAULT_NS" get secret "$SECRET_NAME" >/dev/null 2>&1; then
|
||||
root_token="$(kubectl -n "$VAULT_NS" get secret "$SECRET_NAME" -o jsonpath='{.data.VAULT_ROOT_TOKEN}' | base64 -d || true)"
|
||||
unseal_key="$(kubectl -n "$VAULT_NS" get secret "$SECRET_NAME" -o jsonpath='{.data.VAULT_UNSEAL_KEY}' | base64 -d || true)"
|
||||
fi
|
||||
|
||||
# Initialize if needed
|
||||
if [ "$initialized" != "true" ]; then
|
||||
echo "==> Vault not initialized; initializing..."
|
||||
init_json="$(vault_exec operator init -key-shares=1 -key-threshold=1 -format=json)"
|
||||
root_token="$(echo "$init_json" | grep -o '"root_token":"[^"]*"' | cut -d'"' -f4)"
|
||||
unseal_key="$(echo "$init_json" | grep -o '"unseal_keys_b64":\["[^"]*"\]' | grep -o '\["[^"]*"\]' | tr -d '[]"')"
|
||||
sealed="true"
|
||||
|
||||
echo "==> Storing root token & unseal key in Secret ${VAULT_NS}/${SECRET_NAME}"
|
||||
kubectl -n "$VAULT_NS" create secret generic "$SECRET_NAME" \
|
||||
--from-literal=VAULT_ROOT_TOKEN="$root_token" \
|
||||
--from-literal=VAULT_UNSEAL_KEY="$unseal_key" \
|
||||
--dry-run=client -o yaml | kubectl apply -f -
|
||||
else
|
||||
echo "==> Vault already initialized."
|
||||
fi
|
||||
|
||||
# Unseal if needed
|
||||
if [ "$sealed" = "true" ]; then
|
||||
if [ -z "$unseal_key" ]; then
|
||||
echo "ERROR: Vault is sealed and no unseal key available"
|
||||
exit 1
|
||||
fi
|
||||
echo "==> Unsealing..."
|
||||
vault_exec operator unseal "$unseal_key" >/dev/null
|
||||
fi
|
||||
|
||||
# Login
|
||||
if [ -z "$root_token" ]; then
|
||||
echo "==> Reading root token from Secret..."
|
||||
root_token="$(kubectl -n "$VAULT_NS" get secret "$SECRET_NAME" -o jsonpath='{.data.VAULT_ROOT_TOKEN}' | base64 -d)"
|
||||
fi
|
||||
vault_exec login "$root_token" >/dev/null
|
||||
|
||||
# Ensure KV v2 enabled
|
||||
if ! vault_exec secrets list -format=json 2>/dev/null | grep -q "\"${KV_MOUNT}/\""; then
|
||||
echo "==> Enabling KV v2 at ${KV_MOUNT}/"
|
||||
vault_exec secrets enable -path="$KV_MOUNT" -version=2 kv >/dev/null
|
||||
fi
|
||||
|
||||
# Configure Kubernetes auth with a reviewer token from a local SA
|
||||
echo "==> Ensuring reviewer SA + binding"
|
||||
kubectl -n "$VAULT_NS" get sa vault-auth >/dev/null 2>&1 || kubectl -n "$VAULT_NS" create sa vault-auth
|
||||
kubectl get clusterrolebinding vault-auth-delegator >/dev/null 2>&1 || \
|
||||
kubectl create clusterrolebinding vault-auth-delegator \
|
||||
--clusterrole=system:auth-delegator \
|
||||
--serviceaccount="${VAULT_NS}:vault-auth"
|
||||
|
||||
reviewer_jwt="$(kubectl -n "$VAULT_NS" create token vault-auth)"
|
||||
kube_ca="$(kubectl -n kube-system get configmap kube-root-ca.crt -o jsonpath='{.data.ca\.crt}')"
|
||||
kube_host="https://kubernetes.default.svc:443"
|
||||
|
||||
vault_exec auth enable kubernetes >/dev/null 2>&1 || true
|
||||
vault_exec write auth/kubernetes/config \
|
||||
token_reviewer_jwt="$reviewer_jwt" \
|
||||
kubernetes_host="$kube_host" \
|
||||
kubernetes_ca_cert="$kube_ca" >/dev/null
|
||||
|
||||
# Policy + role for ESO/oauth2 job
|
||||
vault_exec policy write "$POLICY_NAME" - >/dev/null <<'HCL'
|
||||
path "secret/data/*" {
|
||||
capabilities = ["create", "update", "read", "list"]
|
||||
}
|
||||
|
||||
path "secret/metadata/*" {
|
||||
capabilities = ["create", "update", "read", "list"]
|
||||
}
|
||||
HCL
|
||||
|
||||
vault_exec write "auth/kubernetes/role/${ROLE_NAME}" \
|
||||
bound_service_account_names="${JOB_SA_FOR_ROLE},external-secrets" \
|
||||
bound_service_account_namespaces="${JOB_NS_FOR_ROLE},external-secrets" \
|
||||
policies="${POLICY_NAME}" \
|
||||
ttl="24h" >/dev/null
|
||||
|
||||
echo "==> Done."
|
||||
echo "K8s Secret with init creds: ${VAULT_NS}/${SECRET_NAME}"
|
||||
echo "IMPORTANT: back these up securely and delete the Secret when you're comfortable:"
|
||||
echo " kubectl -n ${VAULT_NS} delete secret ${SECRET_NAME}"
|
||||
@@ -0,0 +1,27 @@
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: vault-init
|
||||
namespace: vault
|
||||
annotations:
|
||||
argocd.argoproj.io/hook: PostSync
|
||||
argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
|
||||
argocd.argoproj.io/sync-wave: "1"
|
||||
spec:
|
||||
backoffLimit: 3
|
||||
template:
|
||||
spec:
|
||||
serviceAccountName: argocd-hook-sa
|
||||
containers:
|
||||
- name: vault-init
|
||||
image: bitnami/kubectl:1.31
|
||||
command: ["/bin/bash", "/scripts/vault-init-and-store.sh"]
|
||||
volumeMounts:
|
||||
- name: scripts
|
||||
mountPath: /scripts
|
||||
volumes:
|
||||
- name: scripts
|
||||
configMap:
|
||||
name: vault-init-script
|
||||
defaultMode: 0755
|
||||
restartPolicy: Never
|
||||
@@ -0,0 +1,45 @@
|
||||
server:
|
||||
# HA mode with Raft integrated storage
|
||||
ha:
|
||||
enabled: true
|
||||
replicas: 3
|
||||
raft:
|
||||
enabled: true
|
||||
config: |
|
||||
ui = true
|
||||
listener "tcp" {
|
||||
address = "0.0.0.0:8200"
|
||||
tls_disable = 1
|
||||
}
|
||||
storage "raft" {
|
||||
path = "/vault/data"
|
||||
}
|
||||
disable_mlock = true
|
||||
service_registration "kubernetes" {}
|
||||
telemetry {
|
||||
prometheus_retention_time = "30s"
|
||||
disable_hostname = true
|
||||
}
|
||||
|
||||
standalone:
|
||||
enabled: false
|
||||
|
||||
dataStorage:
|
||||
enabled: true
|
||||
size: 10Gi
|
||||
|
||||
extraEnvironmentVars: {}
|
||||
|
||||
# Tolerate control-plane taint
|
||||
tolerations:
|
||||
- key: node-role.kubernetes.io/control-plane
|
||||
operator: Exists
|
||||
effect: NoSchedule
|
||||
|
||||
# Enable ServiceMonitor for Prometheus
|
||||
serverTelemetry:
|
||||
serviceMonitor:
|
||||
enabled: true
|
||||
selectors:
|
||||
release: prometheus
|
||||
interval: 30s
|
||||
Reference in New Issue
Block a user