4c2f275f04
Why: shrink the blast radius of the Puppet control-plane pods (CA/eyaml keys, compiled catalogs) per the security sweep in #307 — remove root where it is not required and strip cargo-culted capabilities. How: - puppetboard cert-generator init: root+APE:true -> uid 1000, drop:[all], APE:false; pod fsGroup 1000; removed trailing `chown -R 1000:1000` (PVC now group-owned). - puppetdb create-log-dir init: root -> uid 999, drop:[all], APE:false; pod fsGroup 999; removed `chown 999:999`. - All OpenVox capability add-lists: removed the duplicate CAP_-prefixed spellings (k8s normalises both to the same kernel cap) and dropped the unused AUDIT_WRITE. - Added allowPrivilegeEscalation:false and seccompProfile RuntimeDefault across the workloads. Stays root (evidence-backed, class-B fallback): the puppetserver master/compiler and puppetdb main containers, plus the perms-and-dirs and generate-types root containers. The OpenVox image entrypoint runs `chown -R puppet:puppet` over root-owned baked-in dirs and drops the JVM to the puppet user via `runuser` (needs CHOWN/SETUID/SETGID); a non-root start crashloops. Their cap sets are reduced to the minimum justified (CHOWN/DAC_OVERRIDE/FOWNER[/SETUID/SETGID]). Validation: `kustomize build --enable-helm` clean; kubeconform 0 invalid / 0 errors; pre-commit (yamllint etc.) green. Confirmed against live pods: puppetserver/puppetdb JVMs already run as puppet/puppetdb via `runuser`; `pam_loginuid` is absent from the su/runuser PAM stacks and loginuid is unset, so dropping AUDIT_WRITE is safe. Post-merge smoke test (puppet had an outage this session — watch closely): after argocd sync, confirm puppetserver master + a compiler reach `running` at /status/v1/simple, puppetdb reaches `running`, puppetboard serves 200, and the generate-types + g10k CronJobs complete — i.e. catalogs still compile and reports still ingest. Closes #307 https://claude.ai/code/session_015ur3i7D2azsMAWTSVABApv Reviewed-on: #319 Co-authored-by: Ben Vincent <ben@unkin.net> Co-committed-by: Ben Vincent <ben@unkin.net>
177 lines
5.8 KiB
YAML
177 lines
5.8 KiB
YAML
---
|
|
apiVersion: apps/v1
|
|
kind: Deployment
|
|
metadata:
|
|
labels:
|
|
app.kubernetes.io/component: puppetboard
|
|
app.kubernetes.io/instance: puppetserver
|
|
app.kubernetes.io/name: puppetserver
|
|
app.kubernetes.io/version: 8.8.0
|
|
name: puppetboard
|
|
namespace: puppet
|
|
spec:
|
|
selector:
|
|
matchLabels:
|
|
app.kubernetes.io/component: puppetboard
|
|
app.kubernetes.io/name: puppetserver
|
|
strategy:
|
|
type: RollingUpdate
|
|
rollingUpdate:
|
|
maxUnavailable: 1
|
|
template:
|
|
metadata:
|
|
annotations:
|
|
configmap.reloader.stakater.com/auto: "true"
|
|
labels:
|
|
app.kubernetes.io/component: puppetboard
|
|
app.kubernetes.io/instance: puppetserver
|
|
app.kubernetes.io/name: puppetserver
|
|
app.kubernetes.io/version: 8.8.0
|
|
spec:
|
|
enableServiceLinks: false
|
|
securityContext:
|
|
fsGroup: 1000
|
|
fsGroupChangePolicy: OnRootMismatch
|
|
seccompProfile:
|
|
type: RuntimeDefault
|
|
initContainers:
|
|
- name: wait-puppetserver
|
|
image: curlimages/curl:8.11.1
|
|
imagePullPolicy: IfNotPresent
|
|
command:
|
|
- sh
|
|
- -c
|
|
- |
|
|
echo 'Waiting for puppetserver to become ready...'
|
|
until printf "." && curl --silent --fail --insecure 'https://puppetca:8140/status/v1/simple' | grep -q '^running$'; do
|
|
sleep 2;
|
|
done;
|
|
echo 'Puppetserver OK ✓'
|
|
resources:
|
|
limits:
|
|
cpu: 20m
|
|
memory: 32Mi
|
|
requests:
|
|
cpu: 20m
|
|
memory: 32Mi
|
|
- name: cert-generator
|
|
image: git.unkin.net/unkin/almalinux9-base:20260606
|
|
imagePullPolicy: IfNotPresent
|
|
command:
|
|
- sh
|
|
- -c
|
|
- |
|
|
set -e
|
|
|
|
# Set the hostname for the certificate
|
|
HOSTNAME="puppetboard"
|
|
CERT_DIR="/opt/puppetboard/ssl"
|
|
|
|
# Create certificate directory
|
|
mkdir -p ${CERT_DIR}
|
|
|
|
# Check if certificates already exist
|
|
if [ -f "${CERT_DIR}/${HOSTNAME}.pem" ] && [ -f "${CERT_DIR}/${HOSTNAME}.key" ] && [ -f "${CERT_DIR}/ca.pem" ]; then
|
|
echo "Certificates already exist for ${HOSTNAME}, skipping generation"
|
|
exit 0
|
|
fi
|
|
|
|
# Request certificate from Puppet CA for Puppetboard
|
|
echo "Requesting certificate for ${HOSTNAME} from puppetca service"
|
|
|
|
# Generate private key
|
|
openssl genrsa -out ${CERT_DIR}/${HOSTNAME}.key 2048
|
|
|
|
# Create certificate signing request (CSR)
|
|
openssl req -new -key ${CERT_DIR}/${HOSTNAME}.key \
|
|
-out /tmp/${HOSTNAME}.csr \
|
|
-subj "/CN=${HOSTNAME}"
|
|
|
|
# Submit CSR to Puppet CA
|
|
echo "Submitting certificate request to Puppet CA..."
|
|
curl -X PUT \
|
|
--insecure \
|
|
--data-binary @/tmp/${HOSTNAME}.csr \
|
|
-H "Content-Type: text/plain" \
|
|
https://puppetca:8140/puppet-ca/v1/certificate_request/${HOSTNAME}
|
|
|
|
# Wait for certificate to be signed (poll the CA)
|
|
echo "Waiting for certificate to be signed..."
|
|
for i in {1..30}; do
|
|
if curl --insecure -f -s https://puppetca:8140/puppet-ca/v1/certificate/${HOSTNAME} > ${CERT_DIR}/${HOSTNAME}.pem; then
|
|
echo "Certificate received for ${HOSTNAME}"
|
|
break
|
|
fi
|
|
echo "Attempt $i: Certificate not ready yet, waiting 10 seconds..."
|
|
sleep 10
|
|
done
|
|
|
|
# Verify we got the certificate
|
|
if [ ! -f "${CERT_DIR}/${HOSTNAME}.pem" ] || [ ! -s "${CERT_DIR}/${HOSTNAME}.pem" ]; then
|
|
echo "Failed to obtain certificate for ${HOSTNAME}"
|
|
exit 1
|
|
fi
|
|
|
|
# Get CA certificate
|
|
curl --insecure -f https://puppetca:8140/puppet-ca/v1/certificate/ca > ${CERT_DIR}/ca.pem
|
|
|
|
# Set appropriate permissions
|
|
chmod 644 ${CERT_DIR}/${HOSTNAME}.pem
|
|
chmod 600 ${CERT_DIR}/${HOSTNAME}.key
|
|
chmod 644 ${CERT_DIR}/ca.pem
|
|
|
|
echo "Certificate generation completed for ${HOSTNAME}"
|
|
volumeMounts:
|
|
- name: puppetboard-certs
|
|
mountPath: /opt/puppetboard/ssl
|
|
resources:
|
|
limits:
|
|
cpu: 100m
|
|
memory: 128Mi
|
|
requests:
|
|
cpu: 50m
|
|
memory: 64Mi
|
|
securityContext:
|
|
runAsUser: 1000
|
|
runAsGroup: 1000
|
|
runAsNonRoot: true
|
|
allowPrivilegeEscalation: false
|
|
capabilities:
|
|
drop:
|
|
- all
|
|
containers:
|
|
- name: puppetboard
|
|
image: ghcr.io/voxpupuli/puppetboard:7.0.1
|
|
imagePullPolicy: IfNotPresent
|
|
ports:
|
|
- containerPort: 80
|
|
name: puppetboard
|
|
envFrom:
|
|
- configMapRef:
|
|
name: puppetboard-config
|
|
- secretRef:
|
|
name: puppetboard-secrets
|
|
resources:
|
|
requests:
|
|
memory: 350Mi
|
|
cpu: 50m
|
|
limits:
|
|
memory: 1024Mi
|
|
cpu: 500m
|
|
securityContext:
|
|
runAsNonRoot: true
|
|
runAsUser: 1000
|
|
runAsGroup: 1000
|
|
allowPrivilegeEscalation: false
|
|
capabilities:
|
|
drop:
|
|
- all
|
|
volumeMounts:
|
|
- name: puppetboard-certs
|
|
mountPath: /opt/puppetboard/ssl
|
|
readOnly: true
|
|
volumes:
|
|
- name: puppetboard-certs
|
|
persistentVolumeClaim:
|
|
claimName: puppetboard-certs
|