mirror of
https://github.com/kerberos-io/helm-charts.git
synced 2026-11-05 20:20:24 +00:00
Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
56ea65c6e7 | ||
|
|
af4e1c36a9 | ||
|
|
166b4a31e6 | ||
|
|
e938cb01b5 | ||
|
|
bd4e118a7a |
@@ -0,0 +1,28 @@
|
||||
name: HTTP port migration
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
migration:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: azure/setup-helm@v4.3.1
|
||||
- uses: helm/kind-action@v1
|
||||
- name: Validate migration against Kubernetes
|
||||
env:
|
||||
SERVER_VALIDATION: "true"
|
||||
run: bash scripts/check-http-port-migration.sh
|
||||
- name: Validate chart runtime compatibility
|
||||
run: |
|
||||
helm lint charts/hub
|
||||
bash scripts/check-admin-runtime.sh
|
||||
bash scripts/check-hardened-workers.sh
|
||||
bash scripts/check-frontend-runtime.sh
|
||||
bash scripts/check-hub-api-runtime.sh
|
||||
- name: Smoke test hardened frontend images and Service routing
|
||||
env:
|
||||
CLUSTER_SMOKE: "true"
|
||||
run: bash scripts/check-frontend-runtime.sh
|
||||
@@ -16,7 +16,7 @@ type: application
|
||||
# This is the chart version. This version number should be incremented each time you make changes
|
||||
# to the chart and its templates, including the app version.
|
||||
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
||||
version: 0.169.0
|
||||
version: 0.170.1
|
||||
|
||||
# This is the version number of the application being deployed. This version number should be
|
||||
# incremented each time you make changes to the application. Versions are not expected to
|
||||
|
||||
+104
-4
@@ -31,12 +31,41 @@ Uninstall the Kerberos Hub chart
|
||||
|
||||
helm uninstall hub -n kerberos-hub
|
||||
|
||||
### Hardened Hub API
|
||||
|
||||
Starting with chart **`0.170.1`**, `kerberoshub.api.securityContext` is rendered
|
||||
on the Hub API container and `kerberoshub.api.podSecurityContext` on its pod.
|
||||
The current image runs as `65532:65532` with a read-only root filesystem,
|
||||
privilege escalation disabled, all capabilities dropped and `RuntimeDefault`
|
||||
seccomp. A disk-backed `hub-api-tmp` emptyDir mounted at `/tmp`, with pod
|
||||
`fsGroup: 65532`, keeps attachment uploads and multipart parsing writable.
|
||||
API Service, ingress and port settings are unchanged.
|
||||
|
||||
Append custom mounts and volumes to the default lists rather than replacing
|
||||
`hub-api-tmp`. Helm replaces lists in overrides. Existing MongoDB TLS, API TLS,
|
||||
catalog and workflow-configuration mounts remain supported.
|
||||
|
||||
For a legacy image that requires its original identity or writable root
|
||||
filesystem, explicitly set both `kerberoshub.api.securityContext: null` and
|
||||
`kerberoshub.api.podSecurityContext: null`. Custom security settings are
|
||||
per-API; other workloads do not inherit them. When upgrading, check retained
|
||||
values (`--reuse-values` or GitOps overrides) so the security context and
|
||||
writable scratch mount stay aligned.
|
||||
|
||||
Run `bash scripts/check-hub-api-runtime.sh` to validate defaults, overrides,
|
||||
legacy opt-out, TLS/configuration mounts and deployment modes.
|
||||
|
||||
### Hardened frontend images
|
||||
|
||||
New shell-free frontend images run as `65532:65532` and listen on port `8080`.
|
||||
Before selecting such an image tag, set the following values together. Defaults
|
||||
remain compatible with legacy images on port `80`; this chart does not upgrade
|
||||
the frontend image automatically. The Service and ingress remain on port `80`.
|
||||
Starting with chart **`0.170.0`**, both frontends default to shell-free
|
||||
`ghcr.io/uug-ai/hub-frontend:v1.19.28`, running as `65532:65532` on port `8080`
|
||||
with a read-only root filesystem and writable `/tmp`. The Service and ingress
|
||||
remain on port `80`. Admin retains its legacy image and port `80`; its hardened
|
||||
runtime remains opt-in.
|
||||
|
||||
The following frontend runtime settings are now the defaults. When upgrading,
|
||||
remove stale port `80` overrides and coordinate image/runtime values; Helm
|
||||
`--reuse-values` and GitOps overrides may retain the old settings.
|
||||
Use chart `0.167.2` or newer when the demo frontend is enabled; `0.167.1`
|
||||
supports these settings only on the primary frontend.
|
||||
|
||||
@@ -73,6 +102,26 @@ the primary and demo frontends, which share the frontend image tag. Update the
|
||||
live Argo CD Application's chart pin as well as its values: an image-only
|
||||
upgrade leaves older charts forwarding to port `80` and causes gateway errors.
|
||||
|
||||
For legacy frontend images or a rollback, explicitly restore **all** runtime
|
||||
settings together (including removing inherited security defaults):
|
||||
|
||||
```yaml
|
||||
kerberoshub:
|
||||
frontend:
|
||||
tag: v1.13.9
|
||||
containerPort: 80
|
||||
securityContext: null
|
||||
volumes: []
|
||||
volumeMounts: []
|
||||
```
|
||||
|
||||
Keep any application-specific branding mounts when adjusting these lists.
|
||||
Chart upgrades do not rewrite separately managed NodePort Services: ensure they
|
||||
target the named `http` port, not numeric `80`. Follow the duplicate-port recovery
|
||||
instructions below if the existing Deployment cannot be patched to `8080`.
|
||||
Run `bash scripts/check-frontend-runtime.sh` for default, demo, legacy override,
|
||||
and deployment-values regression coverage.
|
||||
|
||||
### Hardened Admin image (opt-in, chart 0.167.3)
|
||||
|
||||
`charts/hub` deploys Admin in `all` and `ui` modes. Shell-free Admin `v1.4.3`
|
||||
@@ -128,6 +177,57 @@ security context, and disable `/healthz` probes together. Run
|
||||
`bash scripts/check-admin-runtime.sh` from the repository root to verify
|
||||
legacy defaults, hardened runtime/probes, and unchanged OAuth2 routing.
|
||||
|
||||
### Recovering a duplicate HTTP port during upgrades
|
||||
|
||||
Chart `0.169.1` publishes this recovery guidance. The helper is available in the
|
||||
repository's `scripts/` directory, not in the chart archive. This release does
|
||||
not automatically migrate live container ports or change runtime defaults.
|
||||
|
||||
An upgrade changing a container's HTTP port from `80` to `8080` can fail with
|
||||
`spec.template.spec.containers[0].ports[1].name: Duplicate value: "http"`.
|
||||
Kubernetes strategic merges identify container ports by number, not name:
|
||||
depending on the live state and apply mechanism, the old `80` entry can survive
|
||||
beside the new `8080` entry. Fresh chart renders contain only one HTTP port.
|
||||
This is not a reason to rename `http`: Services and probes reference that name.
|
||||
|
||||
Inspect the failing Deployment and confirm the image's listening port first:
|
||||
|
||||
```sh
|
||||
kubectl -n kerberos-hub get deployment hub-frontend -o yaml
|
||||
```
|
||||
|
||||
In a checkout of this repository, use the targeted migration helper to replace
|
||||
the named HTTP entry (not strategically merge it). It supports `admin`,
|
||||
`hub-frontend` and `hub-frontend-demo`, preserves unrelated ports and containers,
|
||||
and rejects unfamiliar HTTP configurations. It requires `kubectl` and `jq` and
|
||||
uses the current kubeconfig context. The default is a **server dry run**:
|
||||
|
||||
```sh
|
||||
bash scripts/migrate-http-port.sh kerberos-hub hub-frontend 8080
|
||||
# Apply only after inspecting the dry run and confirming the target/context.
|
||||
bash scripts/migrate-http-port.sh kerberos-hub hub-frontend 8080 --apply
|
||||
```
|
||||
|
||||
Run this only for affected Deployments, using the port selected in your upgrade
|
||||
values. Then retry the Helm upgrade or GitOps sync with the coordinated image,
|
||||
port, security context and scratch-volume settings above. Pause GitOps auto-sync
|
||||
during manual recovery so it cannot race or undo the patch, and resume it after
|
||||
the desired state has synced. The helper uses resource-version/name tests to
|
||||
reject concurrent changes; re-inspect and retry if those tests fail.
|
||||
|
||||
Changing the pod template triggers a rollout, and setting `8080` while the old
|
||||
image still listens on `80` can temporarily interrupt Service traffic. Schedule
|
||||
the recovery in a maintenance window and complete the matching image upgrade
|
||||
immediately. The helper does not change images, mount scratch volumes, or repair
|
||||
unrelated readiness failures. For a legacy-image rollback use port `80` instead,
|
||||
restore the matching runtime settings, and retry the rollback. Do not use blanket
|
||||
`helm upgrade --force` or resource replacement for unrelated workloads.
|
||||
|
||||
Run `bash scripts/check-http-port-migration.sh` to reproduce the strategic-merge
|
||||
failure and test targeted replacement, idempotency, and reverse migration for all
|
||||
three Deployments. CI also checks API rejection of duplicate names and acceptance
|
||||
of the repaired spec against a disposable Kind cluster.
|
||||
|
||||
### Hardened Hub workers (opt-in, chart 0.167.3)
|
||||
|
||||
The worker templates accept **per-component** `securityContext` (container) and
|
||||
|
||||
@@ -140,6 +140,10 @@ spec:
|
||||
labels:
|
||||
app: hub-api
|
||||
spec:
|
||||
{{- with .Values.kerberoshub.api.podSecurityContext }}
|
||||
securityContext:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- if .Values.kerberoshub.serviceAccount.create }}
|
||||
serviceAccountName: {{ default (printf "%s-%s-sa" .Release.Name .Chart.Name | trunc 63 | trimSuffix "-") .Values.kerberoshub.serviceAccount.name }}
|
||||
{{- end }}
|
||||
@@ -229,6 +233,10 @@ spec:
|
||||
- name: hub-api
|
||||
image: "{{ .Values.global.imageRegistry }}{{ .Values.kerberoshub.api.repository }}:{{ .Values.kerberoshub.api.tag }}"
|
||||
imagePullPolicy: {{ .Values.kerberoshub.api.pullPolicy }}
|
||||
{{- with .Values.kerberoshub.api.securityContext }}
|
||||
securityContext:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- with .Values.kerberoshub.api.resources }}
|
||||
resources:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
|
||||
@@ -110,7 +110,7 @@ spec:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
ports:
|
||||
- containerPort: {{ .Values.kerberoshub.frontend.containerPort | default 80 }}
|
||||
- containerPort: {{ .Values.kerberoshub.frontend.containerPort | default 8080 }}
|
||||
name: http
|
||||
{{- with .Values.kerberoshub.frontend.volumeMounts}}
|
||||
volumeMounts:
|
||||
|
||||
@@ -191,7 +191,7 @@ spec:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
ports:
|
||||
- containerPort: {{ .Values.kerberoshub.frontend.containerPort | default 80 }}
|
||||
- containerPort: {{ .Values.kerberoshub.frontend.containerPort | default 8080 }}
|
||||
name: http
|
||||
{{- with .Values.kerberoshub.frontend.volumeMounts}}
|
||||
volumeMounts:
|
||||
|
||||
+59
-25
@@ -50,7 +50,6 @@ mongodb:
|
||||
# hub-pipeline-monitor, ...) instead of the one in uri, so the Atlas query
|
||||
# profiler and currentOp show which service issued an operation.
|
||||
appNamePerService: true
|
||||
|
||||
# Self-hosted mongodb, you could also use the uri property above.
|
||||
host: mongodb.mongodb
|
||||
adminDatabase: admin
|
||||
@@ -73,7 +72,6 @@ mongodb:
|
||||
caFileName: ""
|
||||
mountPath: "/etc/mongodb/tls"
|
||||
insecureSkipVerify: false
|
||||
|
||||
# Classifications supported by Hub filters, alerts, markers, and other
|
||||
# downstream processing. Classifier output keys must match these values.
|
||||
# Disable this to use the legacy classification catalog stored in MongoDB.
|
||||
@@ -115,7 +113,6 @@ deviceTags:
|
||||
# - key: anpr-pilot
|
||||
# label: ANPR pilot
|
||||
# description: Runs licence plate recognition on this camera
|
||||
|
||||
# Configuration packaging only: consuming contracts requires a compatible
|
||||
# API/frontend. Enabling this mounts files but does not register stage workers.
|
||||
workflowConfiguration:
|
||||
@@ -339,8 +336,27 @@ kerberoshub:
|
||||
api:
|
||||
repository: ghcr.io/uug-ai/hub-api
|
||||
pullPolicy: IfNotPresent
|
||||
tag: "v1.9.51"
|
||||
tag: "v1.11.31"
|
||||
replicas: 2
|
||||
podSecurityContext:
|
||||
fsGroup: 65532
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 65532
|
||||
runAsGroup: 65532
|
||||
readOnlyRootFilesystem: true
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop: ["ALL"]
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
# Keep writable scratch space for attachment uploads and multipart parsing.
|
||||
volumes:
|
||||
- name: hub-api-tmp
|
||||
emptyDir: {}
|
||||
volumeMounts:
|
||||
- name: hub-api-tmp
|
||||
mountPath: /tmp
|
||||
audit:
|
||||
enabled: true
|
||||
topologySpreadConstraints: [] # Optional pod topology spread constraints (empty = none).
|
||||
@@ -375,6 +391,7 @@ kerberoshub:
|
||||
memory: 100Mi
|
||||
cpu: 250m
|
||||
# E-mail templates
|
||||
# Append custom mounts/volumes to the lists above; retain hub-api-tmp.
|
||||
#volumeMounts:
|
||||
# - name: custom-email-templates
|
||||
# mountPath: /mail
|
||||
@@ -482,11 +499,28 @@ kerberoshub:
|
||||
frontend:
|
||||
repository: ghcr.io/uug-ai/hub-frontend
|
||||
pullPolicy: IfNotPresent
|
||||
tag: "v1.13.9"
|
||||
tag: "v1.19.28"
|
||||
replicas: 2
|
||||
# Hardened frontend images listen on 8080; retain 80 for existing images.
|
||||
containerPort: 80
|
||||
securityContext: {}
|
||||
containerPort: 8080
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 65532
|
||||
runAsGroup: 65532
|
||||
readOnlyRootFilesystem: true
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop: ["ALL"]
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
# Keep these entries when adding custom branding/translation mounts.
|
||||
volumes:
|
||||
- name: frontend-tmp
|
||||
emptyDir:
|
||||
medium: Memory
|
||||
sizeLimit: 16Mi
|
||||
volumeMounts:
|
||||
- name: frontend-tmp
|
||||
mountPath: /tmp
|
||||
topologySpreadConstraints: [] # Optional pod topology spread constraints (empty = none).
|
||||
# Set to false to skip rendering the hub-frontend Service (e.g. when an
|
||||
# external/shared Service is managed elsewhere).
|
||||
@@ -776,7 +810,7 @@ kerberoshub:
|
||||
podSecurityContext: {}
|
||||
repository: ghcr.io/uug-ai/hub-cleanup
|
||||
pullPolicy: IfNotPresent
|
||||
tag: "v1.4.19"
|
||||
tag: "v1.4.26"
|
||||
replicas: 1 # Number of pods for the service.
|
||||
topologySpreadConstraints: [] # Optional pod topology spread constraints (empty = none).
|
||||
# Optional extra volumes / volumeMounts for this deployment (empty = none).
|
||||
@@ -1141,7 +1175,7 @@ kerberoshub:
|
||||
podSecurityContext: {}
|
||||
repository: ghcr.io/uug-ai/hub-monitor-device
|
||||
pullPolicy: IfNotPresent
|
||||
tag: "v1.4.2"
|
||||
tag: "v1.4.10"
|
||||
replicas: 1 # Number of pods for the service.
|
||||
topologySpreadConstraints: [] # Optional pod topology spread constraints (empty = none).
|
||||
logLevel: "info" # possible values: trace, debug, info, warn, error
|
||||
@@ -1229,7 +1263,7 @@ kerberospipeline:
|
||||
# argocd.argoproj.io/sync-wave: "10"
|
||||
repository: ghcr.io/uug-ai/hub-pipeline-event
|
||||
pullPolicy: IfNotPresent
|
||||
tag: "v1.3.1"
|
||||
tag: "v1.3.9"
|
||||
replicas: 1 # Number of pods for the service.
|
||||
topologySpreadConstraints: [] # Optional pod topology spread constraints (empty = none).
|
||||
# Optional extra volumes / volumeMounts for this deployment (empty = none).
|
||||
@@ -1250,7 +1284,7 @@ kerberospipeline:
|
||||
# deploymentAnnotations:
|
||||
# argocd.argoproj.io/sync-wave: "70"
|
||||
pullPolicy: IfNotPresent
|
||||
tag: "v1.3.13"
|
||||
tag: "v1.3.29"
|
||||
replicas: 1 # Number of pods for the service.
|
||||
topologySpreadConstraints: [] # Optional pod topology spread constraints (empty = none).
|
||||
# Optional extra volumes / volumeMounts for this deployment (empty = none).
|
||||
@@ -1271,7 +1305,7 @@ kerberospipeline:
|
||||
# deploymentAnnotations:
|
||||
# argocd.argoproj.io/sync-wave: "60"
|
||||
pullPolicy: IfNotPresent
|
||||
tag: "v1.6.26"
|
||||
tag: "v1.7.14"
|
||||
replicas: 1 # Number of pods for the service.
|
||||
topologySpreadConstraints: [] # Optional pod topology spread constraints (empty = none).
|
||||
# Optional extra volumes / volumeMounts for this deployment (empty = none).
|
||||
@@ -1289,12 +1323,12 @@ kerberospipeline:
|
||||
throttler:
|
||||
securityContext: {}
|
||||
podSecurityContext: {}
|
||||
repository: uugai/hub-pipeline-throttler
|
||||
repository: ghcr.io/uug-ai/hub-pipeline-throttler
|
||||
deploymentAnnotations: {}
|
||||
# deploymentAnnotations:
|
||||
# argocd.argoproj.io/sync-wave: "30"
|
||||
pullPolicy: IfNotPresent
|
||||
tag: "v1.2.1"
|
||||
tag: "v1.2.10"
|
||||
replicas: 1 # Number of pods for the service.
|
||||
topologySpreadConstraints: [] # Optional pod topology spread constraints (empty = none).
|
||||
# Optional extra volumes / volumeMounts for this deployment (empty = none).
|
||||
@@ -1317,7 +1351,7 @@ kerberospipeline:
|
||||
# deploymentAnnotations:
|
||||
# argocd.argoproj.io/sync-wave: "20"
|
||||
pullPolicy: IfNotPresent
|
||||
tag: "v1.3.18"
|
||||
tag: "v1.3.46"
|
||||
replicas: 1 # Number of pods for the service.
|
||||
audit:
|
||||
enabled: true
|
||||
@@ -1337,9 +1371,9 @@ kerberospipeline:
|
||||
# persistentVolumeClaim:
|
||||
# claimName: custom-layout-claim
|
||||
notifyTest:
|
||||
repository: uugai/hub-pipeline-notification-test
|
||||
repository: ghcr.io/uug-ai/hub-pipeline-notification-test
|
||||
pullPolicy: IfNotPresent
|
||||
tag: "v1.2.2"
|
||||
tag: "v1.2.6"
|
||||
replicas: 1 # Number of pods for the service.
|
||||
topologySpreadConstraints: [] # Optional pod topology spread constraints (empty = none).
|
||||
resources:
|
||||
@@ -1360,7 +1394,7 @@ kerberospipeline:
|
||||
# deploymentAnnotations:
|
||||
# argocd.argoproj.io/sync-wave: "40"
|
||||
pullPolicy: IfNotPresent
|
||||
tag: "v1.8.22"
|
||||
tag: "v1.8.29"
|
||||
replicas: 1 # Number of pods for the service.
|
||||
topologySpreadConstraints: [] # Optional pod topology spread constraints (empty = none).
|
||||
# Optional extra volumes / volumeMounts for this deployment (empty = none).
|
||||
@@ -1398,7 +1432,7 @@ kerberospipeline:
|
||||
# deploymentAnnotations:
|
||||
# argocd.argoproj.io/sync-wave: "50"
|
||||
pullPolicy: IfNotPresent
|
||||
tag: "v2.0.3"
|
||||
tag: "v2.0.9"
|
||||
replicas: 3 # Number of pods for the service.
|
||||
topologySpreadConstraints: [] # Optional pod topology spread constraints (empty = none).
|
||||
# Optional extra volumes / volumeMounts for this deployment (empty = none).
|
||||
@@ -1424,7 +1458,7 @@ kerberospipeline:
|
||||
# deploymentAnnotations:
|
||||
# argocd.argoproj.io/sync-wave: "50"
|
||||
pullPolicy: IfNotPresent
|
||||
tag: "v1.3.15"
|
||||
tag: "v1.3.22"
|
||||
replicas: 2 # Number of pods for the service.
|
||||
prefetchCount: 0 # Max unacknowledged RabbitMQ messages per pod; 0 = service default (5).
|
||||
topologySpreadConstraints: [] # Optional pod topology spread constraints (empty = none).
|
||||
@@ -1452,12 +1486,12 @@ kerberospipeline:
|
||||
counting:
|
||||
securityContext: {}
|
||||
podSecurityContext: {}
|
||||
repository: uugai/hub-pipeline-counting
|
||||
repository: ghcr.io/uug-ai/hub-pipeline-counting
|
||||
deploymentAnnotations: {}
|
||||
# deploymentAnnotations:
|
||||
# argocd.argoproj.io/sync-wave: "50"
|
||||
pullPolicy: IfNotPresent
|
||||
tag: "v2.0.0"
|
||||
tag: "v2.0.6"
|
||||
replicas: 1 # Number of pods for the service.
|
||||
topologySpreadConstraints: [] # Optional pod topology spread constraints (empty = none).
|
||||
# Optional extra volumes / volumeMounts for this deployment (empty = none).
|
||||
@@ -1479,7 +1513,7 @@ kerberospipeline:
|
||||
# argocd.argoproj.io/sync-wave: "50"
|
||||
repository: ghcr.io/uug-ai/hub-pipeline-sprite
|
||||
pullPolicy: IfNotPresent
|
||||
tag: "v1.1.21"
|
||||
tag: "v1.1.27"
|
||||
replicas: 5 # Number of pods for the service.
|
||||
prefetchCount: 0 # Max unacknowledged RabbitMQ messages per pod; 0 = service default (5).
|
||||
topologySpreadConstraints: [] # Optional pod topology spread constraints (empty = none).
|
||||
@@ -1506,7 +1540,7 @@ kerberospipeline:
|
||||
export:
|
||||
repository: ghcr.io/uug-ai/hub-pipeline-export
|
||||
pullPolicy: IfNotPresent
|
||||
tag: "v1.2.10"
|
||||
tag: "v1.2.20"
|
||||
replicas: 2 # Number of pods for the service.
|
||||
topologySpreadConstraints: [] # Optional pod topology spread constraints (empty = none).
|
||||
logLevel: "info" # possible values: trace, debug, info, warn, error
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
CHART_DIR="${1:-charts/hub}"
|
||||
values=()
|
||||
if [[ $# -ge 2 ]]; then values=(--values "$2"); fi
|
||||
|
||||
resource() {
|
||||
local kind="$1" name="$2"
|
||||
awk -v kind="$kind" -v name="$name" '
|
||||
function emit() { if (matchedKind && matchedName) printf "%s", block }
|
||||
/^---$/ { emit(); block=""; matchedKind=0; matchedName=0 }
|
||||
{ block=block $0 "\n" }
|
||||
$0 == "kind: " kind { matchedKind=1 }
|
||||
$0 == " name: " name { matchedName=1 }
|
||||
END { emit() }
|
||||
'
|
||||
}
|
||||
|
||||
assert_contains() {
|
||||
if ! grep -Eq -- "$2" <<< "$1"; then
|
||||
printf 'Missing expected frontend setting: %s\n' "$2" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
for mode in all ui; do
|
||||
rendered="$(helm template hub "$CHART_DIR" "${values[@]}" --set "mode=$mode" \
|
||||
--set kerberoshub.frontend.demoEnabled=true)"
|
||||
for name in hub-frontend hub-frontend-demo; do
|
||||
deployment="$(resource Deployment "$name" <<< "$rendered")"
|
||||
assert_contains "$deployment" 'image: "ghcr.io/uug-ai/hub-frontend:v1.19.28"$'
|
||||
assert_contains "$deployment" 'containerPort: 8080$'
|
||||
[[ "$(grep -c 'containerPort:' <<< "$deployment")" == 1 ]]
|
||||
for setting in 'name: http' 'runAsNonRoot: true' 'runAsUser: 65532' \
|
||||
'runAsGroup: 65532' 'readOnlyRootFilesystem: true' \
|
||||
'allowPrivilegeEscalation: false' 'type: RuntimeDefault' 'drop:' '- ALL' \
|
||||
'medium: Memory' 'sizeLimit: 16Mi' 'mountPath: /tmp' 'name: frontend-tmp'; do
|
||||
assert_contains "$deployment" "^[[:space:]]*(- )?$setting$"
|
||||
done
|
||||
service="$(resource Service "$name-svc" <<< "$rendered")"
|
||||
assert_contains "$service" 'port: 80$'
|
||||
assert_contains "$service" 'targetPort: http$'
|
||||
echo "OK ($mode): $name hardened image, security, scratch and named routing"
|
||||
done
|
||||
admin="$(resource Deployment admin <<< "$rendered")"
|
||||
assert_contains "$admin" 'containerPort: 80$'
|
||||
|
||||
legacy="$(helm template hub "$CHART_DIR" "${values[@]}" --set "mode=$mode" \
|
||||
--set kerberoshub.frontend.demoEnabled=true \
|
||||
--set kerberoshub.frontend.tag=v1.13.9 \
|
||||
--set kerberoshub.frontend.containerPort=80 \
|
||||
--set kerberoshub.frontend.securityContext=null \
|
||||
--set-json 'kerberoshub.frontend.volumes=[]' \
|
||||
--set-json 'kerberoshub.frontend.volumeMounts=[]')"
|
||||
for name in hub-frontend hub-frontend-demo; do
|
||||
deployment="$(resource Deployment "$name" <<< "$legacy")"
|
||||
assert_contains "$deployment" 'containerPort: 80$'
|
||||
assert_contains "$deployment" 'image: "ghcr.io/uug-ai/hub-frontend:v1.13.9"$'
|
||||
if grep -Eq 'securityContext:|mountPath: /tmp' <<< "$deployment"; then
|
||||
echo "Legacy frontend retains incompatible hardened runtime settings" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
done
|
||||
|
||||
if [[ "${CLUSTER_SMOKE:-false}" == true ]]; then
|
||||
namespace=frontend-runtime-test
|
||||
kubectl create namespace "$namespace"
|
||||
helm template runtime "$CHART_DIR" "${values[@]}" --namespace "$namespace" \
|
||||
--set mode=ui --set ingress="" --set kerberoshub.frontend.demoEnabled=true \
|
||||
--show-only templates/kerberos-hub/hub-frontend.yaml \
|
||||
--show-only templates/kerberos-hub/hub-frontend-demo.yaml | kubectl apply -f -
|
||||
pid=""
|
||||
cleanup() {
|
||||
if [[ -n "$pid" ]]; then kill "$pid"; fi
|
||||
if [[ "$1" != 0 ]]; then
|
||||
kubectl -n "$namespace" get pods
|
||||
kubectl -n "$namespace" describe pods
|
||||
kubectl -n "$namespace" logs -l app=hub-frontend --all-containers=true
|
||||
kubectl -n "$namespace" logs -l app=hub-frontend-demo --all-containers=true
|
||||
fi
|
||||
}
|
||||
trap 'cleanup "$?"' EXIT
|
||||
for name in hub-frontend hub-frontend-demo; do
|
||||
kubectl -n "$namespace" rollout status "deployment/$name" --timeout=180s
|
||||
kubectl -n "$namespace" port-forward "service/$name-svc" 18080:80 &
|
||||
pid=$!
|
||||
for attempt in {1..30}; do
|
||||
if curl --fail --silent http://127.0.0.1:18080/healthz > /dev/null; then break; fi
|
||||
sleep 1
|
||||
done
|
||||
[[ "$(curl --fail --silent http://127.0.0.1:18080/healthz)" == ok ]]
|
||||
curl --fail --silent http://127.0.0.1:18080/assets/env.js | grep 'window' > /dev/null
|
||||
kill "$pid"
|
||||
wait "$pid" || [[ "$?" == 143 ]]
|
||||
pid=""
|
||||
echo "OK: $name serves health and generated runtime configuration through Service port 80"
|
||||
done
|
||||
fi
|
||||
|
||||
echo "Frontend runtime checks passed."
|
||||
@@ -60,10 +60,19 @@ assert_line() {
|
||||
fi
|
||||
}
|
||||
|
||||
without_frontends() {
|
||||
awk '
|
||||
/^---$/ { skip=0 }
|
||||
/^# Source: hub\/templates\/kerberos-hub\/(hub-frontend(-demo)?|hub-api)\.yaml$/ { skip=1 }
|
||||
!skip { print }
|
||||
'
|
||||
}
|
||||
|
||||
for mode in all pipeline ui; do
|
||||
defaults="$(helm template hub "$CHART_DIR" --set "mode=$mode")"
|
||||
if grep -q 'securityContext:' <<< "$defaults"; then
|
||||
echo "Default $mode mode unexpectedly enables security settings" >&2
|
||||
worker_defaults="$(without_frontends <<< "$defaults")"
|
||||
if grep -q 'securityContext:' <<< "$worker_defaults"; then
|
||||
echo "Default $mode mode unexpectedly enables worker security settings" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -111,8 +120,8 @@ for mode in all pipeline ui; do
|
||||
fi
|
||||
done
|
||||
expected="${#names[@]}"
|
||||
[[ "$(grep -c 'runAsUser: 65532' <<< "$hardened")" == "$expected" ]]
|
||||
[[ "$(grep -c 'fsGroup: 65532' <<< "$hardened")" == "$expected" ]]
|
||||
[[ "$(without_frontends <<< "$hardened" | grep -c 'runAsUser: 65532')" == "$expected" ]]
|
||||
[[ "$(without_frontends <<< "$hardened" | grep -c 'fsGroup: 65532')" == "$expected" ]]
|
||||
|
||||
# Image selection, Services, env and TLS mounts must not change.
|
||||
for pattern in '^[ ]+image:' '^[ ]+(port|targetPort|containerPort):' 'mongodb-tls'; do
|
||||
@@ -179,7 +188,7 @@ for mode in all ui pipeline; do
|
||||
[[ "$(resource Service oauth2-proxy-admin <<< "$legacy")" == "$(resource Service oauth2-proxy-admin <<< "$configured")" ]]
|
||||
[[ "$(grep 'image:' <<< "$legacy")" == "$(grep 'image:' <<< "$configured")" ]]
|
||||
# No other workload inherits Admin's container port or security settings.
|
||||
[[ "$(grep -c 'runAsUser: 65532' <<< "$configured")" == 1 ]]
|
||||
[[ "$(without_frontends <<< "$configured" | grep -c 'runAsUser: 65532')" == 1 ]]
|
||||
echo "OK ($mode): Admin opt-in port/security/tmpfs; legacy image, Service port, ingress and OAuth2-proxy preserved"
|
||||
done
|
||||
echo "All hardened Admin checks passed."
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
REAL_KUBECTL="$(command -v kubectl)"
|
||||
export REAL_KUBECTL
|
||||
|
||||
# Substitute only transport: exercise the migration against Kubernetes' real patch engine.
|
||||
kubectl() {
|
||||
if [[ "$*" == *" get deployment "* ]]; then
|
||||
printf '%s\n' "$LIVE"
|
||||
else
|
||||
local patch="" previous="" argument
|
||||
for argument in "$@"; do
|
||||
if [[ "$previous" == -p ]]; then patch="$argument"; fi
|
||||
previous="$argument"
|
||||
done
|
||||
if [[ "${EXPECT_APPLY:-false}" == true ]]; then
|
||||
[[ "$*" != *"--dry-run=server"* ]] || return 1
|
||||
else
|
||||
[[ "$*" == *"--dry-run=server"* ]] || return 1
|
||||
fi
|
||||
local current="$LIVE"
|
||||
if [[ "${SIMULATE_RACE:-false}" == true ]]; then
|
||||
current="$(jq '.metadata.resourceVersion = "2"' <<< "$LIVE")"
|
||||
fi
|
||||
"$REAL_KUBECTL" patch --local -f <(printf '%s\n' "$current") \
|
||||
--type=json -p "$patch" -o json
|
||||
fi
|
||||
}
|
||||
export -f kubectl
|
||||
|
||||
for deployment in admin hub-frontend hub-frontend-demo; do
|
||||
for old in 80 8080; do
|
||||
target=8080
|
||||
if [[ "$old" == 8080 ]]; then target=80; fi
|
||||
LIVE="$(jq -nc --arg name "$deployment" --argjson port "$old" '{
|
||||
apiVersion: "apps/v1", kind: "Deployment",
|
||||
metadata: {name: $name, resourceVersion: "1"},
|
||||
spec: {selector: {matchLabels: {app: $name}}, template: {
|
||||
metadata: {labels: {app: $name}}, spec: {containers: [
|
||||
{name: "sidecar", image: "example/sidecar:unchanged"},
|
||||
{name: $name, image: "example/frontend:unchanged", ports: [
|
||||
{name: "http", containerPort: $port},
|
||||
{name: "metrics", containerPort: 2112}
|
||||
]}
|
||||
]}
|
||||
}}
|
||||
}')"
|
||||
export LIVE
|
||||
merge="$(jq -nc --arg name "$deployment" --argjson port "$target" \
|
||||
'{spec:{template:{spec:{containers:[{name:$name,ports:[{name:"http",containerPort:$port}]}]}}}}')"
|
||||
merged="$("$REAL_KUBECTL" patch --local -f <(printf '%s\n' "$LIVE") \
|
||||
--type=strategic -p "$merge" -o json)"
|
||||
jq -e '[.spec.template.spec.containers[] | .ports[]? |
|
||||
select(.name == "http")] | length == 2' <<< "$merged" > /dev/null
|
||||
|
||||
fixed="$(bash "$ROOT/scripts/migrate-http-port.sh" test "$deployment" "$target")"
|
||||
jq -e --argjson port "$target" '
|
||||
[.spec.template.spec.containers[] | .ports[]? | select(.name == "http")] |
|
||||
length == 1 and .[0].containerPort == $port
|
||||
' <<< "$fixed" > /dev/null
|
||||
[[ "$(jq -Sc '.spec.template.spec.containers[0]' <<< "$fixed")" == \
|
||||
"$(jq -Sc '.spec.template.spec.containers[0]' <<< "$LIVE")" ]]
|
||||
[[ "$(jq -Sc '.spec.template.spec.containers[1] | del(.ports)' <<< "$fixed")" == \
|
||||
"$(jq -Sc '.spec.template.spec.containers[1] | del(.ports)' <<< "$LIVE")" ]]
|
||||
jq -e '.spec.template.spec.containers[1].ports |
|
||||
any(.name == "metrics" and .containerPort == 2112)' <<< "$fixed" > /dev/null
|
||||
|
||||
LIVE="$fixed"
|
||||
again="$(EXPECT_APPLY=true bash "$ROOT/scripts/migrate-http-port.sh" test "$deployment" "$target" --apply)"
|
||||
[[ "$(jq -Sc . <<< "$again")" == "$(jq -Sc . <<< "$fixed")" ]]
|
||||
LIVE="$merged"
|
||||
repaired="$(EXPECT_APPLY=true bash "$ROOT/scripts/migrate-http-port.sh" test "$deployment" "$target" --apply)"
|
||||
jq -e '[.spec.template.spec.containers[] | .ports[]? |
|
||||
select(.name == "http")] | length == 1' <<< "$repaired" > /dev/null
|
||||
|
||||
if [[ "${SERVER_VALIDATION:-false}" == true ]]; then
|
||||
if "$REAL_KUBECTL" create --dry-run=server \
|
||||
-f <(jq 'del(.metadata.resourceVersion)' <<< "$merged") > /dev/null 2>&1; then
|
||||
echo "Expected API rejection of duplicate http ports" >&2
|
||||
exit 1
|
||||
fi
|
||||
"$REAL_KUBECTL" create --dry-run=server \
|
||||
-f <(jq 'del(.metadata.resourceVersion)' <<< "$repaired") > /dev/null
|
||||
fi
|
||||
echo "OK: $deployment $old -> $target; duplicate reproduced, replacement and rollback safe"
|
||||
done
|
||||
done
|
||||
|
||||
if SIMULATE_RACE=true bash "$ROOT/scripts/migrate-http-port.sh" test hub-frontend-demo 8080 > /dev/null 2>&1; then
|
||||
echo "Expected rejection of concurrent Deployment changes" >&2
|
||||
exit 1
|
||||
fi
|
||||
LIVE="$(jq '.spec.template.spec.containers[1].ports = [{name:"http",containerPort:9000}]' <<< "$LIVE")"
|
||||
if bash "$ROOT/scripts/migrate-http-port.sh" test hub-frontend-demo 8080 > /dev/null 2>&1; then
|
||||
echo "Expected rejection of an unknown HTTP port" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "HTTP port migration checks passed."
|
||||
@@ -0,0 +1,86 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
CHART_DIR="${1:-charts/hub}"
|
||||
|
||||
resource() {
|
||||
local kind="$1" name="$2"
|
||||
awk -v kind="$kind" -v name="$name" '
|
||||
function emit() { if (matchedKind && matchedName) printf "%s", block }
|
||||
/^---$/ { emit(); block=""; matchedKind=0; matchedName=0 }
|
||||
{ block=block $0 "\n" }
|
||||
$0 == "kind: " kind { matchedKind=1 }
|
||||
$0 == " name: " name { matchedName=1 }
|
||||
END { emit() }
|
||||
'
|
||||
}
|
||||
|
||||
assert_line() {
|
||||
if ! grep -Eq -- "$2" <<< "$1"; then
|
||||
printf 'Missing expected Hub API setting: %s\n' "$2" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
fixtures=(
|
||||
--set workflowConfiguration.enabled=true
|
||||
--set mongodb.tls.enabled=true
|
||||
--set mongodb.tls.existingSecret=mongodb-ca
|
||||
--set kerberoshub.api.serverTLS.enabled=true
|
||||
--set kerberoshub.api.serverTLS.secretName=api-tls
|
||||
)
|
||||
|
||||
for mode in all ui pipeline; do
|
||||
defaults="$(helm template hub "$CHART_DIR" --set "mode=$mode" "${fixtures[@]}")"
|
||||
legacy="$(helm template hub "$CHART_DIR" --set "mode=$mode" "${fixtures[@]}" \
|
||||
--set kerberoshub.api.securityContext=null \
|
||||
--set kerberoshub.api.podSecurityContext=null)"
|
||||
configured="$(helm template hub "$CHART_DIR" --set "mode=$mode" "${fixtures[@]}" \
|
||||
--set kerberoshub.api.securityContext.runAsUser=1000 \
|
||||
--set kerberoshub.api.securityContext.runAsGroup=1000 \
|
||||
--set kerberoshub.api.podSecurityContext.fsGroup=1000)"
|
||||
helm lint "$CHART_DIR" --quiet --set "mode=$mode" "${fixtures[@]}"
|
||||
|
||||
if [[ "$mode" == pipeline ]]; then
|
||||
[[ -z "$(resource Deployment hub-api <<< "$defaults")" ]]
|
||||
[[ "$defaults" == "$legacy" && "$defaults" == "$configured" ]]
|
||||
echo "OK (pipeline): no Hub API resources or unrelated changes"
|
||||
continue
|
||||
fi
|
||||
|
||||
api="$(resource Deployment hub-api <<< "$defaults")"
|
||||
for pattern in \
|
||||
'^[ ]{6}securityContext:$' '^[ ]{8}fsGroup: 65532$' \
|
||||
'^[ ]{10}securityContext:$' '^[ ]{12}runAsUser: 65532$' \
|
||||
'^[ ]{12}runAsGroup: 65532$' '^[ ]{12}runAsNonRoot: true$' \
|
||||
'^[ ]{12}readOnlyRootFilesystem: true$' \
|
||||
'^[ ]{12}allowPrivilegeEscalation: false$' '^[ ]{12}capabilities:$' \
|
||||
'^[ ]{14}drop:$' '^[ ]{14}- ALL$' \
|
||||
'^[ ]{12}seccompProfile:$' '^[ ]{14}type: RuntimeDefault$' \
|
||||
'^[ ]+(- )?name: hub-api-tmp$' '^[ ]+(- )?emptyDir: \{\}$' \
|
||||
'^[ ]+(- )?mountPath: /tmp$' '^[ ]+secretName: mongodb-ca$' \
|
||||
'^[ ]+secretName: api-tls$' '^[ ]+mountPath: /etc/mongodb/tls$' \
|
||||
'^[ ]+mountPath: /etc/hub-api/tls$' \
|
||||
'^[ ]+mountPath: /etc/kerberos/configuration$' \
|
||||
'^[ ]+mountPath: /etc/kerberos/classifications$'; do
|
||||
assert_line "$api" "$pattern"
|
||||
done
|
||||
[[ "$(grep -c 'securityContext:' <<< "$api")" == 2 ]]
|
||||
old_api="$(resource Deployment hub-api <<< "$legacy")"
|
||||
if grep -q 'securityContext:' <<< "$old_api"; then
|
||||
echo "Legacy Hub API opt-out still renders a security context" >&2
|
||||
exit 1
|
||||
fi
|
||||
custom_api="$(resource Deployment hub-api <<< "$configured")"
|
||||
for field in runAsUser runAsGroup fsGroup; do
|
||||
assert_line "$custom_api" "^[ ]+$field: 1000$"
|
||||
done
|
||||
|
||||
# Only security settings change; preserve env, mounts, images and routing.
|
||||
for pattern in '^[ ]+image:' '^[ ]+(port|targetPort|containerPort):' \
|
||||
'^[ ]+(- )?(name|mountPath|secretName|value):'; do
|
||||
[[ "$(grep -E "$pattern" <<< "$defaults")" == "$(grep -E "$pattern" <<< "$legacy")" ]]
|
||||
[[ "$(grep -E "$pattern" <<< "$defaults")" == "$(grep -E "$pattern" <<< "$configured")" ]]
|
||||
done
|
||||
echo "OK ($mode): API security defaults, overrides, opt-out, scratch and TLS/config mounts"
|
||||
done
|
||||
@@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if [[ $# -lt 3 || $# -gt 4 ]]; then
|
||||
echo "Usage: bash scripts/migrate-http-port.sh NAMESPACE DEPLOYMENT PORT [--apply]" >&2
|
||||
exit 1
|
||||
fi
|
||||
namespace="$1" deployment="$2" port="$3"
|
||||
case "$deployment" in
|
||||
admin|hub-frontend|hub-frontend-demo) ;;
|
||||
*) echo "Unsupported Deployment: $deployment" >&2; exit 1 ;;
|
||||
esac
|
||||
case "$port" in
|
||||
80|8080) ;;
|
||||
*) echo "Expected HTTP port 80 or 8080, got: $port" >&2; exit 1 ;;
|
||||
esac
|
||||
dry_run=(--dry-run=server)
|
||||
if [[ $# -eq 4 ]]; then
|
||||
if [[ "$4" != --apply ]]; then
|
||||
echo "Unknown option: $4" >&2
|
||||
exit 1
|
||||
fi
|
||||
dry_run=()
|
||||
fi
|
||||
|
||||
live="$(kubectl -n "$namespace" get deployment "$deployment" -o json)"
|
||||
patch="$(jq -ce --arg container "$deployment" --argjson port "$port" '
|
||||
. as $deployment |
|
||||
[.spec.template.spec.containers | to_entries[] |
|
||||
select(.value.name == $container)] |
|
||||
if length != 1 then error("Expected exactly one matching container") else .[0] end |
|
||||
.key as $index | .value.ports as $ports |
|
||||
if ($ports | type) != "array" then error("Container ports must be an array") else . end |
|
||||
[$ports[] | select(.name == "http")] as $http |
|
||||
if ($http | length) == 0 then error("No named http port to migrate") else . end |
|
||||
if any($http[]; (.containerPort != 80 and .containerPort != 8080) or
|
||||
((.protocol // "TCP") != "TCP") or has("hostPort") or has("hostIP"))
|
||||
then error("Unsupported http port configuration; migrate manually") else . end |
|
||||
if any($ports[]; .name != "http" and .containerPort == $port and
|
||||
((.protocol // "TCP") == "TCP"))
|
||||
then error("Target port is already used by another named port") else . end |
|
||||
([$ports[] | select(.name != "http")] +
|
||||
[($http[0] + {containerPort: $port})]) as $replacement |
|
||||
[
|
||||
{op: "test", path: "/metadata/resourceVersion", value: $deployment.metadata.resourceVersion},
|
||||
{op: "test", path: ("/spec/template/spec/containers/" + ($index | tostring) + "/name"),
|
||||
value: $container},
|
||||
{op: "replace", path: ("/spec/template/spec/containers/" + ($index | tostring) + "/ports"),
|
||||
value: $replacement}
|
||||
]
|
||||
' <<< "$live")"
|
||||
|
||||
kubectl -n "$namespace" patch deployment "$deployment" --type=json \
|
||||
-p "$patch" "${dry_run[@]}" -o yaml
|
||||
Reference in New Issue
Block a user