feat(ql3): add opt-in security administration job

This commit is contained in:
whyour
2026-08-25 03:15:37 +08:00
parent fbedae0116
commit 931f9eee38
31 changed files with 1606 additions and 14 deletions
+12
View File
@@ -76,6 +76,18 @@ command schemas and operator procedure are documented in
response-loss behavior and remaining gates are frozen in
[`ADR-0500`](../../../docs/adr/ADR-0500-short-lived-cluster-security-administration-command.md).
An opt-in one-shot Job is available under
[`operations/security-administration`](./operations/security-administration).
It is deliberately absent from the shared operations Kustomization. Select
`base` or `cloudnative-pg`; select a credential-delivery variant only for
issue/rotate after provisioning a private encrypted RWO PVC. A non-root init
container copies the kubelet symlink projection into a `0700` memory-backed
directory with `0600` files before the administrator starts. The Job has no
Kubernetes API token or RBAC, does not retry, and retains the one-connection
database ceiling. The exact serial dispatch and cleanup procedure is in the
operator guide above; production remains gated on the ADR-0501 live K3s,
PostgreSQL and PVC ceremony.
Workstation operators may pass an explicit owner-private context to reuse only
stable client paths:
@@ -0,0 +1,134 @@
apiVersion: batch/v1
kind: Job
metadata:
name: ql3-security-administration
namespace: qinglong3-system
labels:
app.kubernetes.io/name: ql3-security-administration
app.kubernetes.io/component: security-administration
app.kubernetes.io/part-of: qinglong3
qinglong.io/execution-model: caller-driven
spec:
backoffLimit: 0
activeDeadlineSeconds: 300
ttlSecondsAfterFinished: 600
template:
metadata:
labels:
app.kubernetes.io/name: ql3-security-administration
app.kubernetes.io/component: security-administration
app.kubernetes.io/part-of: qinglong3
qinglong.io/execution-model: caller-driven
spec:
serviceAccountName: ql3-security-administration
automountServiceAccountToken: false
enableServiceLinks: false
restartPolicy: Never
securityContext:
runAsNonRoot: true
runAsUser: 10001
runAsGroup: 10001
fsGroup: 10001
seccompProfile:
type: RuntimeDefault
initContainers:
- name: stage-private-input
image: qinglong3-cluster-admin:3.0.0-alpha.0
imagePullPolicy: IfNotPresent
command:
- node
- /opt/qinglong/node_modules/@qinglong/cluster-admin/dist/security-administration/clusterAdministrationKubernetesInputStageCli.js
args:
- --source=/var/run/secrets/qinglong3/security-administration-projected
- --target=/var/run/qinglong3/security-administration-private/input
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
resources:
requests:
cpu: 25m
memory: 48Mi
limits:
cpu: 250m
memory: 128Mi
volumeMounts:
- name: projected-input
mountPath: /var/run/secrets/qinglong3/security-administration-projected
readOnly: true
- name: private-input
mountPath: /var/run/qinglong3/security-administration-private
containers:
- name: administrator
image: qinglong3-cluster-admin:3.0.0-alpha.0
imagePullPolicy: IfNotPresent
command:
- node
- /opt/qinglong/node_modules/@qinglong/cluster-admin/dist/security-administration/clusterAdministrationCli.js
args:
- --command=/var/run/qinglong3/security-administration-private/input/command.json
- --assertion=/var/run/qinglong3/security-administration-private/input/assertion.jwt
- --keyset=/var/run/qinglong3/security-administration-private/input/keyset.json
- --pepper=/var/run/qinglong3/security-administration-private/input/pepper
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
env:
- name: QL3_POSTGRES_ADMIN_TLS_MODE
value: verify-full
- name: QL3_POSTGRES_ADMIN_TLS_CA_FILE
value: /var/run/secrets/qinglong3/postgres-security-administration/ca.crt
- name: QL3_POSTGRES_ADMIN_URL
valueFrom:
secretKeyRef:
name: ql3-security-administration-database
key: postgres-admin-url
- name: QL3_POSTGRES_ADMIN_TLS_SERVERNAME
valueFrom:
secretKeyRef:
name: ql3-security-administration-database
key: postgres-tls-servername
resources:
requests:
cpu: 25m
memory: 48Mi
limits:
cpu: 250m
memory: 128Mi
volumeMounts:
- name: private-input
mountPath: /var/run/qinglong3/security-administration-private
readOnly: true
- name: postgres-ca
mountPath: /var/run/secrets/qinglong3/postgres-security-administration
readOnly: true
volumes:
- name: projected-input
secret:
secretName: ql3-security-administration-input
defaultMode: 288
items:
- key: command.json
path: command.json
- key: assertion.jwt
path: assertion.jwt
- key: keyset.json
path: keyset.json
- key: pepper
path: pepper
- name: private-input
emptyDir:
medium: Memory
sizeLimit: 1Mi
- name: postgres-ca
secret:
secretName: ql3-security-administration-database
defaultMode: 292
items:
- key: postgres-ca.crt
path: ca.crt
@@ -0,0 +1,7 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- service-account.yaml
- job.yaml
- network-policy.yaml
@@ -0,0 +1,31 @@
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: ql3-security-administration
namespace: qinglong3-system
labels:
app.kubernetes.io/name: ql3-security-administration
app.kubernetes.io/component: security-administration
app.kubernetes.io/part-of: qinglong3
spec:
podSelector:
matchLabels:
app.kubernetes.io/name: ql3-security-administration
app.kubernetes.io/component: security-administration
policyTypes:
- Ingress
- Egress
ingress: []
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
@@ -0,0 +1,10 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: ql3-security-administration
namespace: qinglong3-system
labels:
app.kubernetes.io/name: ql3-security-administration
app.kubernetes.io/component: security-administration
app.kubernetes.io/part-of: qinglong3
automountServiceAccountToken: false
@@ -0,0 +1,8 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../cloudnative-pg
components:
- ../credential-delivery/component
@@ -0,0 +1,31 @@
- op: replace
path: /spec/template/spec/containers/0/env
value:
- name: QL3_POSTGRES_ADMIN_TLS_MODE
value: verify-full
- name: QL3_POSTGRES_ADMIN_TLS_CA_FILE
value: /var/run/secrets/qinglong3/postgres-security-administration/ca.crt
- name: QL3_POSTGRES_ADMIN_HOST
value: ql3-postgres-rw.qinglong3-system.svc
- name: QL3_POSTGRES_ADMIN_PORT
value: '5432'
- name: QL3_POSTGRES_ADMIN_DATABASE
value: qinglong
- name: QL3_POSTGRES_ADMIN_USER
valueFrom:
secretKeyRef:
name: ql3-postgres-admin-auth
key: username
- name: QL3_POSTGRES_ADMIN_PASSWORD
valueFrom:
secretKeyRef:
name: ql3-postgres-admin-auth
key: password
- name: QL3_POSTGRES_ADMIN_TLS_SERVERNAME
value: ql3-postgres-rw.qinglong3-system.svc
- op: replace
path: /spec/template/spec/volumes/2/secret/secretName
value: ql3-postgres-ca
- op: replace
path: /spec/template/spec/volumes/2/secret/items/0/key
value: ca.crt
@@ -0,0 +1,24 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../base
patches:
- path: job-patch.yaml
target:
group: batch
version: v1
kind: Job
name: ql3-security-administration
- path: network-policy-patch.yaml
target:
group: networking.k8s.io
version: v1
kind: NetworkPolicy
name: ql3-security-administration
images:
- name: qinglong3-cluster-admin
newName: registry.example.com/qinglong/qinglong3-cluster-admin
digest: sha256:0000000000000000000000000000000000000000000000000000000000000000
@@ -0,0 +1,26 @@
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: ql3-security-administration
namespace: qinglong3-system
spec:
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
- to:
- podSelector:
matchLabels:
cnpg.io/cluster: ql3-postgres
ports:
- protocol: TCP
port: 5432
@@ -0,0 +1,22 @@
- op: add
path: /spec/template/spec/initContainers/0/args/-
value: --delivery-directory=/var/lib/qinglong3/security-administration-delivery/private
- op: add
path: /spec/template/spec/initContainers/0/volumeMounts/-
value:
name: credential-delivery
mountPath: /var/lib/qinglong3/security-administration-delivery
- op: add
path: /spec/template/spec/containers/0/args/-
value: --delivery=/var/lib/qinglong3/security-administration-delivery/private/replace-with-unique-delivery.json
- op: add
path: /spec/template/spec/containers/0/volumeMounts/-
value:
name: credential-delivery
mountPath: /var/lib/qinglong3/security-administration-delivery
- op: add
path: /spec/template/spec/volumes/-
value:
name: credential-delivery
persistentVolumeClaim:
claimName: ql3-security-administration-delivery
@@ -0,0 +1,10 @@
apiVersion: kustomize.config.k8s.io/v1alpha1
kind: Component
patches:
- path: job-patch.yaml
target:
group: batch
version: v1
kind: Job
name: ql3-security-administration
@@ -0,0 +1,13 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../base
components:
- component
images:
- name: qinglong3-cluster-admin
newName: registry.example.com/qinglong/qinglong3-cluster-admin
digest: sha256:0000000000000000000000000000000000000000000000000000000000000000
@@ -0,0 +1,12 @@
# Generic PostgreSQL example only. CloudNativePG users do not need this Secret.
apiVersion: v1
kind: Secret
metadata:
name: ql3-security-administration-database
namespace: qinglong3-system
type: Opaque
immutable: true
stringData:
postgres-admin-url: postgresql://ql3_admin:REPLACE@postgres.example.test:5432/qinglong
postgres-tls-servername: postgres.example.test
postgres-ca.crt: REPLACE_WITH_POSTGRES_CA_CERTIFICATE
@@ -0,0 +1,12 @@
# Example only. Use an encrypted, access-controlled StorageClass and a fresh PVC.
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: ql3-security-administration-delivery
namespace: qinglong3-system
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 16Mi
@@ -0,0 +1,14 @@
# Example only. Create a private per-dispatch Secret; never commit real values.
apiVersion: v1
kind: Secret
metadata:
name: ql3-security-administration-input
namespace: qinglong3-system
type: Opaque
immutable: true
stringData:
command.json: |
{"schemaVersion":1,"operation":"audit.list","request":{"limit":25,"filter":{"outcome":"allowed"}}}
assertion.jwt: REPLACE_WITH_SHORT_LIVED_MULTI_FACTOR_ASSERTION
keyset.json: REPLACE_WITH_PINNED_SECURITY_ADMINISTRATION_KEYSET
pepper: REPLACE_WITH_CANONICAL_32_BYTE_BASE64URL_API_CREDENTIAL_PEPPER