u2usvc

Secrets Management

Vault

Setup

Integrate with K8s

# enable (create) a new KV (v2) secrets engine under k8s/prod/gfub 
vault secrets enable -path=k8s -version=2 kv

# store secrets (store in batch, each `put` does an override)
vault kv put k8s/prod/gfub/atsvc \
JWT_SECRET_KEY="da0a56e4c80c7aa7f93aad7452efdec2f759f915xl38dan" \
DATABASE_PASSWORD="sohd801h08h10fhv1" \
APPLICATION_URL="https://atsvc.com" \
SPRING_PROFILES_ACTIVE="dev" \
DATABASE_USERNAME="admin" \
DATABASE_URL="jdbc:postgresql://db:5432/atsvc" \
REDIS_HOST="redis" \
REDIS_PORT="6379" \
POSTGRES_USER="admin" \
POSTGRES_PASSWORD="sohd801h08h10fhv1" \
POSTGRES_DB="atsvc"

# verify
vault kv get k8s/prod/gfub/atsvc

Install external secrets

helm repo add external-secrets https://charts.external-secrets.io
helm repo update

helm install external-secrets external-secrets/external-secrets \
--namespace external-secrets \
--create-namespace

Note: The ExternalSecret specifies what to fetch, the SecretStore specifies how to access. https://external-secrets.io/main/api/externalsecret/ https://external-secrets.io/main/api/secretstore/

???
cat external-secrets.yaml
# ---
# apiVersion: external-secrets.io/v1
# kind: SecretStore
# metadata:
#   name: vault-backend
# spec:
#   provider:
#     vault:
#       server: "https://vault.aperture.ad:8200"
#       path: k8s #?
#       version: v2
#       auth:
#         tokenSecretRef:
#           name: vault-token
#           namespace: gfub
#           key: token
#       caProvider:
#         type: ConfigMap
#         name: internal-root-ca
#         key: "ca.crt"
# ---
# apiVersion: external-secrets.io/v1
# kind: ExternalSecret
# metadata:
#   name: prod-gfub
#   namespace: gfub
# spec:
#   refreshInterval: 1m
#   secretStoreRef:
#     name: vault-backend
#     kind: SecretStore
#   target:
#     name: prod-gfub
#   data:
#     - secretKey: JWT_SECRET_KEY
#       remoteRef:
#         key: prod/gfub/atsvc
#         property: JWT_SECRET_KEY
#     - secretKey: DATABASE_PASSWORD
#       remoteRef:
#         key: prod/gfub/atsvc
#         property: DATABASE_PASSWORD
#     - secretKey: APPLICATION_URL
#       remoteRef:
#         key: prod/gfub/atsvc
#         property: APPLICATION_URL
#     - secretKey: SPRING_PROFILES_ACTIVE
#       remoteRef:
#         key: prod/gfub/atsvc
#         property: SPRING_PROFILES_ACTIVE
#     - secretKey: DATABASE_USERNAME
#       remoteRef:
#         key: prod/gfub/atsvc
#         property: DATABASE_USERNAME
#     - secretKey: DATABASE_URL
#       remoteRef:
#         key: prod/gfub/atsvc
#         property: DATABASE_URL
#     - secretKey: REDIS_HOST
#       remoteRef:
#         key: prod/gfub/atsvc
#         property: REDIS_HOST
#     - secretKey: REDIS_PORT
#       remoteRef:
#         key: prod/gfub/atsvc
#         property: REDIS_PORT
#     - secretKey: POSTGRES_USER
#       remoteRef:
#         key: prod/gfub/atsvc
#         property: POSTGRES_USER
#     - secretKey: POSTGRES_PASSWORD
#       remoteRef:
#         key: prod/gfub/atsvc
#         property: POSTGRES_PASSWORD
#     - secretKey: POSTGRES_DB
#       remoteRef:
#         key: prod/gfub/atsvc
#         property: POSTGRES_DB
kubectl create namespace gfub

cat ./gfub-policy.hcl
# path "k8s/data/prod/gfub/atsvc" {
#   capabilities = ["read", "list"]
# }
#
# path "k8s/metadata/prod/gfub/atsvc" {
#   capabilities = ["read", "list"]
# }

vault policy write gfub-policy ./gfub-policy.hcl

vault token create -policy=gfub-policy -period=0 -orphan
# token: $TOKEN

kubectl create secret generic vault-token --namespace gfub --from-literal=token="$TOKEN"

kubectl apply -f ./external-secrets.yaml
kubectl label namespace gfub use-internal-ca=true

# verify
kubectl -n gfub get externalsecret
kubectl -n gfub get secret

Fix certificate issues when external-secrets operator tries to connect to vault:

kubectl label namespace external-secrets use-internal-ca=true

Configure secret for kubernetes to be able to pull image from private harbor repo and allow default SA to use that secret:

kubectl create secret docker-registry reg-token --docker-server=https://harbor.aperture.ad/ --docker-username=kubernetes --docker-password="coolpasswd" --docker-email="kubernetes@aperture.ad"
kubectl patch serviceaccount default -p '{"imagePullSecrets": [{"name": "reg-token"}]}'

Setup on Proxmox

Install Hashicorp Vault to an external server. I will use docker community script to dep https://community-scripts.github.io/ProxmoxVE/scripts?id=docker&category=Containers+%26+Docker

export $(grep -v '^#' .env | xargs)
ssh -i $TF_VAR_pve_ssh_key_path root@pve.aperture.ad

bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/docker.sh)"
# select advanced install, choose root passwd and configure ssh key
# add compose plugin
# ......... installation completed
exit

I will now generate certs for vault on my workstation:

openssl genrsa -out vault.aperture.ad.key 4096

cat > vault.aperture.ad.cnf <<EOF
[req]
default_bits = 4096
prompt = no
default_md = sha256
distinguished_name = dn
req_extensions = req_ext

[dn]
CN = vault.aperture.ad

[req_ext]
subjectAltName = @san

[san]
DNS.1 = vault.aperture.ad
EOF

# on WS
openssl req -new \
-key vault.aperture.ad.key \
-out vault.aperture.ad.csr \
-config vault.aperture.ad.cnf

openssl x509 -req \
-in vault.aperture.ad.csr \
-CA k8s-aperture-root-ca.crt \
-CAkey k8s-aperture-root-ca.key \
-CAcreateserial \
-out vault.aperture.ad.crt \
-days 825 \
-sha256 \
-extensions req_ext \
-extfile vault.aperture.ad.cnf

sftp -i vault root@vault.aperture.ad
> put certificates/vault.aperture.ad.crt /root/vault/certs/server.crt
> put certificates/vault.aperture.ad.key /root/vault/certs/server.key
> put certificates/k8s-aperture-root-ca-01.crt /usr/local/share/ca-certificates/

After docker LXC installation, exit proxmox ssh and ssh into docker LXC.

update-ca-certificates

mkdir -p $HOME/vault/{config,file,logs,certs}
cd $HOME/vault/certs/

cd $HOME/vault
cat <<EOF > docker-compose.yaml
version: '3.3'
services:
  vault:
    image: hashicorp/vault
    container_name: vault-new
    environment:
      VAULT_ADDR: "https://vault.aperture.ad:8200"
      VAULT_API_ADDR: "https://vault.aperture.ad:8200"
      VAULT_ADDRESS: "https://vault.aperture.ad:8200"
      # VAULT_UI: true
      # VAULT_TOKEN:
    ports:
      - "8200:8200"
      - "8201:8201"
    restart: always
    volumes:
      - ./logs:/vault/logs/:rw
      - ./data:/vault/data/:rw
      - ./config:/vault/config/:rw
      - ./certs:/certs/:rw
      - ./file:/vault/file/:rw
    cap_add:
      - IPC_LOCK
    entrypoint: vault server -config /vault/config/config.hcl
EOF

cd $HOME/vault/config
cat <<EOF > config.hcl
ui = true
disable_mlock = "true"

storage "raft" {
  path    = "/vault/data"
  node_id = "node1"
}

listener "tcp" {
  address = "[::]:8200"
  tls_disable = "false"
  tls_cert_file = "/certs/server.crt"
  tls_key_file  = "/certs/server.key"
}

api_addr = "https://vault.aperture.ad:8200"
cluster_addr = "https://vault.aperture.ad:8201"
EOF

cd $HOME/vault
docker compose up -d

# Exec into the vault container
docker exec -it vault-new /bin/sh

# Once logged into the vault container
vault operator init
# store the unseal keys and the token outputted

# run the following in the container, entering 3 different unseal keys
vault operator unseal
vault operator unseal
vault operator unseal

# install vault on your workstation
export VAULT_ADDR="https://vault.aperture.ad:8200"
vault login
# Token (will be hidden):