ce7cb702218693d08ea6d83464d2457c9250027c

This commit is contained in:
ms2k 2026-11-12 20:46:28 +09:00
commit 34c5f8963c
12 changed files with 145336 additions and 0 deletions

72
vendor/platform/vault/bootstrap.md vendored Normal file
View file

@ -0,0 +1,72 @@
# Vault Bootstrap
## 1. Deploy
```bash
helm repo add hashicorp https://helm.releases.hashicorp.com
helm upgrade --install vault hashicorp/vault \
--namespace vault \
--create-namespace \
-f vendor/platform/vault/values.yaml
kubectl -n vault apply -f vendor/platform/vault/network-policies.yaml
kubectl -n vault wait --for=condition=Ready pod/vault-0 --timeout=120s
```
## 2. Initialize
```bash
kubectl -n vault exec vault-0 -- vault operator init \
-key-shares=5 \
-key-threshold=3 \
-format=json > ./vendor/platform/vault/.secrets/vault-init.json
```
## 3. Unseal
```bash
for pod in vault-0 vault-1 vault-2; do
jq -r '.unseal_keys_b64[:3][]' ./vendor/platform/vault/.secrets/vault-init.json | xargs -I{} \
kubectl -n vault exec "$pod" -- vault operator unseal {}
done
```
This same procedure is required after every cluster restart.
## 4. Terraform (PKI + auth methods + policies)
```bash
kubectl -n vault port-forward svc/vault 8200:8200
export VAULT_ADDR=http://127.0.0.1:8200
export VAULT_TOKEN=$(jq -r '.root_token' ./vendor/platform/vault/.secrets/vault-init.json)
export DOMAIN_NAME='some domain name'
export ADMIN_USER='some admin user'
cd terraform/internal/vault
../../scripts/tofu-versitygw-state init
../../scripts/tofu-versitygw-state apply \
-var="internal_domain=${DOMAIN_NAME}" \
-var="users=[\"${ADMIN_USER}\"]" \
-var="admin_user=${ADMIN_USER}"
../../scripts/tofu-versitygw-state output -json user_passwords
```
Required existing secret: `secret/cloudflare/api-token` key `api-token`.
## 5. Revoke root token
```bash
vault login -method=userpass username=${ADMIN_USER}
vault token revoke $(jq -r '.root_token' ./vendor/platform/vault/.secrets/vault-init.json)
```
## 6. Set up Vault Secrets Operator
```bash
helm upgrade --install -n vault \
vault-secrets-operator \
hashicorp/vault-secrets-operator \
-f vendor/platform/vault/vso-values.yaml
```

25
vendor/platform/vault/certificate.yaml vendored Normal file
View file

@ -0,0 +1,25 @@
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: vault-server
namespace: vault
spec:
secretName: vault-server-tls
issuerRef:
name: vault-internal
kind: ClusterIssuer
dnsNames:
- vault.quorate.internal
- vault.vault.svc.cluster.local
- vault-active.vault.svc.cluster.local
- vault-active-direct.vault.svc.cluster.local
- vault-internal.vault.svc.cluster.local
- "*.vault-internal.vault.svc.cluster.local"
duration: 2160h
renewBefore: 360h
privateKey:
algorithm: ECDSA
size: 256
rotationPolicy: Always
usages:
- server auth

View file

@ -0,0 +1,4 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- vso-config.yaml

View file

@ -0,0 +1,12 @@
---
# Shared VaultConnection -- referenced by VaultAuth resources in any namespace.
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultConnection
metadata:
name: vault
namespace: vault
spec:
address: https://vault-active-tls.vault.svc.cluster.local:443
caCertSecretRef: vault-server-tls
skipTLSVerify: false
tlsServerName: vault-active-direct.vault.svc.cluster.local

185
vendor/platform/vault/helm-releases.yaml vendored Normal file
View file

@ -0,0 +1,185 @@
---
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: vault
namespace: vault
spec:
interval: 15m
releaseName: vault
targetNamespace: vault
chart:
spec:
chart: vault
version: "0.34.1"
sourceRef:
kind: HelmRepository
name: hashicorp
namespace: flux-system
install:
createNamespace: true
remediation:
retries: 3
upgrade:
remediation:
retries: 3
# Keep the established named target stable across pod revisions.
postRenderers:
- kustomize:
patches:
- target:
kind: StatefulSet
name: vault
patch: |-
- op: replace
path: /spec/template/spec/containers/0/ports/0/name
value: https-migration
- op: replace
path: /spec/template/spec/containers/0/readinessProbe/exec/command
value:
- /bin/sh
- -ec
- vault status -address=https://127.0.0.1:8200 -ca-cert=/vault/userconfig/vault-server-tls/ca.crt -tls-server-name=vault-active-direct.vault.svc.cluster.local
values:
global:
enabled: true
tlsDisable: false
server:
image:
repository: hashicorp/vault
tag: "2.2.1@sha256:962a92876e168d68eaaa24b0b8b49bbce1cb63024060a67bba95cd158a366fc2"
annotations:
certificates.quorate.internal/activation: '{"vault":"native"}'
volumes:
- name: vault-server-tls
secret:
secretName: vault-server-tls
defaultMode: 0440
volumeMounts:
- name: vault-server-tls
mountPath: /vault/userconfig/vault-server-tls
readOnly: true
resources:
requests:
cpu: 50m
memory: 256Mi
limits:
memory: 512Mi
affinity: |
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app.kubernetes.io/name: vault
component: server
topologyKey: kubernetes.io/hostname
ha:
enabled: true
replicas: 3
apiAddr: https://$(VAULT_K8S_POD_NAME).vault-internal.vault.svc.cluster.local:8200
raft:
enabled: true
setNodeId: true
config: |
ui = true
listener "tcp" {
address = "[::]:8200"
cluster_address = "[::]:8201"
tls_cert_file = "/vault/userconfig/vault-server-tls/tls.crt"
tls_key_file = "/vault/userconfig/vault-server-tls/tls.key"
tls_reload_interval = "1s"
tls_min_version = "tls12"
tls_disable_client_certs = true
}
storage "raft" {
path = "/vault/data"
retry_join {
leader_api_addr = "https://vault-0.vault-internal.vault.svc.cluster.local:8200"
leader_ca_cert_file = "/vault/userconfig/vault-server-tls/ca.crt"
leader_tls_servername = "vault-0.vault-internal.vault.svc.cluster.local"
}
retry_join {
leader_api_addr = "https://vault-1.vault-internal.vault.svc.cluster.local:8200"
leader_ca_cert_file = "/vault/userconfig/vault-server-tls/ca.crt"
leader_tls_servername = "vault-1.vault-internal.vault.svc.cluster.local"
}
retry_join {
leader_api_addr = "https://vault-2.vault-internal.vault.svc.cluster.local:8200"
leader_ca_cert_file = "/vault/userconfig/vault-server-tls/ca.crt"
leader_tls_servername = "vault-2.vault-internal.vault.svc.cluster.local"
}
}
service_registration "kubernetes" {}
dataStorage:
enabled: true
size: 10Gi
storageClass: openebs-hostpath
accessMode: ReadWriteOnce
auditStorage:
enabled: true
size: 5Gi
storageClass: openebs-hostpath
accessMode: ReadWriteOnce
serviceAccount:
create: true
podDisruptionBudget:
maxUnavailable: 1
statefulSet:
securityContext:
container:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
ui:
enabled: true
serviceType: ClusterIP
injector:
enabled: false
---
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: vault-secrets-operator
namespace: vault
spec:
interval: 15m
releaseName: vault-secrets-operator
targetNamespace: vault
dependsOn:
- name: vault
chart:
spec:
chart: vault-secrets-operator
version: "1.6.2"
sourceRef:
kind: HelmRepository
name: hashicorp
namespace: flux-system
install:
remediation:
retries: 3
upgrade:
remediation:
retries: 3
values:
controller:
replicas: 2
manager:
image:
tag: "1.6.2@sha256:8f77bc42ac47a4a1b2d1bb8b3a398fc53c19b3ce626002824a87ceb6cf91e5ad"
kubeRbacProxy:
image:
tag: "v0.23.1@sha256:c280684dd23793fb2f287d1f4efc84b7800b3e01205d770b98694827bfe514cb"
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
podDisruptionBudget:
enabled: true
minAvailable: 1
maxUnavailable: ""

View file

@ -0,0 +1,10 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- namespace.yaml
- certificate.yaml
- vso-upgrade-hook-policy.yaml
- helm-releases.yaml
- network-policies.yaml
- service-active-direct.yaml
- service-active-tls.yaml

6
vendor/platform/vault/namespace.yaml vendored Normal file
View file

@ -0,0 +1,6 @@
apiVersion: v1
kind: Namespace
metadata:
labels:
gateway.quorate.internal/allow-internal: "true"
name: vault

View file

@ -0,0 +1,215 @@
---
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: vault-server
namespace: vault
spec:
endpointSelector:
matchLabels:
app.kubernetes.io/name: vault
component: server
ingress:
# Raft peer-to-peer (intra-namespace only)
- fromEndpoints:
- matchLabels:
k8s:io.kubernetes.pod.namespace: vault
app.kubernetes.io/name: vault
component: server
toPorts:
- ports:
- port: "8200"
protocol: TCP
- port: "8201"
protocol: TCP
# port-forward from host (bootstrap: init, unseal, Terraform)
- fromEntities:
- host
toPorts:
- ports:
- port: "8200"
protocol: TCP
# cert-manager: Vault PKI signing via pki_int
- fromEndpoints:
- matchLabels:
k8s:io.kubernetes.pod.namespace: cert-manager
app.kubernetes.io/name: cert-manager
app.kubernetes.io/component: controller
toPorts:
- ports:
- port: "8200"
protocol: TCP
# VSO controller: reads secrets from Vault API
- fromEndpoints:
- matchLabels:
k8s:io.kubernetes.pod.namespace: vault
app.kubernetes.io/name: vault-secrets-operator
toPorts:
- ports:
- port: "8200"
protocol: TCP
- fromEndpoints:
- matchLabels:
k8s:io.kubernetes.pod.namespace: argo-opnsense-certificate
app.kubernetes.io/name: opnsense-certificate-rotator
app.kubernetes.io/instance: opnsense-certificate-rotator
toPorts:
- ports:
- port: "8200"
protocol: TCP
- fromEndpoints:
- matchLabels:
k8s:io.kubernetes.pod.namespace: stalwart
app.kubernetes.io/name: stalwart-controller
app.kubernetes.io/instance: stalwart
toPorts:
- ports:
- port: "8200"
protocol: TCP
- fromCIDR:
- 10.115.187.218/24
- 10.63.81.107/24
- 10.162.8.60/24
toPorts:
- ports:
- port: "8200"
protocol: TCP
egress:
# Raft peer-to-peer
- toEndpoints:
- matchLabels:
k8s:io.kubernetes.pod.namespace: vault
app.kubernetes.io/name: vault
component: server
toPorts:
- ports:
- port: "8200"
protocol: TCP
- port: "8201"
protocol: TCP
# Database engine: rotates PostgreSQL static role credentials
- toEndpoints:
- matchLabels:
k8s:io.kubernetes.pod.namespace: postgres
cnpg.io/cluster: main
toPorts:
- ports:
- port: "5432"
protocol: TCP
# Kubernetes service registration + TokenReview (k8s auth method)
- toEntities:
- kube-apiserver
toPorts:
- ports:
- port: "6443"
protocol: TCP
- toEndpoints:
- matchLabels:
k8s:io.kubernetes.pod.namespace: kube-system
k8s-app: kube-dns
toPorts:
- ports:
- port: "53"
protocol: UDP
- port: "53"
protocol: TCP
---
# vault-secrets-operator: TLS API on 8200, health probes on 8081 from kubelet.
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: vault-secrets-operator
namespace: vault
spec:
endpointSelector:
matchLabels:
app.kubernetes.io/name: vault-secrets-operator
control-plane: controller-manager
ingress:
# Kubelet health/readiness probes + kube-apiserver webhook calls
- fromEntities:
- host
toPorts:
- ports:
- port: "8081"
protocol: TCP
- port: "9443"
protocol: TCP
egress:
# Vault API
- toEndpoints:
- matchLabels:
k8s:io.kubernetes.pod.namespace: vault
app.kubernetes.io/name: vault
component: server
toPorts:
- ports:
- port: "8200"
protocol: TCP
# Kubernetes API (watch/patch secrets, TokenReview)
- toEntities:
- kube-apiserver
toPorts:
- ports:
- port: "6443"
protocol: TCP
- toEndpoints:
- matchLabels:
k8s:io.kubernetes.pod.namespace: kube-system
k8s-app: kube-dns
toPorts:
- ports:
- port: "53"
protocol: UDP
- port: "53"
protocol: TCP
---
# Host egress to vault pods (kubectl port-forward during bootstrap) and VSO health probes.
apiVersion: cilium.io/v2
kind: CiliumClusterwideNetworkPolicy
metadata:
name: host-egress-vault
spec:
nodeSelector: {}
egress:
- toEndpoints:
- matchLabels:
k8s:io.kubernetes.pod.namespace: vault
app.kubernetes.io/name: vault
component: server
toPorts:
- ports:
- port: "8200"
protocol: TCP
# Kubelet → VSO health probes; kube-apiserver → VSO webhook
- toEndpoints:
- matchLabels:
k8s:io.kubernetes.pod.namespace: vault
app.kubernetes.io/name: vault-secrets-operator
control-plane: controller-manager
toPorts:
- ports:
- port: "8081"
protocol: TCP
- port: "9443"
protocol: TCP
---
# Same-node Gateway/LB and host bootstrap paths can return to node-local
# ephemeral source ports via the host path.
apiVersion: cilium.io/v2
kind: CiliumClusterwideNetworkPolicy
metadata:
name: host-ingress-vault-replies
spec:
nodeSelector: {}
ingress:
- fromEndpoints:
- matchLabels:
k8s:io.kubernetes.pod.namespace: vault
app.kubernetes.io/name: vault
component: server
toPorts:
- ports:
- port: "32768"
endPort: 60999
protocol: TCP

View file

@ -0,0 +1,3 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources: []

View file

@ -0,0 +1,30 @@
apiVersion: v1
kind: Service
metadata:
name: vault-active-direct
namespace: vault
labels:
app.kubernetes.io/name: vault
app.kubernetes.io/instance: vault
io.cilium/bgp-announce: "true"
annotations:
lbipam.cilium.io/ips: "10.69.181.158"
spec:
type: LoadBalancer
externalTrafficPolicy: Local
publishNotReadyAddresses: false
loadBalancerSourceRanges:
- 10.115.187.218/24
- 10.63.81.107/24
- 10.162.8.60/24
selector:
app.kubernetes.io/name: vault
app.kubernetes.io/instance: vault
component: server
vault-active: "true"
ports:
- name: https
port: 443
targetPort: https-migration
protocol: TCP
appProtocol: https

View file

@ -0,0 +1,23 @@
apiVersion: v1
kind: Service
metadata:
name: vault-active-tls
namespace: vault
labels:
app.kubernetes.io/name: vault
app.kubernetes.io/instance: vault
app.kubernetes.io/component: server
spec:
type: ClusterIP
publishNotReadyAddresses: false
ports:
- name: https
port: 443
targetPort: https-migration
protocol: TCP
appProtocol: https
selector:
app.kubernetes.io/name: vault
app.kubernetes.io/instance: vault
component: server
vault-active: "true"

View file

@ -0,0 +1,26 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: label-vso-upgrade-hook
spec:
rules:
- name: propagate-app-labels
match:
any:
- resources:
kinds:
- Job
names:
- upgrade-crds-vault-secrets-operator
namespaces:
- vault
annotations:
helm.sh/hook: pre-upgrade
mutate:
patchStrategicMerge:
spec:
template:
metadata:
labels:
+(app.kubernetes.io/name): '{{ request.object.metadata.labels."app.kubernetes.io/name" }}'
+(app.kubernetes.io/instance): '{{ request.object.metadata.labels."app.kubernetes.io/instance" }}'