mirror of
https://github.com/kerberos-io/helm-charts.git
synced 2026-11-05 20:20:24 +00:00
Compare commits
38
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
56ea65c6e7 | ||
|
|
af4e1c36a9 | ||
|
|
166b4a31e6 | ||
|
|
e938cb01b5 | ||
|
|
bd4e118a7a | ||
|
|
fe658cce05 | ||
|
|
1288266666 | ||
|
|
9c1e37aba4 | ||
|
|
b186ce94ba | ||
|
|
584ea33ce6 | ||
|
|
4442c9c201 | ||
|
|
c1e15368c7 | ||
|
|
8d86eefba5 | ||
|
|
78d83e368f | ||
|
|
e899f070f8 | ||
|
|
4a643aa5c2 | ||
|
|
fdca7b1196 | ||
|
|
5a130b7301 | ||
|
|
6f1dcf8f00 | ||
|
|
371408e5eb | ||
|
|
0eff4bf2ee | ||
|
|
18e456d490 | ||
|
|
374d40aa41 | ||
|
|
073c12f8eb | ||
|
|
001c835068 | ||
|
|
b520e2ecc1 | ||
|
|
c42388b96f | ||
|
|
04d6990460 | ||
|
|
e228fd36fb | ||
|
|
5af6472826 | ||
|
|
e0a273422e | ||
|
|
71e2db095e | ||
|
|
356f510ba9 | ||
|
|
95ffe47edb | ||
|
|
abb22276ca | ||
|
|
331df9d4af | ||
|
|
17dd600872 | ||
|
|
4880dc1197 |
@@ -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.161.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
|
||||
|
||||
+559
-24
@@ -31,6 +31,388 @@ 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
|
||||
|
||||
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.
|
||||
|
||||
```yaml
|
||||
kerberoshub:
|
||||
frontend:
|
||||
containerPort: 8080
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 65532
|
||||
runAsGroup: 65532
|
||||
readOnlyRootFilesystem: true
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop: ["ALL"]
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
volumes:
|
||||
- name: frontend-tmp
|
||||
emptyDir:
|
||||
medium: Memory
|
||||
sizeLimit: 16Mi
|
||||
volumeMounts:
|
||||
- name: frontend-tmp
|
||||
mountPath: /tmp
|
||||
```
|
||||
|
||||
Merge the temporary volume into any existing branding/translation mounts rather
|
||||
than replacing them. Custom files must be readable by UID `65532`; mount them
|
||||
read-only. Only `/tmp` needs writes for generated public runtime configuration
|
||||
and nginx's PID/temp files. The web root stays read-only. Health checks can use
|
||||
`GET /healthz` on the named `http` container port. These settings apply to both
|
||||
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`
|
||||
runs as `65532:65532` and listens on `8080`, unlike the legacy image on `80`.
|
||||
Set these values together; the chart does not upgrade the image automatically:
|
||||
|
||||
```yaml
|
||||
admin:
|
||||
repository: ghcr.io/uug-ai/admin
|
||||
tag: v1.4.3
|
||||
containerPort: 8080
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 65532
|
||||
runAsGroup: 65532
|
||||
readOnlyRootFilesystem: true
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop: ["ALL"]
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
volumes:
|
||||
- name: admin-tmp
|
||||
emptyDir:
|
||||
medium: Memory
|
||||
sizeLimit: 16Mi # Example; size for your runtime configuration.
|
||||
volumeMounts:
|
||||
- name: admin-tmp
|
||||
mountPath: /tmp
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: http
|
||||
periodSeconds: 10
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: http
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
```
|
||||
|
||||
Merge the scratch volume with any existing mounts. Only `/tmp` is writable;
|
||||
application files remain read-only. Memory-backed scratch counts against the
|
||||
pod memory limit. The `admin-svc` Service and Admin ingress still use port `80`;
|
||||
the Service targets the named `http` container port. OAuth2-proxy is unchanged.
|
||||
Defaults remain port `80` with no security context, added volumes, or probes. Coordinate
|
||||
the image, values and a chart revision containing these settings: published
|
||||
`0.167.2` does not contain Admin runtime support.
|
||||
|
||||
Rollbacks to legacy images must restore port `80`, remove the hardened
|
||||
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
|
||||
`podSecurityContext` (pod) maps. Both default to `{}` and are omitted from the
|
||||
rendered Deployment. Image tags, ports, existing mounts and legacy permissions
|
||||
are unchanged. Support covers:
|
||||
|
||||
| Values path | Deployment |
|
||||
| --- | --- |
|
||||
| `kerberospipeline.counting` | `pipe-counting` |
|
||||
| `kerberospipeline.throttler` | `pipe-throttler` |
|
||||
| `kerberospipeline.notify` | `pipe-notify` |
|
||||
| `kerberospipeline.thumbnail` | `pipe-thumbnail` |
|
||||
| `kerberospipeline.dominantColor` | `pipe-dominantcolor` |
|
||||
| `kerberoshub.cleanup` | `hub-cleanup` |
|
||||
| `kerberoshub.monitordevice` | `hub-monitor-device` |
|
||||
| `kerberoshub.services.workflows` | `hub-workflows` |
|
||||
| `kerberoshub.services.<stage>` | Enabled generic workflow workers, e.g. loitering, ANPR or forwarder |
|
||||
|
||||
These application hardening changes are proposed in draft PRs, not release
|
||||
clearance. First select and validate a compatible hardened image for each
|
||||
component, then deploy a chart revision containing this support and its matching
|
||||
values together. Published chart `0.167.2` does not contain these worker settings.
|
||||
Do not enable these settings for an unverified legacy, Python/GPU or custom image.
|
||||
There is no global switch that hardens every worker.
|
||||
|
||||
For a verified `65532:65532` Go worker, explicitly configure its own values path:
|
||||
|
||||
```yaml
|
||||
kerberospipeline:
|
||||
counting:
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 65532
|
||||
runAsGroup: 65532
|
||||
readOnlyRootFilesystem: true
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop: ["ALL"]
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
```
|
||||
|
||||
Apply that same container map independently to other verified workers; it is not
|
||||
inherited by them. The confirmed loitering image processes trajectories entirely
|
||||
in memory and needs no writable mount. Worker metrics Services remain on `8080`;
|
||||
`GET /metrics` is process liveness, not RabbitMQ/dependency readiness.
|
||||
|
||||
The notification image proposed by `uug-ai/hub-pipeline-notification#149` retains
|
||||
its existing worker/provider/FFmpeg behavior. Configure its own
|
||||
`kerberospipeline.notify` security maps only after selecting that compatible
|
||||
image; retain provider settings and merge existing mounts/resources. Mount
|
||||
workload-sized writable scratch at `/tmp` (the default temporary directory);
|
||||
the worker creates `/tmp/hub-pipeline-thumbnail` for generated media. Keep
|
||||
`/mail/templates` override mounts read-only and custom CA mounts readable by UID
|
||||
`65532`. The application working directory need not be writable. MongoDB TLS
|
||||
secret mounts remain read-only. Final native image proof is still required.
|
||||
|
||||
Thumbnail and dominant-colour workers need writable **`/tmp`**, including legacy
|
||||
debug image output. Merge a disk-backed `emptyDir` and its mount into each
|
||||
component's existing `volumes`/`volumeMounts`. For example, after sizing for the
|
||||
workload:
|
||||
|
||||
```yaml
|
||||
kerberospipeline:
|
||||
thumbnail:
|
||||
# Also configure the container securityContext above for this worker.
|
||||
podSecurityContext:
|
||||
fsGroup: 65532
|
||||
volumes:
|
||||
- name: media-scratch
|
||||
emptyDir:
|
||||
sizeLimit: 4Gi # Illustrative only, NOT a safe universal limit.
|
||||
volumeMounts:
|
||||
- name: media-scratch
|
||||
mountPath: /tmp
|
||||
resources:
|
||||
requests:
|
||||
ephemeral-storage: 4Gi
|
||||
limits:
|
||||
ephemeral-storage: 6Gi
|
||||
```
|
||||
|
||||
Size scratch for the sum of concurrently downloaded full recordings, extracted
|
||||
JPEG/PNG frames, debug files and headroom. Align ephemeral-storage requests/limits
|
||||
with scratch and other pod usage; retain suitable CPU/memory resources.
|
||||
Memory-backed scratch counts against the pod memory limit and can double-count
|
||||
media already buffered in Go. Small CI fixture volumes are not production sizing.
|
||||
Only opt in to `fsGroup` after reviewing **all** mounts on that pod: persistent
|
||||
volumes can be affected by Kubernetes/CSI ownership handling. Do not add a
|
||||
recursive chmod/chown init container or mount over the root-owned application.
|
||||
|
||||
The proposed ANPR image uses a workload-sized writable directory such as `/work`
|
||||
with `kerberoshub.services.anpr.env.TMPDIR: /work`; configure matching
|
||||
`volumes`/`volumeMounts` and container security settings on that stage only.
|
||||
Optional dataset mode also requires its own writable `ANPR_DATASET_DIR` mount.
|
||||
Models and application directories stay read-only.
|
||||
|
||||
This repository has **no Vault server or design-system chart**. The files named
|
||||
`vault-proxy` and `vault-forwarder` deploy different legacy applications; their
|
||||
ports are not the Vault server's port. Do not change them for `uug-ai/vault#119`.
|
||||
That proposed Vault image requires port `8080` (legacy `80`), UID/GID `65532`,
|
||||
read-only `/home/vault`, workload-sized writable `/run/vault`, and compatible
|
||||
ownership on existing optional `LOCAL_CACHE_DIRECTORY` mounts. Its liveness
|
||||
endpoint is `GET /api/health`. Wire those settings in the actual Vault deployment
|
||||
only when a compatible image release is selected; no migration is required.
|
||||
|
||||
### Passkeys
|
||||
|
||||
Users and sub-users can create and manage their own passkeys in **Profile >
|
||||
Passkeys**, then use **Sign in with a passkey** for passwordless authentication
|
||||
with device PIN/biometric verification. Existing password/MFA sign-in remains
|
||||
available. Enrollment and removal require the current password and an
|
||||
authenticator-app code when MFA is enabled.
|
||||
|
||||
Deploy Hub API and frontend images that support passkeys before enabling this
|
||||
configuration; the chart version alone does not update the service image tags.
|
||||
Passkeys are disabled by default. Configure their trust explicitly:
|
||||
|
||||
```yaml
|
||||
kerberoshub:
|
||||
api:
|
||||
passkeys:
|
||||
enabled: true
|
||||
rpId: "hub.example.com"
|
||||
origins:
|
||||
- "https://hub.example.com"
|
||||
rpName: "Kerberos Hub"
|
||||
```
|
||||
|
||||
| Value | Default | Purpose |
|
||||
| --- | --- | --- |
|
||||
| `kerberoshub.api.passkeys.enabled` | `false` | Turns passkeys on: sets `FEATURE_PASSKEYS_ENABLED` on Hub API and the frontend, and injects the passkey configuration into Hub API pods |
|
||||
| `kerberoshub.api.passkeys.rpId` | `""` | Required when enabled; sets `PASSKEY_RP_ID` |
|
||||
| `kerberoshub.api.passkeys.origins` | `[]` | Required when enabled; exact trusted frontend origins, joined with commas into `PASSKEY_ORIGINS` |
|
||||
| `kerberoshub.api.passkeys.rpName` | `"Kerberos Hub"` | Display name; sets `PASSKEY_RP_NAME` |
|
||||
|
||||
Use the **frontend's public hostname and origins**, not the API URL. Origins
|
||||
must have no path or trailing slash. Each origin hostname must equal the RP ID
|
||||
or be its subdomain. Include only trusted origins, especially when sharing an
|
||||
RP ID across tenant subdomains. Keep the RP ID stable: changing it makes
|
||||
existing passkeys unusable. The API validates the RP/origin relationship at
|
||||
runtime; missing/invalid configuration makes passkeys unavailable without
|
||||
affecting password/MFA sign-in.
|
||||
|
||||
HTTPS and a compatible browser are required. Local development may use
|
||||
`rpId: "localhost"` with `origins: ["http://localhost:4200"]`.
|
||||
The frontend displays translated cancellation, verification and unavailable
|
||||
messages when a ceremony cannot complete.
|
||||
|
||||
Since chart `0.168.1`, Hub API and frontend images that include the passkey
|
||||
feature flag also require `FEATURE_PASSKEYS_ENABLED=true`. `enabled` sets it on
|
||||
both, so the API answers `501 FEATURE_NOT_SUPPORTED` and the frontend hides
|
||||
passkeys when it is `false`. If you configure passkeys through
|
||||
`kerberoshub.extraEnv` instead, add `FEATURE_PASSKEYS_ENABLED=true` there and in
|
||||
the frontend environment too.
|
||||
|
||||
Apply values with a Helm upgrade or GitOps sync; changes trigger an API and frontend rollout.
|
||||
If these variables were previously configured through `kerberoshub.extraEnv`,
|
||||
remove those entries when enabling the dedicated settings to avoid duplicate
|
||||
environment variables. Setting `enabled: false` omits the chart-managed
|
||||
variables; any passkey variables supplied through `extraEnv` still take effect.
|
||||
|
||||
The API's MongoDB identity needs reads on `users`, CRUD on `passkeys`, and
|
||||
insert/find-and-delete on `passkey_challenges`. It attempts to create the
|
||||
`passkey_challenge_expiry_ttl` index on `expiresAt` with
|
||||
`expireAfterSeconds: 0`; expiration and single-use enforcement work without the
|
||||
index, but the index is needed for automatic expired-challenge cleanup.
|
||||
|
||||
### Sequence logging
|
||||
|
||||
Set `kerberospipeline.sequence.logLevel` to control the Sequence service's
|
||||
`LOG_LEVEL` environment variable. The default is `info`; supported values are
|
||||
`debug`, `info`, `warn`, and `error` (lowercase).
|
||||
|
||||
```yaml
|
||||
kerberospipeline:
|
||||
sequence:
|
||||
logLevel: "debug"
|
||||
```
|
||||
|
||||
Apply the values with a Helm upgrade or GitOps sync. Changing the value updates
|
||||
the pod template and triggers a rollout. Debug logging includes Sequence
|
||||
processing checkpoints when supported by the deployed service image.
|
||||
|
||||
### Sprite Resolution
|
||||
|
||||
`kerberospipeline.sprite.width` and `height` set the pixel dimensions of each
|
||||
@@ -268,8 +650,8 @@ kerberospipeline:
|
||||
| `opentelemetry.enabled` | Enable or disable OpenTelemetry instrumentation. | `false` |
|
||||
| `opentelemetry.routingEnabled` | Enable or disable OpenTelemetry routing/export behavior. | `false` |
|
||||
| `opentelemetry.collector.endpoint` | OpenTelemetry collector endpoint used for trace export. | `"http://otel-collector:4317"` |
|
||||
| `opentelemetry.sampler.type` | Trace sampler, exported as `OTEL_TRACES_SAMPLER` (e.g. `parentbased_traceidratio`). Empty keeps the SDK default (sample everything). | `""` |
|
||||
| `opentelemetry.sampler.arg` | Sampler argument, exported as `OTEL_TRACES_SAMPLER_ARG` (e.g. `"0.1"` for ~10% of traces). | `""` |
|
||||
| `opentelemetry.apiSamplingRatios.operator` | hub-api: share of user and API-client requests traced (0–1), rendered as `TRACE_SAMPLER_RATIO_OPERATOR` (hub-api on trace v1.2.6+). With no ratio set, hub-api records every trace. hub-workflows, the `pipe-*` services and the workflow stages always follow the trace they continue (`parentbased_always_off`); uploads are sampled at the Vault. | `""` |
|
||||
| `opentelemetry.apiSamplingRatios.other` | hub-api: share of everything else traced (device heartbeats, agent uploads and live segments, background work), rendered as `TRACE_SAMPLER_RATIO_OTHER`; `0` turns it off. | `""` |
|
||||
| `openai.enabled` | Enable or disable OpenAI-backed semantic features. | `false` |
|
||||
| `openai.apikey` | OpenAI API key used when OpenAI integration is enabled. | `"xxx"` |
|
||||
| `kerberosvault.uri` | The default Kerberos Vault uri (you can add multiple within the app), by default `'https://api.storage.yourdomain.com'` | `"https://api.vault.yourdomain.com"` |
|
||||
@@ -332,6 +714,7 @@ kerberospipeline:
|
||||
| `kerberoshub.api.mfaIssuer` | When enabling the MFA access, this is the name that will be shown in the MFA app. | `"yourdomain.com"` |
|
||||
| `kerberoshub.api.apiKey` | API key for `kerberoshub.api`. | `"a-random-admin-api-key"` |
|
||||
| `kerberoshub.api.defaultTaskRetentionDays` | Default retention (in days) applied to tasks without an explicit `retention_days`. New tasks are stamped with this value. Set to `"0"` or a negative value to keep tasks indefinitely. Must match `kerberoshub.cleanup.defaultTaskRetentionDays`. | `"0"` |
|
||||
| `kerberoshub.api.auditExportMaxEventsPerFile` | Most audit events one export file holds (`AUDIT_EXPORT_MAX_EVENTS_PER_FILE`). It does not refuse larger exports: the Audit page downloads them as several files of at most this size. An integer from 1 to 1000000; empty uses the API default of 100000. | `""` |
|
||||
| `kerberoshub.api.tls` | Bring your own TLS certificates for Kerberos Hub API ingress. | `<list>` |
|
||||
| `kerberoshub.api.tls.secretName` | Kubernetes Secret name used by `kerberoshub.api.tls`. | `""` |
|
||||
| `kerberoshub.api.language` | The language of Kerberos Hub API responses, error messages will be communicated in the specified language. | `"english"` |
|
||||
@@ -441,6 +824,8 @@ kerberospipeline:
|
||||
| `kerberoshub.frontend.features.devices.analyticsEnabled` | Enable or disable the Analytics tab on device detail pages. | `"true"` |
|
||||
| `kerberoshub.frontend.features.devices.developerToolsEnabled` | Enable or disable owner-only live Agent logs and terminal access on device detail pages. | `"true"` |
|
||||
| `kerberoshub.frontend.features.dashboard.sitesFullscreenEnabled` | Enable or disable the fullscreen Sites map on the Dashboard. | `"true"` |
|
||||
| `kerberoshub.frontend.features.media.forward.enabled` | Enable or disable the bulk Forward media action in the Recordings Actions menu. | `"true"` |
|
||||
| `kerberoshub.frontend.features.media.detail.analysis.enabled` | Enable or disable the Analysis block (dominant colors, thumbnail, sprite, motion) on the media detail page. | `"true"` |
|
||||
| `kerberoshub.frontend.features.media.filter.date.enabled` | Enable or disable `kerberoshub.frontend.features.media.filter.date`. | `"true"` |
|
||||
| `kerberoshub.frontend.features.media.filter.sites.enabled` | Enable or disable `kerberoshub.frontend.features.media.filter.sites`. | `"true"` |
|
||||
| `kerberoshub.frontend.features.media.filter.groups.enabled` | Enable or disable `kerberoshub.frontend.features.media.filter.groups`. | `"true"` |
|
||||
@@ -511,12 +896,16 @@ kerberospipeline:
|
||||
| `kerberoshub.cleanup.resources.requests.cpu` | CPU request for `kerberoshub.cleanup`. | `"10m"` |
|
||||
| `kerberoshub.audit.repository` | Hub audit service container image repository. | `"ghcr.io/uug-ai/hub-audit"` |
|
||||
| `kerberoshub.audit.pullPolicy` | Hub audit service image pull policy. | `"IfNotPresent"` |
|
||||
| `kerberoshub.audit.tag` | Hub audit service image tag. | `"v1.0.1"` |
|
||||
| `kerberoshub.audit.tag` | Hub audit service image tag. S3 destinations need v1.0.9 or later, and `s3.allowUnlockedBucket` needs v1.0.10. | `"v1.0.10"` |
|
||||
| `kerberoshub.audit.replicas` | Number of audit service replicas. Per-destination MongoDB leases prevent concurrent delivery. | `1` |
|
||||
| `kerberoshub.audit.dispatchInterval` | How often the mounted destination configuration is reloaded and eligible destinations are polled. | `"5s"` |
|
||||
| `kerberoshub.audit.leaseDuration` | Per-destination lease duration. It must exceed every destination timeout by at least 30 seconds. | `"2m"` |
|
||||
| `kerberoshub.audit.terminationGracePeriodSeconds` | Pod termination grace period. Keep this longer than `leaseDuration` so an in-flight cycle can finish. | `150` |
|
||||
| `kerberoshub.audit.destinations` | Webhook destinations. Each entry supports `id`, `enabled`, `url`, delivery limits, filters, public `headers`, `bearerTokenSecret`, and arbitrary `secretHeaders`. Destination IDs retain independent checkpoints. | `[]` |
|
||||
| `kerberoshub.audit.destinations` | Audit destinations. Every entry supports `id`, `type` (`webhook` by default, or `s3`), `enabled`, delivery limits and filters. Webhooks add `url`, public `headers`, `bearerTokenSecret` and arbitrary `secretHeaders`. S3 destinations add `s3.bucket`, `s3.region`, optional `s3.prefix`, `s3.endpoint` and `s3.forcePathStyle` for S3-compatible stores, and an optional `credentialsSecret` (`name`, `accessKeyIdKey`, `secretAccessKeyKey`); without it the pod's AWS identity is used. The bucket must have Object Lock with a `COMPLIANCE` default retention unless `s3.allowUnlockedBucket` is `true` (stores without Object Lock such as Garage; the archive is then deletable). Every field, the delivery semantics and per-provider S3 settings are documented at https://learn.uug.ai/docs/hub/audit-events/#export-audit-events. Destination IDs retain independent checkpoints. | `[]` |
|
||||
| `kerberoshub.audit.serviceAccount.create` | Create a ServiceAccount used only by hub-audit, for example for S3 access through IRSA. | `false` |
|
||||
| `kerberoshub.audit.serviceAccount.name` | Name of the hub-audit ServiceAccount; with `create: false`, an existing ServiceAccount to use. Defaults to `hub-audit` when created; when empty and not created, `kerberoshub.serviceAccount` applies. | `""` |
|
||||
| `kerberoshub.audit.serviceAccount.annotations` | Annotations for the hub-audit ServiceAccount, such as `eks.amazonaws.com/role-arn`. | `{}` |
|
||||
| `kerberoshub.audit.serviceAccount.labels` | Labels for the hub-audit ServiceAccount. | `{}` |
|
||||
| `kerberoshub.audit.serviceMonitor.enabled` | Create a Prometheus `ServiceMonitor` for audit service metrics. | `true` |
|
||||
| `kerberoshub.audit.serviceMonitor.interval` | Audit service metrics scrape interval. | `"15s"` |
|
||||
| `kerberoshub.audit.volumes` | Additional pod volumes, for example a private webhook CA bundle. | `[]` |
|
||||
@@ -527,6 +916,7 @@ kerberospipeline:
|
||||
| `kerberoshub.monitordevice.tag` | The Docker image tag/version. | `"v1.4.0"` |
|
||||
| `kerberoshub.monitordevice.replicas` | Number of replicas for `kerberoshub.monitordevice`. | `1` |
|
||||
| `kerberoshub.monitordevice.logLevel` | Log verbosity level for `kerberoshub.monitordevice`. | `"info"` |
|
||||
| `kerberoshub.monitordevice.scanIntervalSeconds` | Seconds between device status scans. Sets how quickly a camera going offline or online is detected. For accounts with "Group notifications" on, `hub-monitor-device` v1.4.10 and later then hold a change 20 seconds (`DEVICE_GROUPING_HOLD_SECONDS`) to collect other cameras that change status at the same time; older releases hold it one scan. | `60` |
|
||||
| `kerberoshub.monitordevice.resources.requests.memory` | Memory request for `kerberoshub.monitordevice`. | `"10Mi"` |
|
||||
| `kerberoshub.monitordevice.resources.requests.cpu` | CPU request for `kerberoshub.monitordevice`. | `"10m"` |
|
||||
| `kerberoshub.reactivate.repository` | Container image repository for `kerberoshub.reactivate`. | `"uugai/hub-reactivatesubscriptions"` |
|
||||
@@ -592,6 +982,7 @@ kerberospipeline:
|
||||
| `kerberospipeline.analysis.tag` | The Docker image tag/version. | `"v1.7.8"` |
|
||||
| `kerberospipeline.analysis.replicas` | Number of replicas for `kerberospipeline.analysis`. | `1` |
|
||||
| `kerberospipeline.analysis.logLevel` | Log verbosity level for `kerberospipeline.analysis`. | `"info"` |
|
||||
| `kerberospipeline.analysis.thumbnailRegeneration.enabled` | Regenerate thumbnails from the first frame of a classified moving object (`THUMBNAIL_REGENERATION_ENABLED`). Adds one thumbnail job per classified recording. Requires an analysis image that reads the flag. | `false` |
|
||||
| `kerberospipeline.analysis.dbCache.enabled` | Cache hot-path MongoDB lookups (site vault per device) in memory. Site changes apply after at most one TTL. | `false` |
|
||||
| `kerberospipeline.analysis.dbCache.ttl` | Lifetime of a cached lookup, as a Go duration (e.g. `"30s"`, `"5m"`). | `"5m"` |
|
||||
| `kerberospipeline.analysis.resources.requests.memory` | Memory request for `kerberospipeline.analysis`. | `"10Mi"` |
|
||||
@@ -673,6 +1064,8 @@ kerberospipeline:
|
||||
| `email.templates.device` | The template which is send when a camera goes online or offline, by default `'device'`. | `"device"` |
|
||||
| `email.templates.alertTitle` | Email subject title for the `alert` template. | `"[Alert] Kerberos Hub detected something an event"` |
|
||||
| `email.templates.deviceTitle` | Email subject title for the `device` template. | `"[Device] A Kerberos Agent's status has been changed"` |
|
||||
| `email.templates.deviceGroup` | The template sent instead of `device` when several cameras of an account change status in the same scan and the account groups device notifications ("Group notifications"). SMTP only; see *Grouped camera status* below. | `"device_group"` |
|
||||
| `email.templates.deviceGroupTitle` | Email subject title for the `device_group` template. | `"[Device] Several cameras changed status"` |
|
||||
| `email.mailgun.apiKey` | The Mailgun API key linked to your Mailgun domain. | `""` |
|
||||
| `imagePullSecrets.name` | Docker registry secret name, which is also granted with the license. This allows you to download the Docker images. | `""` |
|
||||
| `kerberoshub.forwarder.pullPolicy` | The Docker registry pull policy. | `""` |
|
||||
@@ -681,6 +1074,136 @@ kerberospipeline:
|
||||
| `kerberoshub.frontend.ssoDomain` | The domain that's being used to activate SSO from the login page. | `""` |
|
||||
| `kerberoshub.frontend.stripe.apikey` | If using the public version, `stripe` can be used for automated billing and subscriptions. | `""` |
|
||||
|
||||
### Email templates and variables
|
||||
|
||||
Every email Hub sends is a template: an HTML file, plus a plain-text `.txt` version, containing `{{variable}}` placeholders that are filled in just before sending. This section lists which value each placeholder gets.
|
||||
|
||||
#### Which template is used
|
||||
|
||||
- `email.templates.<name>` sets the template name for each email (for example `email.templates.detection: "detection"`).
|
||||
- With `email.provider: smtp`, Hub reads `/mail/templates/<name>.html` and `/mail/templates/<name>.txt` from the container. If the files are not there, it uses its built-in template with that name. To use your own, mount a volume at `/mail` (see the commented `custom-email-templates` volume in `values.yaml`). The [`custom-layout/templates`](custom-layout/templates) folder holds a complete set to start from.
|
||||
- With `email.provider: mailgun`, `<name>` is a template stored in your Mailgun account. Mailgun receives `user`, `text`, `link`, `numberOfMedia`, `dataUsage` and the email-specific values below as template variables. The computed values (dates, camera, sites, groups, thumbnail) are only filled in with `smtp`.
|
||||
- Alert emails (`detection`) are only sent when `email.provider` is `smtp`.
|
||||
- A placeholder that has no value for that email stays in the email as literal text, so only use the placeholders listed for it. The bundled templates contain `{{tab1_href}}`, `{{tab1_title}}`, `{{tab2_href}}` and `{{tab2_title}}`; these are not filled in.
|
||||
|
||||
#### Links
|
||||
|
||||
- The **base URL** is `kerberoshub.frontend.schema` + `://` + `kerberoshub.frontend.url` (the `PUBLIC_URL` environment variable), for example `https://app.example.com`.
|
||||
- A **tenant URL** is the base URL with the first part of the host replaced by the recipient account's domain. With base URL `https://app.example.com` and account domain `acme`, the tenant URL is `https://acme.example.com`. Accounts without a domain use the base URL. Only the last two parts of the host are kept, so a base URL on `app.example.co.uk` becomes `acme.co.uk`.
|
||||
|
||||
#### Alerts: `detection`
|
||||
|
||||
Sent by the notification pipeline (`kerberospipeline.notify`) when a custom alert or the generic detection alert fires. The subject is `email.templates.alertTitle`; for a custom alert it is prefixed with the alert's name in brackets, for example `[Front door] Hub detected an event.`
|
||||
|
||||
| Placeholder | Value |
|
||||
|---|---|
|
||||
| `{{user}}` | Name of the account that receives the alert: first and last name, or the username when both are empty. This is the account the camera belongs to, not the user who created the alert. |
|
||||
| `{{title}}` | The email subject. |
|
||||
| `{{text}}` | The alert message: what was detected, on which camera and when. |
|
||||
| `{{link}}` | **Custom alerts**: a signed link to the recording file. It stops working when the storage link expires. **Generic detection alert**: the recording's page in Hub, `<tenant URL>/media/<id>`. |
|
||||
| `{{medialink}}` | `https://app.kerberos.io/media/<id>`. Always this host; it does not follow `kerberoshub.frontend.url`. |
|
||||
| `{{thumbnail}}` | An `<img>` of the recording: embedded in the email for custom alerts, a signed thumbnail URL for the generic detection alert. |
|
||||
| `{{date}}`, `{{time}}`, `{{datetime}}` | Start of the recording, in the account's timezone. |
|
||||
| `{{eventdate}}`, `{{eventtime}}`, `{{eventdatetime}}` | When the alert was sent, in the account's timezone. |
|
||||
| `{{timezone}}` | The account's timezone. |
|
||||
| `{{devicename}}`, `{{deviceid}}` | Camera name and camera key. |
|
||||
| `{{sites}}`, `{{groups}}` | Comma-separated names of the sites and groups the camera belongs to. |
|
||||
| `{{classifications}}` | Comma-separated objects detected in the recording. |
|
||||
|
||||
#### Camera status: `device`
|
||||
|
||||
Sent by `hub-monitor-device` when a camera goes online, offline or idle. The subject is `email.templates.deviceTitle`.
|
||||
|
||||
| Placeholder | Value |
|
||||
|---|---|
|
||||
| `{{user}}` | Username of the account the camera belongs to. |
|
||||
| `{{title}}` | The email subject. |
|
||||
| `{{text}}` | The change, worded from the previous status (see below). |
|
||||
| `{{status}}` | `active` (online), `inactive` (offline) or `idle` (online, camera stream not connected). |
|
||||
| `{{devicename}}`, `{{deviceid}}` | Camera name and camera key. |
|
||||
| `{{eventdate}}`, `{{eventtime}}`, `{{eventdatetime}}` | When the status changed, in the server's timezone (normally UTC), not the account's. |
|
||||
| `{{link}}` | Not set; replaced with an empty value. |
|
||||
|
||||
`{{text}}` per change (webhooks and IFTTT get the same text):
|
||||
|
||||
| Change | `{{text}}` |
|
||||
|---|---|
|
||||
| Goes offline | "`<camera>` is offline, it hasn't responded for 5 minutes." |
|
||||
| Loses its camera stream while online | "`<camera>` is online, but the camera stream is not connected." |
|
||||
| Comes back from offline without its camera stream | "`<camera>` is back online, but the camera stream is not connected." |
|
||||
| Gets its camera stream back | "`<camera>`'s camera stream is connected again." |
|
||||
| Comes back from offline | "`<camera>` is back online." |
|
||||
|
||||
|
||||
#### Grouped camera status: `device_group`
|
||||
|
||||
Sent by `hub-monitor-device` instead of `device` when an account has **Group notifications** switched on (Alerts → Devices, stored as `notificationSettings.devices.groupNotifications`) and **several** of its cameras change status at the same time. Changes are detected on every scan (`kerberoshub.monitordevice.scanIntervalSeconds`, default 60 seconds); a change is then held 20 seconds and the account checked again, so that cameras of the same outage arrive in one email. The hold is the monitor's `DEVICE_GROUPING_HOLD_SECONDS` (default `20`; `0`, or at least the scan interval, holds until the next scan, which is also what releases before v1.4.10 do). When only one camera changed, the usual `device` email is sent. The subject is `email.templates.deviceGroupTitle`; the template name is `email.templates.deviceGroup` (default `device_group`, so custom files are `device_group.html` and `device_group.txt`).
|
||||
|
||||
| Placeholder | Value |
|
||||
|---|---|
|
||||
| `{{user}}` | Name of the account the cameras belong to: first and last name, or the username when both are empty. |
|
||||
| `{{title}}` | The email subject. |
|
||||
| `{{text}}` | One-line summary, for example "6 cameras changed status: 4 offline, 1 lost their camera stream, 1 back online." The other changes read "back online without camera stream" and "camera stream reconnected". |
|
||||
| `{{count}}` | Number of cameras in the email. |
|
||||
| `{{status}}` | `active`, `inactive` or `idle` when all cameras have the same new status, otherwise `mixed`. |
|
||||
| `{{sections}}` | The cameras as HTML, one section per status and site, group or "Other cameras" (see below). |
|
||||
| `{{sectionsText}}` | The same sections as plain text, for `device_group.txt`. |
|
||||
| `{{link}}` | The cameras page, `<tenant URL>/devices`, built from `kerberoshub.frontend.schema` and `kerberoshub.frontend.url` (see *Links* above). |
|
||||
| `{{eventdate}}`, `{{eventtime}}`, `{{eventdatetime}}` | When the email was sent, in the server's timezone (normally UTC). |
|
||||
|
||||
**Sections.** There is one section per change and per site, group or "Other cameras". Sections are ordered by change (offline, camera stream lost, back online without camera stream, camera stream back, back online), then sites, groups and "Other cameras". Each camera is listed once: under the site or group that holds the most cameras of the email with the same change (a site wins a tie), or under "Other cameras" when it is in no site or group. A section title reads `Site Gate · Offline · all 3 cameras`, `Group Perimeter · Back online without camera stream · 2 cameras` or `Other cameras · Camera stream back · 1 camera`; the change labels are `Offline`, `Camera stream lost`, `Back online without camera stream`, `Camera stream back` and `Back online`, and "all" is added when a section of two or more cameras holds every camera of the site or group. Each camera line has its name and a detail: `no response for 12 min`, `agent online, camera stream not connected`, `agent back online, camera stream not connected`, `camera stream connected` or `online`. A camera that changes again during the hold keeps the status from before its first change: one that went offline and came back reads `back online after a brief interruption`, the other reversals `offline again after briefly coming back, no response for 4 min` and `camera stream lost again after briefly reconnecting`.
|
||||
|
||||
`{{sections}}` is generated HTML with inline styles (email clients ignore stylesheets): per section a `<p>` with the title, followed by a `<table role="presentation" width="100%">` with one row per camera (name left, detail right). Place it where the camera list should appear; its look can't be changed from the template. For full control over the layout, use `{{sectionsText}}`. Camera, site and group names are HTML-escaped.
|
||||
|
||||
`{{sectionsText}}` example:
|
||||
|
||||
```text
|
||||
Site Gate · Offline · all 3 cameras
|
||||
- Gate camera 1 (no response for 4 min)
|
||||
- Gate camera 2 (no response for 4 min)
|
||||
- Gate camera 3 (no response for 4 min)
|
||||
|
||||
Other cameras · Camera stream lost · 1 camera
|
||||
- Lobby camera (agent online, camera stream not connected)
|
||||
```
|
||||
|
||||
**Custom templates** for this email are read by `hub-monitor-device`, so mount the `custom-email-templates` volume on `kerberoshub.monitordevice` as well (see the commented example in `values.yaml`).
|
||||
|
||||
**Which channels are grouped.** Grouping applies to the channels people read: email, Slack, Telegram, Pushover, Pushbullet, SMS and Alexa. Webhooks, IFTTT and the real-time feed are never grouped: they keep receiving one notification per camera, with the same fields and at the same moment as without grouping, so existing integrations keep working.
|
||||
|
||||
**When email stays per camera.** The grouped email is only sent with the SMTP mail provider. With SendGrid or Mailgun the template name is a template id in that account, so those keep one `device` email per camera. With SMTP, a deployment that mounts its own `device` template but no `device_group` template also keeps one email per camera, so a white-labelled device email never falls back to the default layout. Add a `device_group` template next to your `device` template to get grouped emails.
|
||||
|
||||
#### Account and sharing emails
|
||||
|
||||
Sent by the API (`kerberoshub.api`). The subject of each email is the matching `email.templates.<name>Title` value.
|
||||
|
||||
| Email (`email.templates.*`) | Placeholder | Value |
|
||||
|---|---|---|
|
||||
| Registration (`welcome`) | `{{user}}` | New user's first and last name, or username. |
|
||||
| | `{{link}}` | Activation link: `<base URL>/login/activate/<token>`. |
|
||||
| | `{{token}}` | The activation token. |
|
||||
| Account activated (`activate`) | `{{user}}` | User's first and last name, or username. |
|
||||
| | `{{link}}` | `<base URL>/login`. |
|
||||
| Password reset (`forgot`) | `{{user}}` | User's first and last name, or username. |
|
||||
| | `{{password}}` | The newly generated password. |
|
||||
| | `{{ipaddress}}` | IP address the reset was requested from. |
|
||||
| Recording shared (`share`) | `{{user}}` | Sender's first and last name, or username. |
|
||||
| | `{{url}}` | Signed link to the recording file. |
|
||||
| | `{{title}}` | The email subject (`email.templates.shareTitle`). The share's own title is only used when that value is empty. |
|
||||
| | `{{body}}` | The notes the sender added to the share. |
|
||||
| Case shared (`share_case`) | `{{user}}` | Sender's first and last name, or email address. |
|
||||
| | `{{firstname}}`, `{{lastname}}` | Sender's first and last name. |
|
||||
| | `{{url}}` | `<base URL>/share/<token>`. |
|
||||
| | `{{expiry}}` | How long the share stays valid, for example "2 days" or "1 week". |
|
||||
| Case share code (`share_case_otp`) | `{{code}}` | One-time code to open the shared case. |
|
||||
| | `{{expiry}}` | How long the code stays valid. |
|
||||
| Task assigned (`assign_task`) | `{{assignee}}` | Assigned user's first and last name, or username. |
|
||||
| | `{{user}}` | Name of the user who assigned the task. |
|
||||
| | `{{task_name}}` | The task title. |
|
||||
| | `{{link}}` | `<tenant URL>/tasks/`, using the assigned user's domain. |
|
||||
|
||||
The `newip`, `disable` and `highupload` templates are included, but no service currently sends them.
|
||||
|
||||
### Post installation
|
||||
|
||||
After the installation you'll need to initialise the Mongodb with some objects. Have a look at the `mongodb/` folder, you'll find three files available:
|
||||
@@ -719,19 +1242,37 @@ Within the Kerberos Hub front-end you'll see the option to filter through classi
|
||||
The default shared classification list lives in
|
||||
[`templates/configMaps/catalogs/classifications.yaml`](templates/configMaps/catalogs/classifications.yaml),
|
||||
independently of workflow contracts. The chart uses this bundled definition
|
||||
unless a deployer provides a ConfigMap override. Each entry contains the stable classifier output
|
||||
unless a deployer defines the catalog inline or provides a ConfigMap override. Each entry contains the stable classifier output
|
||||
`key`, the user-facing `label`, and an `icon` key. Hub API exposes this catalog
|
||||
to alerts and filters. When the mounted catalog is unavailable, compatible Hub
|
||||
API versions fall back to the legacy `settings` document and then the built-in
|
||||
classification list. Set `classificationCatalogEnabled: false` to omit the
|
||||
ConfigMap and make Hub API use that fallback directly.
|
||||
|
||||
Starting with chart **0.155.0**, the inline `classificationCatalog` values key is
|
||||
removed. Supplying it fails validation, including `null` and `[]`; it is never
|
||||
silently ignored or replaced with bundled defaults.
|
||||
The simplest way to customize the catalog is to define the complete list inline
|
||||
in your values (restored in chart **0.162.0** after its removal in 0.155.0):
|
||||
|
||||
To customize the catalog, create a ConfigMap in the release namespace containing
|
||||
the complete JSON array, then use the per-file replacement interface:
|
||||
```yaml
|
||||
classificationCatalog:
|
||||
- key: car
|
||||
label: Car
|
||||
icon: car
|
||||
- key: forklift
|
||||
label: Forklift
|
||||
```
|
||||
|
||||
The list replaces the bundled defaults entirely (entries are not merged) and is
|
||||
rendered into the chart's `classification-catalog` ConfigMap, so Hub API and the
|
||||
bundled `catalogs/classifications.json` of workflow packaging use the same list
|
||||
and a change rolls the API pod. Each entry needs a `key` and `label`, `icon` is
|
||||
optional, and keys must be unique. Omitting the key (or `null`) keeps the bundled
|
||||
defaults; `[]` is rejected, so use `classificationCatalogEnabled: false` for the
|
||||
API fallback. An inline list cannot be combined with
|
||||
`classificationCatalogEnabled: false`, `classificationCatalogExistingConfigMap` or
|
||||
a per-file catalog replacement.
|
||||
|
||||
Alternatively, create a ConfigMap in the release namespace containing the complete
|
||||
JSON array, then use the per-file replacement interface:
|
||||
|
||||
```yaml
|
||||
classificationCatalogEnabled: true
|
||||
@@ -746,13 +1287,8 @@ workflowConfiguration:
|
||||
replace: true
|
||||
```
|
||||
|
||||
To migrate, copy the existing list exactly (preserving keys, labels and icons)
|
||||
into the ConfigMap's JSON data, remove `classificationCatalog` from every values
|
||||
overlay and command-line override, and deploy the ConfigMap before the chart
|
||||
upgrade. A `null` value is not a migration: remove the key. The bundled list
|
||||
remains the default when no override is configured. If an old `[]` intentionally
|
||||
selected the API's runtime fallback, use `classificationCatalogEnabled: false`
|
||||
and configure workflow packaging without bundled defaults instead.
|
||||
Deploy that ConfigMap before the chart upgrade. The bundled list remains the
|
||||
default when no override is configured.
|
||||
|
||||
The existing `classificationCatalogExistingConfigMap: hub-classification-catalog`
|
||||
option remains supported, including for deployments without workflow packaging.
|
||||
@@ -1097,12 +1633,12 @@ not owned by workflows. A library may ship a catalog without implementing an API
|
||||
**effective** classification list under `/etc/kerberos/configuration`, using
|
||||
the directory layout above. The projected volume reuses the existing
|
||||
classification ConfigMap, including `classificationCatalogExistingConfigMap`
|
||||
overrides or explicit per-file replacements, rather
|
||||
overrides, an inline `classificationCatalog` or explicit per-file replacements, rather
|
||||
than storing another copy in the workflow ConfigMap.
|
||||
|
||||
The default classification ConfigMap name, mount path, `CLASSIFICATION_CATALOG_FILE`
|
||||
and API endpoint remain unchanged. Inline `classificationCatalog` values,
|
||||
including `[]` and `null`, are rejected from chart 0.155.0 onward. The current API
|
||||
and API endpoint remain unchanged. An inline `classificationCatalog` list is
|
||||
rendered into that ConfigMap; `[]` is rejected. The current API
|
||||
still falls back to settings/built-in values for an empty/invalid mounted catalog. The preparatory
|
||||
bundle does not reproduce that runtime fallback and must not be treated as a
|
||||
resolved API catalog. The Start-specific loader shares the existing classification
|
||||
@@ -1189,10 +1725,9 @@ A replacement for `catalogs/classifications.json` also supplies the standalone
|
||||
classification API. Both mounts use that ConfigMap and key, even when its key
|
||||
is not named `classifications.json`. The chart does not generate or checksum the
|
||||
replaced ConfigMap. To avoid two competing sources, do not combine this
|
||||
replacement with a nonempty `classificationCatalogExistingConfigMap`. That
|
||||
existing ConfigMap option remains supported when no per-file catalog replacement
|
||||
is configured. The retired inline `classificationCatalog` key is rejected
|
||||
regardless of the selected configuration.
|
||||
replacement with a nonempty `classificationCatalogExistingConfigMap` or an inline
|
||||
`classificationCatalog`. Those options remain supported when no per-file catalog
|
||||
replacement is configured.
|
||||
`classificationCatalogEnabled` must remain true when including defaults.
|
||||
|
||||
This is whole-file replacement, not field-level YAML merging or catalog-entry
|
||||
|
||||
@@ -0,0 +1,431 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1"
|
||||
/>
|
||||
<meta name="description" content="Kerberos.io Mailing">
|
||||
<style type="text/css">
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url("https://kerberos.io/dist/fonts/Inter-Regular.woff?v=/dist/fonts/Inter-Regular.woff2?v=3.183.18") format("woff2"),
|
||||
url("https://kerberos.io/dist/fonts/Inter-Regular.woff?v=/dist/fonts/Inter-Regular.woff2?v=3.183.18") format("woff");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url("https://kerberos.io/dist/fonts/Inter-Medium.woff2?v=3.18") format("woff2"),
|
||||
url("https://kerberos.io/dist/fonts/Inter-Medium.woff?v=3.18") format("woff");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url("https://kerberos.io/dist/fonts/Inter-SemiBold.woff2?v=3.18") format("woff2"),
|
||||
url("https://kerberos.io/dist/fonts/Inter-SemiBold.woff?v=3.18") format("woff");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter var';
|
||||
font-weight: 100 900;
|
||||
font-display: swap;
|
||||
font-style: normal;
|
||||
font-named-instance: 'Regular';
|
||||
src: url("https://kerberos.io/dist/fonts/Inter-roman.var.woff2?v=3.18") format("woff2");
|
||||
}
|
||||
|
||||
body{
|
||||
background: #E5E5E5;
|
||||
margin-top:0;
|
||||
margin-bottom: 0;
|
||||
margin-right: 0;
|
||||
margin-left: 0;
|
||||
padding-top: 0;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
padding-bottom: 0;
|
||||
font-family: 'Inter';
|
||||
}
|
||||
a, a:hover, a:active {
|
||||
color: #262424;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.corner-td{
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
table {border-collapse:separate;max-width: 850px; margin: 0 auto; width: 100%;}
|
||||
.ExternalClass p, .ExternalClass span, .ExternalClass font, .ExternalClass td {line-height: 100%;}
|
||||
.ExternalClass {width: 100%;}
|
||||
@media screen and (max-width:500px){
|
||||
.tab-td{
|
||||
padding-left: 10px!important;
|
||||
}
|
||||
.tab-td a h4{
|
||||
font-size: 14px!important;
|
||||
}
|
||||
.corner-td{
|
||||
width: 20px!important;
|
||||
}
|
||||
.company-name-td h3{
|
||||
font-size: 16px!important;
|
||||
}
|
||||
table.header-table{
|
||||
padding-top: 8px!important;
|
||||
padding-right: 0px!important;
|
||||
padding-bottom: 24px!important;
|
||||
padding-left: 0px!important;
|
||||
}
|
||||
.colored-card-td h4{
|
||||
font-size: 14px!important;
|
||||
}
|
||||
.colored-card-td h2{
|
||||
font-size: 20px!important;
|
||||
}
|
||||
.colored-card-td a p{
|
||||
font-size: 12px!important;
|
||||
width: 143px!important;
|
||||
}
|
||||
.colored-card-td{
|
||||
padding-top: 24px!important;
|
||||
padding-right: 24px!important;
|
||||
padding-bottom: 24px!important;
|
||||
padding-left: 24px!important;
|
||||
}
|
||||
.colorless-card-td{
|
||||
padding-top: 24px!important;
|
||||
padding-right: 24px!important;
|
||||
padding-bottom: 24px!important;
|
||||
padding-left: 24px!important;
|
||||
}
|
||||
.colorless-card-td h3{
|
||||
font-size: 18px!important;
|
||||
}
|
||||
.colorless-card-td p{
|
||||
font-size: 14px!important;
|
||||
}
|
||||
.colorless-card-table{
|
||||
margin-left: 0px!important;
|
||||
margin-right: 0px!important;
|
||||
margin-top: 24px!important;
|
||||
margin-bottom: 24px!important;
|
||||
}
|
||||
.footer-td{
|
||||
display: table-row!important;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width:600px) {
|
||||
.footer-td{
|
||||
display: table-row!important;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width:650px) {
|
||||
.footer-table{
|
||||
margin-left: 0px!important;
|
||||
margin-right: 0px!important;
|
||||
margin-top: 0px!important;
|
||||
margin-bottom: 36px!important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body height="100%" width="100%">
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="E5E5E5" style="border-collaps:collaps; mso-table-lspace:0pt; mso-table-rspace:0pt;">
|
||||
<tr>
|
||||
<td bgcolor="E5E5E5">
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" height="36" class="header-table" style="padding-top: 36px ;padding-right: 0;padding-bottom: 36px;padding-left: 0;border-collaps:collaps; mso-table-lspace:0pt; mso-table-rspace:0pt;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="corner-td" align="left"></td>
|
||||
<td width="48" height="36" align="left"><img alt="Kerberos.io" width="36" height="36" src="https://kerberos.io/images/email/kerberos.png"/></td>
|
||||
<td height="36" align="left" class="company-name-td">
|
||||
<h3 width="36" height="36" style=" font-family: Inter;
|
||||
font-size: 20px;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
line-height: 24px;
|
||||
mso-line-height-rule:exactly;
|
||||
letter-spacing: 0em;
|
||||
text-align: left;
|
||||
color: #262424;">Kerberos.io</h3>
|
||||
</td>
|
||||
<td height="36" width="36" style="padding-left: 36px;" class="tab-td" align="right">
|
||||
<a style="text-decoration: none;color: none;" href={{tab1_href}}>
|
||||
<h4 style="font-family: Inter;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 36px;
|
||||
mso-line-height-rule:exactly;
|
||||
letter-spacing: 0em;
|
||||
text-align: right;
|
||||
color: #6D6666;">{{tab1_title}}</h4>
|
||||
</a>
|
||||
</td>
|
||||
<td height="36" width="36" style="padding-left: 36px;" class="tab-td" align="right" >
|
||||
<a style="text-decoration: none;color: none;" href={{tab2_href}}>
|
||||
<h4 style="font-family: Inter;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 36px;
|
||||
mso-line-height-rule:exactly;
|
||||
letter-spacing: 0em;
|
||||
text-align: right;
|
||||
color: #6D6666;">{{tab2_title}}</h4>
|
||||
</a>
|
||||
</td>
|
||||
<td class="corner-td" align="right"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="E5E5E5" style="border-collaps:collaps; mso-table-lspace:0pt; mso-table-rspace:0pt;">
|
||||
<tr>
|
||||
<td bgcolor="E5E5E5">
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collaps:collaps; mso-table-lspace:0pt; mso-table-rspace:0pt;" >
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="corner-td" align="left"></td>
|
||||
<td class="colored-card-td" bgcolor="#57356B" style="padding-left: 48px;padding-right: 48px;padding-top: 48px;padding-bottom: 48px;border-radius: 4px;background-color:#57356B;">
|
||||
<h4 style="font-family: Inter;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 24px;
|
||||
mso-line-height-rule:exactly;
|
||||
letter-spacing: 0em;
|
||||
text-align: left;
|
||||
color:#b09fb9;">Hey, {{user}}</h4>
|
||||
<h2 style=" font-family: Inter;
|
||||
font-size: 24px;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
line-height: 36px;
|
||||
mso-line-height-rule:exactly;
|
||||
letter-spacing: 0em;
|
||||
text-align: left;
|
||||
color:#FFFFFF;
|
||||
padding-top: 12px;
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 0;
|
||||
padding-left: 0;
|
||||
padding-right: 0;">Several of your cameras changed status</h2>
|
||||
<h2 style=" font-family: Inter;
|
||||
font-size: 20px;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
line-height: 36px;
|
||||
mso-line-height-rule:exactly;
|
||||
letter-spacing: 0em;
|
||||
text-align: left;
|
||||
color:#FFFFFF;
|
||||
padding-top: 0;
|
||||
padding-bottom: 24px;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
color:#b09fb9;">{{text}}</h2>
|
||||
</td>
|
||||
<td class="corner-td" align="right"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="E5E5E5" style="border-collaps:collaps; mso-table-lspace:0pt; mso-table-rspace:0pt;">
|
||||
<tr>
|
||||
<td bgcolor="E5E5E5">
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" class="colorless-card-table" style="margin-top: 36px;margin-bottom: 36px;margin-left: 0;margin-right: 0;border-collaps:collaps; mso-table-lspace:0pt; mso-table-rspace:0pt;" >
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="corner-td" align="left"></td>
|
||||
<td class="colorless-card-td" bgcolor="#FFFFFF" style="background-color:#FFFFFF;padding-top: 36px;padding-right: 43px;padding-bottom: 25px;padding-left: 43px;border-radius: 4px;">
|
||||
<h3 style=" font-family: Inter;
|
||||
font-size: 20px;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
line-height: 36px;
|
||||
mso-line-height-rule:exactly;
|
||||
letter-spacing: 0em;
|
||||
text-align: left;
|
||||
color: #262424;
|
||||
width: 480px">What changed</h3>
|
||||
{{sections}}
|
||||
<p style=" font-family: Inter;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 24px;
|
||||
mso-line-height-rule:exactly;
|
||||
letter-spacing: 0em;
|
||||
text-align: left;
|
||||
color: #6D6666;
|
||||
margin-top: 12px;">Cameras that change status together usually share a cause, such as a site losing or regaining its internet connection or power. Have a look into your Kerberos Hub account for the latest recordings and the status of your cameras.</p>
|
||||
|
||||
<a style="text-decoration: none;color: none;" href="{{link}}">
|
||||
<p style="font-family: Inter;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
line-height: 24px;
|
||||
mso-line-height-rule:exactly;
|
||||
letter-spacing: 0em;
|
||||
text-align: left;
|
||||
color:#FFFFFF;
|
||||
background-color: #84559F;
|
||||
padding-top: 6px;
|
||||
padding-bottom: 6px;
|
||||
padding-right: 16px;
|
||||
padding-left: 16px;
|
||||
width: 160px;
|
||||
border-radius: 4px;
|
||||
text-align: center;
|
||||
cursor: pointer;">Go to Kerberos Hub -></p>
|
||||
</a>
|
||||
</td>
|
||||
<td class="corner-td" align="right"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="E5E5E5" style="border-collaps:collaps; mso-table-lspace:0pt; mso-table-rspace:0pt;">
|
||||
<tr>
|
||||
<td bgcolor="E5E5E5">
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" class="footer-table" style="margin-top: 0;border-collaps:collaps; mso-table-lspace:0pt; mso-table-rspace:0pt;" >
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="corner-td" align="left"></td>
|
||||
<!--[if mso | IE]>
|
||||
<table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td style="vertical-align:top;display:table-row !important">
|
||||
<![endif]-->
|
||||
<td height="146" width="190" class="footer-td" style="margin-bottom: 12px;" valign="top" align="left">
|
||||
<table style="border-collaps:collaps; mso-table-lspace:0pt; mso-table-rspace:0pt;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<h4 style=" font-family: Inter;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
line-height: 36px;
|
||||
mso-line-height-rule:exactly;
|
||||
letter-spacing: 0em;
|
||||
text-align: left;
|
||||
color:#6D6666;">Get in touch</h4>
|
||||
<a style="text-decoration: none;color: none;" href="mailto:support@verstraeten.io">
|
||||
<p style=" font-family: Inter;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 16px;
|
||||
mso-line-height-rule:exactly;
|
||||
letter-spacing: 0em;
|
||||
text-align: left;
|
||||
color: #A69D9D;">support@kerberos.io</p>
|
||||
</a>
|
||||
<p style=" font-family: Inter;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 16px;
|
||||
mso-line-height-rule:exactly;
|
||||
letter-spacing: 0em;
|
||||
text-align: left;
|
||||
color: #A69D9D;">9000 Ghent, BE</p>
|
||||
|
||||
<a style="text-decoration: none;color: none;" href="https://kerberos.io/">
|
||||
<p style=" font-family: Inter;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 24px;
|
||||
mso-line-height-rule:exactly;
|
||||
letter-spacing: 0em;
|
||||
text-align: left;
|
||||
color: #A69D9D;">https://kerberos.io</p>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
<!--[if mso | IE]>
|
||||
<table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td style="vertical-align:top;display:table-row !important">
|
||||
<![endif]-->
|
||||
<td class="footer-td" style="border-radius: 4px;padding-left: 0;padding-right: 0;padding-top: 0;padding-bottom: 0; margin-bottom: 12px;" valign="top" align="left">
|
||||
<table style="border-collaps:collaps; mso-table-lspace:0pt; mso-table-rspace:0pt;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<h4 style=" font-family: Inter;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
line-height: 36px;
|
||||
mso-line-height-rule:exactly;
|
||||
letter-spacing: 0em;
|
||||
text-align: left;
|
||||
color:#6D6666;">About Kerberos</h4>
|
||||
<p style=" font-family: Inter;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 24px;
|
||||
mso-line-height-rule:exactly;
|
||||
letter-spacing: 0em;
|
||||
text-align: left;
|
||||
color: #A69D9D;">Welcome to the revolutionary video analytics and video management platform. Open, modular, and extensible for everyone, anywhere.</p>
|
||||
|
||||
|
||||
<p style="margin-top: 12px;">
|
||||
<a href="https://twitter.com/kerberosio" style="text-decoration: none;color: none;">
|
||||
<img width="24" height="24" alt="Twitter" src="https://kerberos.io/images/email/twitter.png"/>
|
||||
</a>
|
||||
<a href="https://reddit.com/r/kerberos_io" style="text-decoration: none;color: none;">
|
||||
<img g width="24" height="24" alt="Reddit" src="https://kerberos.io/images/email/reddit.png"/>
|
||||
</a>
|
||||
<a href="https://www.youtube.com/channel/UCnd9q7iRNNw4W95eQwQuECA" style="text-decoration: none;color: none;">
|
||||
<img g width="24" height="24" alt="Youtube" src="https://kerberos.io/images/email/youtube.png"/>
|
||||
</a>
|
||||
<a href="https://github.com/kerberos-io" style="text-decoration: none;color: none;">
|
||||
<img g width="24" height="24" alt="Github" src="https://kerberos.io/images/email/github.png"/>
|
||||
</a>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
<td class="corner-td" align="right"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table style="border-collaps:collaps; mso-table-lspace:0pt; mso-table-rspace:0pt;">
|
||||
<tbody>
|
||||
<tr style="height: 50px">
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,22 @@
|
||||
Kerberos.io
|
||||
------------
|
||||
|
||||
Hey, {{user}}
|
||||
|
||||
Several of your cameras changed status
|
||||
{{text}}
|
||||
|
||||
{{sectionsText}}
|
||||
|
||||
Go to Kerberos Hub ->
|
||||
{{link}}
|
||||
|
||||
Get in touch
|
||||
------------
|
||||
support@kerberos.io
|
||||
9000 Ghent, BE
|
||||
https://kerberos.io
|
||||
|
||||
About Kerberos
|
||||
------------
|
||||
Welcome to the revolutionary video analytics and video management platform. Open, modular, and extensible for everyone, anywhere.
|
||||
@@ -1,7 +1,6 @@
|
||||
# Create these ConfigMaps in the release namespace before deploying the chart.
|
||||
# Keys contain complete contracts/catalogs; they are not merged with chart data.
|
||||
# Remove the retired classificationCatalog key from every values overlay.
|
||||
# Do not also set classificationCatalogExistingConfigMap.
|
||||
# Do not also set classificationCatalog or classificationCatalogExistingConfigMap.
|
||||
classificationCatalogEnabled: true
|
||||
workflowConfiguration:
|
||||
enabled: true
|
||||
|
||||
@@ -22,9 +22,6 @@
|
||||
|
||||
{{/* Normalize both interfaces without changing the caller's values. */}}
|
||||
{{- define "hub.workflowConfiguration.settings" -}}
|
||||
{{- if hasKey .Values "classificationCatalog" -}}
|
||||
{{- fail "classificationCatalog is no longer supported; remove the values key and place the complete catalog in a ConfigMap, then use workflowConfiguration.configMaps with path: catalogs/classifications.json and replace: true" -}}
|
||||
{{- end -}}
|
||||
{{- $config := .Values.workflowConfiguration | default dict -}}
|
||||
{{- $unified := or (hasKey $config "includeDefaults") (hasKey $config "configMaps") -}}
|
||||
{{- $legacy := or (hasKey $config "existingConfigMap") (hasKey $config "items") (hasKey $config "extraConfigMaps") -}}
|
||||
@@ -78,6 +75,17 @@
|
||||
{{- fail "a classifications.json replacement cannot be combined with classificationCatalogExistingConfigMap; choose one override mechanism" -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- if not (kindIs "invalid" .Values.classificationCatalog) -}}
|
||||
{{- if not .Values.classificationCatalogEnabled -}}
|
||||
{{- fail "classificationCatalog requires classificationCatalogEnabled; remove the inline list to use the API fallback" -}}
|
||||
{{- end -}}
|
||||
{{- if .Values.classificationCatalogExistingConfigMap -}}
|
||||
{{- fail "classificationCatalog cannot be combined with classificationCatalogExistingConfigMap; choose one override mechanism" -}}
|
||||
{{- end -}}
|
||||
{{- if hasKey $replacements "catalogs/classifications.json" -}}
|
||||
{{- fail "classificationCatalog cannot be combined with a classifications.json replacement; choose one override mechanism" -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- dict "enabled" $enabled "includeDefaults" $includeDefaults "configMaps" $normalizedSources "replacements" $replacements | toYaml -}}
|
||||
{{- end -}}
|
||||
|
||||
|
||||
@@ -84,17 +84,25 @@ Usage: {{- include "hub.mongodb.appUriEnv" (dict "root" $ "app" "hub-api") | nin
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
OpenTelemetry sampler env entries, rendered only when configured. The OTel SDKs
|
||||
read these natively; unset keeps the SDK default (parentbased_always_on).
|
||||
OpenTelemetry sampling env.
|
||||
"hub.opentelemetry.samplerEnv" is for the services that only continue traces
|
||||
started elsewhere (hub-workflows, pipe-*, workflow stages): they follow the
|
||||
parent's sampling decision and never start traces of their own.
|
||||
"hub.opentelemetry.apiSamplerEnv" renders hub-api's per-class sampling ratios
|
||||
(opentelemetry.apiSamplingRatios) as TRACE_SAMPLER_RATIO_<CLASS>; empty values
|
||||
are not rendered.
|
||||
Usage: {{- include "hub.opentelemetry.samplerEnv" $ | trim | nindent 8 }}
|
||||
*/}}
|
||||
{{- define "hub.opentelemetry.samplerEnv" -}}
|
||||
{{- with .Values.opentelemetry.sampler.type }}
|
||||
- name: OTEL_TRACES_SAMPLER
|
||||
value: {{ . | quote }}
|
||||
value: "parentbased_always_off"
|
||||
{{- end -}}
|
||||
|
||||
{{- define "hub.opentelemetry.apiSamplerEnv" -}}
|
||||
{{- range $class, $ratio := (.Values.opentelemetry.apiSamplingRatios | default dict) }}
|
||||
{{- if ne (toString $ratio) "" }}
|
||||
- name: TRACE_SAMPLER_RATIO_{{ upper $class }}
|
||||
value: {{ toString $ratio | quote }}
|
||||
{{- end }}
|
||||
{{- with .Values.opentelemetry.sampler.arg }}
|
||||
- name: OTEL_TRACES_SAMPLER_ARG
|
||||
value: {{ . | quote }}
|
||||
{{- end }}
|
||||
{{- end -}}
|
||||
|
||||
@@ -26,6 +26,10 @@ spec:
|
||||
- name: admin
|
||||
image: "{{ .Values.global.imageRegistry }}{{ .Values.admin.repository }}:{{ .Values.admin.tag }}"
|
||||
imagePullPolicy: {{ .Values.admin.pullPolicy }}
|
||||
{{- with .Values.admin.securityContext }}
|
||||
securityContext:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- with .Values.admin.resources }}
|
||||
resources:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
@@ -36,8 +40,16 @@ spec:
|
||||
{{- end }}
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 80
|
||||
containerPort: {{ .Values.admin.containerPort | default 80 }}
|
||||
protocol: TCP
|
||||
{{- with .Values.admin.readinessProbe }}
|
||||
readinessProbe:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- with .Values.admin.livenessProbe }}
|
||||
livenessProbe:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
env:
|
||||
- name: LOG_LEVEL
|
||||
value: "{{ .Values.admin.logLevel }}"
|
||||
|
||||
@@ -10,7 +10,7 @@ spec:
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 80
|
||||
targetPort: 80
|
||||
targetPort: http
|
||||
name: http
|
||||
selector:
|
||||
app: admin
|
||||
|
||||
@@ -11,6 +11,28 @@
|
||||
{"key": "cell phone", "label": "Cell phone", "icon": "cell phone"}
|
||||
]
|
||||
{{- end -}}
|
||||
{{/*
|
||||
The effective chart-rendered catalog: the inline classificationCatalog values
|
||||
list when set, otherwise the bundled defaults above. values.schema.json
|
||||
validates each entry ({key, label, icon?}); keys must also be unique.
|
||||
*/}}
|
||||
{{- define "hub.classificationCatalog.json" -}}
|
||||
{{- if kindIs "invalid" .Values.classificationCatalog -}}
|
||||
{{- include "hub.classificationCatalog.defaults" . | mustFromJson | toJson -}}
|
||||
{{- else if and (kindIs "slice" .Values.classificationCatalog) .Values.classificationCatalog -}}
|
||||
{{- $seen := dict -}}
|
||||
{{- range $entry := .Values.classificationCatalog -}}
|
||||
{{- $key := toString $entry.key -}}
|
||||
{{- if hasKey $seen $key -}}
|
||||
{{- fail (printf "classificationCatalog: duplicate key %q" $key) -}}
|
||||
{{- end -}}
|
||||
{{- $_ := set $seen $key true -}}
|
||||
{{- end -}}
|
||||
{{- .Values.classificationCatalog | toJson -}}
|
||||
{{- else -}}
|
||||
{{- fail "classificationCatalog must be omitted (bundled defaults) or a nonempty list; to use the API fallback set classificationCatalogEnabled: false" -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- $config := include "hub.workflowConfiguration.settings" . | fromYaml -}}
|
||||
{{- if and .Values.classificationCatalogEnabled (not .Values.classificationCatalogExistingConfigMap) (not (hasKey $config.replacements "catalogs/classifications.json")) }}
|
||||
apiVersion: v1
|
||||
@@ -20,5 +42,5 @@ metadata:
|
||||
namespace: {{ .Release.Namespace }}
|
||||
data:
|
||||
classifications.json: |-
|
||||
{{- include "hub.classificationCatalog.defaults" . | mustFromJson | toPrettyJson | nindent 4 }}
|
||||
{{- include "hub.classificationCatalog.json" . | mustFromJson | toPrettyJson | nindent 4 }}
|
||||
{{- end -}}
|
||||
|
||||
@@ -10,33 +10,60 @@
|
||||
{{- fail (printf "duplicate audit destination id %q" $id) -}}
|
||||
{{- end -}}
|
||||
{{- $_ := set $destinationIDs $id true -}}
|
||||
{{- $url := required (printf "url is required for audit destination %s" $id) $destination.url -}}
|
||||
{{- $type := default "webhook" $destination.type -}}
|
||||
{{- $entry := dict
|
||||
"id" $id
|
||||
"type" (default "webhook" $destination.type)
|
||||
"type" $type
|
||||
"enabled" $destination.enabled
|
||||
"url" $url
|
||||
"timeoutSeconds" (default 10 $destination.timeoutSeconds)
|
||||
"batchSize" (default 100 $destination.batchSize)
|
||||
"maxAttempts" (default 10 $destination.maxAttempts)
|
||||
"startAt" (default "latest" $destination.startAt)
|
||||
"headers" (default dict $destination.headers)
|
||||
"filters" (default dict $destination.filters) -}}
|
||||
{{- $secretRoot := printf "/var/run/secrets/audit-destinations/%s" $id -}}
|
||||
{{- $bearer := default dict $destination.bearerTokenSecret -}}
|
||||
{{- if gt (len $bearer) 0 -}}
|
||||
{{- $secretName := required (printf "bearerTokenSecret.name is required for destination %s" $id) $bearer.name -}}
|
||||
{{- $secretKey := default "token" $bearer.key -}}
|
||||
{{- $_ := set $entry "bearerTokenFile" (printf "%s/bearer-%s" $secretRoot (sha256sum (printf "%s:%s" $secretName $secretKey) | trunc 12)) -}}
|
||||
{{- end -}}
|
||||
{{- $headerFiles := dict -}}
|
||||
{{- range $headerName, $secret := (default dict $destination.secretHeaders) -}}
|
||||
{{- $secretName := required (printf "secretName is required for destination %s header %s" $id $headerName) $secret.secretName -}}
|
||||
{{- $secretKey := required (printf "secretKey is required for destination %s header %s" $id $headerName) $secret.secretKey -}}
|
||||
{{- $_ := set $headerFiles $headerName (printf "%s/header-%s" $secretRoot (sha256sum (printf "%s:%s:%s" $headerName $secretName $secretKey) | trunc 12)) -}}
|
||||
{{- end -}}
|
||||
{{- if gt (len $headerFiles) 0 -}}
|
||||
{{- $_ := set $entry "headerFiles" $headerFiles -}}
|
||||
{{- if eq $type "webhook" -}}
|
||||
{{- if or $destination.s3 $destination.credentialsSecret -}}
|
||||
{{- fail (printf "audit destination %s is a webhook and cannot set s3 or credentialsSecret" $id) -}}
|
||||
{{- end -}}
|
||||
{{- $_ := set $entry "url" (required (printf "url is required for audit destination %s" $id) $destination.url) -}}
|
||||
{{- $_ := set $entry "headers" (default dict $destination.headers) -}}
|
||||
{{- $bearer := default dict $destination.bearerTokenSecret -}}
|
||||
{{- if gt (len $bearer) 0 -}}
|
||||
{{- $secretName := required (printf "bearerTokenSecret.name is required for destination %s" $id) $bearer.name -}}
|
||||
{{- $secretKey := default "token" $bearer.key -}}
|
||||
{{- $_ := set $entry "bearerTokenFile" (printf "%s/bearer-%s" $secretRoot (sha256sum (printf "%s:%s" $secretName $secretKey) | trunc 12)) -}}
|
||||
{{- end -}}
|
||||
{{- $headerFiles := dict -}}
|
||||
{{- range $headerName, $secret := (default dict $destination.secretHeaders) -}}
|
||||
{{- $secretName := required (printf "secretName is required for destination %s header %s" $id $headerName) $secret.secretName -}}
|
||||
{{- $secretKey := required (printf "secretKey is required for destination %s header %s" $id $headerName) $secret.secretKey -}}
|
||||
{{- $_ := set $headerFiles $headerName (printf "%s/header-%s" $secretRoot (sha256sum (printf "%s:%s:%s" $headerName $secretName $secretKey) | trunc 12)) -}}
|
||||
{{- end -}}
|
||||
{{- if gt (len $headerFiles) 0 -}}
|
||||
{{- $_ := set $entry "headerFiles" $headerFiles -}}
|
||||
{{- end -}}
|
||||
{{- else if eq $type "s3" -}}
|
||||
{{- if or $destination.url $destination.headers $destination.bearerTokenSecret $destination.secretHeaders -}}
|
||||
{{- fail (printf "audit destination %s is s3 and cannot set url, headers, bearerTokenSecret or secretHeaders" $id) -}}
|
||||
{{- end -}}
|
||||
{{- $s3 := required (printf "s3 is required for audit destination %s" $id) $destination.s3 -}}
|
||||
{{- $settings := dict
|
||||
"bucket" (required (printf "s3.bucket is required for audit destination %s" $id) $s3.bucket)
|
||||
"region" (required (printf "s3.region is required for audit destination %s" $id) $s3.region) -}}
|
||||
{{- range $key := list "prefix" "endpoint" "forcePathStyle" "allowUnlockedBucket" -}}
|
||||
{{- if hasKey $s3 $key -}}
|
||||
{{- $_ := set $settings $key (get $s3 $key) -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- $credentials := default dict $destination.credentialsSecret -}}
|
||||
{{- if gt (len $credentials) 0 -}}
|
||||
{{- $secretName := required (printf "credentialsSecret.name is required for destination %s" $id) $credentials.name -}}
|
||||
{{- $_ := set $settings "accessKeyIdFile" (printf "%s/access-key-id-%s" $secretRoot (sha256sum (printf "%s:%s" $secretName (default "access-key-id" $credentials.accessKeyIdKey)) | trunc 12)) -}}
|
||||
{{- $_ := set $settings "secretAccessKeyFile" (printf "%s/secret-access-key-%s" $secretRoot (sha256sum (printf "%s:%s" $secretName (default "secret-access-key" $credentials.secretAccessKeyKey)) | trunc 12)) -}}
|
||||
{{- end -}}
|
||||
{{- $_ := set $entry "s3" $settings -}}
|
||||
{{- else -}}
|
||||
{{- fail (printf "audit destination %s has unsupported type %q (webhook or s3)" $id $type) -}}
|
||||
{{- end -}}
|
||||
{{- $destinations = append $destinations $entry -}}
|
||||
{{- end -}}
|
||||
|
||||
@@ -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 }}
|
||||
@@ -436,6 +444,17 @@ spec:
|
||||
- name: MFA_ISSUER
|
||||
value: "{{ .Values.kerberoshub.api.mfaIssuer }}"
|
||||
|
||||
{{- if .Values.kerberoshub.api.passkeys.enabled }}
|
||||
- name: FEATURE_PASSKEYS_ENABLED
|
||||
value: "true"
|
||||
- name: PASSKEY_RP_ID
|
||||
value: {{ required "kerberoshub.api.passkeys.rpId is required when passkeys are enabled" .Values.kerberoshub.api.passkeys.rpId | quote }}
|
||||
- name: PASSKEY_ORIGINS
|
||||
value: {{ required "kerberoshub.api.passkeys.origins is required when passkeys are enabled" .Values.kerberoshub.api.passkeys.origins | join "," | quote }}
|
||||
- name: PASSKEY_RP_NAME
|
||||
value: {{ .Values.kerberoshub.api.passkeys.rpName | quote }}
|
||||
{{- end }}
|
||||
|
||||
# AWS (Legacy, use Kerberos Vault instead)
|
||||
- name: AWS_REGION
|
||||
value: "{{ .Values.kerberoshub.api.aws.region }}"
|
||||
@@ -542,6 +561,10 @@ spec:
|
||||
value: "{{ .Values.kerberoshub.api.defaultTaskRetentionDays }}"
|
||||
- name: CASES_MAX_RETENTION_DAYS
|
||||
value: "{{ .Values.kerberoshub.api.casesMaxRetentionDays }}"
|
||||
{{- with .Values.kerberoshub.api.auditExportMaxEventsPerFile }}
|
||||
- name: AUDIT_EXPORT_MAX_EVENTS_PER_FILE
|
||||
value: {{ . | int64 | toString | quote }}
|
||||
{{- end }}
|
||||
|
||||
# SMTP
|
||||
- name: SMTP_SERVER
|
||||
@@ -597,6 +620,6 @@ spec:
|
||||
value: "{{ .Values.opentelemetry.routingEnabled }}"
|
||||
- name: OTEL_EXPORTER_OTLP_ENDPOINT
|
||||
value: "{{ .Values.opentelemetry.collector.endpoint }}"
|
||||
{{- include "hub.opentelemetry.samplerEnv" $ | trim | nindent 12 }}
|
||||
{{- include "hub.opentelemetry.apiSamplerEnv" $ | trim | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
{{- if and .Values.audit.enabled (default dict .Values.kerberoshub.audit.serviceAccount).create }}
|
||||
{{- $serviceAccount := .Values.kerberoshub.audit.serviceAccount }}
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: {{ default "hub-audit" $serviceAccount.name }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
{{- with $serviceAccount.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- with $serviceAccount.labels }}
|
||||
labels:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -26,7 +26,12 @@ spec:
|
||||
app: hub-audit
|
||||
spec:
|
||||
terminationGracePeriodSeconds: {{ .Values.kerberoshub.audit.terminationGracePeriodSeconds }}
|
||||
{{- if .Values.kerberoshub.serviceAccount.create }}
|
||||
{{- $auditServiceAccount := default dict .Values.kerberoshub.audit.serviceAccount }}
|
||||
{{- if $auditServiceAccount.create }}
|
||||
serviceAccountName: {{ default "hub-audit" $auditServiceAccount.name }}
|
||||
{{- else if $auditServiceAccount.name }}
|
||||
serviceAccountName: {{ $auditServiceAccount.name }}
|
||||
{{- else if .Values.kerberoshub.serviceAccount.create }}
|
||||
serviceAccountName: {{ default (printf "%s-%s-sa" .Release.Name .Chart.Name | trunc 63 | trimSuffix "-") .Values.kerberoshub.serviceAccount.name }}
|
||||
{{- end }}
|
||||
{{- with .Values.imagePullSecrets }}
|
||||
@@ -41,10 +46,22 @@ spec:
|
||||
{{- range $index, $destination := .Values.kerberoshub.audit.destinations }}
|
||||
{{- $bearer := default dict $destination.bearerTokenSecret }}
|
||||
{{- $secretHeaders := default dict $destination.secretHeaders }}
|
||||
{{- if or (gt (len $bearer) 0) (gt (len $secretHeaders) 0) }}
|
||||
{{- $credentials := default dict $destination.credentialsSecret }}
|
||||
{{- if or (gt (len $bearer) 0) (gt (len $secretHeaders) 0) (gt (len $credentials) 0) }}
|
||||
- name: audit-dest-{{ sha256sum $destination.id | trunc 12 }}
|
||||
projected:
|
||||
sources:
|
||||
{{- if gt (len $credentials) 0 }}
|
||||
{{- $accessKeyIdKey := default "access-key-id" $credentials.accessKeyIdKey }}
|
||||
{{- $secretAccessKeyKey := default "secret-access-key" $credentials.secretAccessKeyKey }}
|
||||
- secret:
|
||||
name: {{ required (printf "credentialsSecret.name is required for destination %s" $destination.id) $credentials.name | quote }}
|
||||
items:
|
||||
- key: {{ $accessKeyIdKey | quote }}
|
||||
path: access-key-id-{{ sha256sum (printf "%s:%s" $credentials.name $accessKeyIdKey) | trunc 12 }}
|
||||
- key: {{ $secretAccessKeyKey | quote }}
|
||||
path: secret-access-key-{{ sha256sum (printf "%s:%s" $credentials.name $secretAccessKeyKey) | trunc 12 }}
|
||||
{{- end }}
|
||||
{{- if gt (len $bearer) 0 }}
|
||||
- secret:
|
||||
name: {{ required (printf "bearerTokenSecret.name is required for destination %s" $destination.id) $bearer.name | quote }}
|
||||
@@ -98,7 +115,8 @@ spec:
|
||||
{{- range $index, $destination := .Values.kerberoshub.audit.destinations }}
|
||||
{{- $bearer := default dict $destination.bearerTokenSecret }}
|
||||
{{- $secretHeaders := default dict $destination.secretHeaders }}
|
||||
{{- if or (gt (len $bearer) 0) (gt (len $secretHeaders) 0) }}
|
||||
{{- $credentials := default dict $destination.credentialsSecret }}
|
||||
{{- if or (gt (len $bearer) 0) (gt (len $secretHeaders) 0) (gt (len $credentials) 0) }}
|
||||
- name: audit-dest-{{ sha256sum $destination.id | trunc 12 }}
|
||||
mountPath: /var/run/secrets/audit-destinations/{{ $destination.id }}
|
||||
readOnly: true
|
||||
|
||||
@@ -22,6 +22,10 @@ spec:
|
||||
labels:
|
||||
app: hub-cleanup
|
||||
spec:
|
||||
{{- with .Values.kerberoshub.cleanup.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 }}
|
||||
@@ -47,6 +51,10 @@ spec:
|
||||
- name: hub-cleanup
|
||||
image: "{{ .Values.global.imageRegistry }}{{ .Values.kerberoshub.cleanup.repository }}:{{ .Values.kerberoshub.cleanup.tag }}"
|
||||
imagePullPolicy: {{ .Values.kerberoshub.cleanup.pullPolicy }}
|
||||
{{- with .Values.kerberoshub.cleanup.securityContext }}
|
||||
securityContext:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- with .Values.kerberoshub.cleanup.resources }}
|
||||
resources:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
|
||||
@@ -10,7 +10,7 @@ spec:
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 80
|
||||
targetPort: 80
|
||||
targetPort: http
|
||||
name: http
|
||||
selector:
|
||||
app: hub-frontend-demo
|
||||
@@ -101,12 +101,16 @@ spec:
|
||||
- name: hub-frontend-demo
|
||||
image: "{{ .Values.global.imageRegistry }}{{ .Values.kerberoshub.frontend.repository }}:{{ .Values.kerberoshub.frontend.tag }}"
|
||||
imagePullPolicy: {{ .Values.kerberoshub.frontend.pullPolicy }}
|
||||
{{- with .Values.kerberoshub.frontend.securityContext }}
|
||||
securityContext:
|
||||
{{- toYaml . | nindent 10 }}
|
||||
{{- end }}
|
||||
{{- with .Values.kerberoshub.frontend.resources }}
|
||||
resources:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
ports:
|
||||
- containerPort: 80
|
||||
- containerPort: {{ .Values.kerberoshub.frontend.containerPort | default 8080 }}
|
||||
name: http
|
||||
{{- with .Values.kerberoshub.frontend.volumeMounts}}
|
||||
volumeMounts:
|
||||
@@ -237,6 +241,8 @@ spec:
|
||||
value: "{{ .Values.kerberoshub.frontend.features.workflows.enabled }}"
|
||||
- name: FEATURE_AUDIT_ENABLED
|
||||
value: "{{ .Values.kerberoshub.frontend.features.audit.enabled }}"
|
||||
- name: FEATURE_PASSKEYS_ENABLED
|
||||
value: "{{ .Values.kerberoshub.api.passkeys.enabled }}"
|
||||
- name: FEATURE_DAY_OVERVIEW_ENABLED
|
||||
value: "{{ .Values.kerberoshub.frontend.features.dayOverview.enabled }}"
|
||||
- name: FEATURE_DEVICE_ANALYTICS_ENABLED
|
||||
@@ -361,6 +367,10 @@ spec:
|
||||
value: "{{ .Values.kerberoshub.frontend.features.faceRedaction.classifierTracksEnabled }}"
|
||||
|
||||
# features > media
|
||||
- name: FEATURE_MEDIA_FORWARD_ENABLED
|
||||
value: "{{ .Values.kerberoshub.frontend.features.media.forward.enabled }}"
|
||||
- name: FEATURE_MEDIA_DETAIL_ANALYSIS_ENABLED
|
||||
value: "{{ .Values.kerberoshub.frontend.features.media.detail.analysis.enabled }}"
|
||||
- name: FEATURE_MEDIA_FILTER_DATE_ENABLED
|
||||
value: "{{ .Values.kerberoshub.frontend.features.media.filter.date.enabled }}"
|
||||
- name: FEATURE_MEDIA_FILTER_SITES_ENABLED
|
||||
|
||||
@@ -11,7 +11,7 @@ spec:
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 80
|
||||
targetPort: 80
|
||||
targetPort: http
|
||||
name: http
|
||||
selector:
|
||||
app: hub-frontend
|
||||
@@ -182,12 +182,16 @@ spec:
|
||||
- name: hub-frontend
|
||||
image: "{{ .Values.global.imageRegistry }}{{ .Values.kerberoshub.frontend.repository }}:{{ .Values.kerberoshub.frontend.tag }}"
|
||||
imagePullPolicy: {{ .Values.kerberoshub.frontend.pullPolicy }}
|
||||
{{- with .Values.kerberoshub.frontend.securityContext }}
|
||||
securityContext:
|
||||
{{- toYaml . | nindent 10 }}
|
||||
{{- end }}
|
||||
{{- with .Values.kerberoshub.frontend.resources }}
|
||||
resources:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
ports:
|
||||
- containerPort: 80
|
||||
- containerPort: {{ .Values.kerberoshub.frontend.containerPort | default 8080 }}
|
||||
name: http
|
||||
{{- with .Values.kerberoshub.frontend.volumeMounts}}
|
||||
volumeMounts:
|
||||
@@ -316,6 +320,8 @@ spec:
|
||||
value: "{{ .Values.kerberoshub.frontend.features.workflows.enabled }}"
|
||||
- name: FEATURE_AUDIT_ENABLED
|
||||
value: "{{ .Values.kerberoshub.frontend.features.audit.enabled }}"
|
||||
- name: FEATURE_PASSKEYS_ENABLED
|
||||
value: "{{ .Values.kerberoshub.api.passkeys.enabled }}"
|
||||
- name: FEATURE_DAY_OVERVIEW_ENABLED
|
||||
value: "{{ .Values.kerberoshub.frontend.features.dayOverview.enabled }}"
|
||||
- name: FEATURE_DEVICE_ANALYTICS_ENABLED
|
||||
@@ -453,6 +459,10 @@ spec:
|
||||
value: "{{ .Values.kerberoshub.frontend.features.faceRedaction.classifierTracksEnabled }}"
|
||||
|
||||
# features > media
|
||||
- name: FEATURE_MEDIA_FORWARD_ENABLED
|
||||
value: "{{ .Values.kerberoshub.frontend.features.media.forward.enabled }}"
|
||||
- name: FEATURE_MEDIA_DETAIL_ANALYSIS_ENABLED
|
||||
value: "{{ .Values.kerberoshub.frontend.features.media.detail.analysis.enabled }}"
|
||||
- name: FEATURE_MEDIA_FILTER_DATE_ENABLED
|
||||
value: "{{ .Values.kerberoshub.frontend.features.media.filter.date.enabled }}"
|
||||
- name: FEATURE_MEDIA_FILTER_SITES_ENABLED
|
||||
|
||||
@@ -22,6 +22,10 @@ spec:
|
||||
labels:
|
||||
app: hub-monitor-device
|
||||
spec:
|
||||
{{- with .Values.kerberoshub.monitordevice.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 }}
|
||||
@@ -47,6 +51,10 @@ spec:
|
||||
- name: hub-monitor-device
|
||||
image: "{{ .Values.global.imageRegistry }}{{ .Values.kerberoshub.monitordevice.repository }}:{{ .Values.kerberoshub.monitordevice.tag }}"
|
||||
imagePullPolicy: {{ .Values.kerberoshub.monitordevice.pullPolicy }}
|
||||
{{- with .Values.kerberoshub.monitordevice.securityContext }}
|
||||
securityContext:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- with .Values.kerberoshub.monitordevice.resources }}
|
||||
resources:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
@@ -67,6 +75,8 @@ spec:
|
||||
{{- include "hub.mongodb.appUriEnv" (dict "root" $ "app" "hub-monitor-device") | nindent 12 }}
|
||||
- name: LOG_LEVEL
|
||||
value: "{{ .Values.kerberoshub.monitordevice.logLevel }}"
|
||||
- name: DEVICE_SCAN_INTERVAL_SECONDS
|
||||
value: "{{ .Values.kerberoshub.monitordevice.scanIntervalSeconds | default 60 }}"
|
||||
- name: READ_ONLY
|
||||
value: "{{ .Values.readonly }}"
|
||||
# Mail settings
|
||||
@@ -82,6 +92,13 @@ spec:
|
||||
value: "{{ .Values.email.templates.device }}"
|
||||
- name: DEVICE_TITLE
|
||||
value: "{{ .Values.email.templates.deviceTitle }}"
|
||||
- name: DEVICE_GROUP_TEMPLATE
|
||||
value: "{{ .Values.email.templates.deviceGroup | default "device_group" }}"
|
||||
- name: DEVICE_GROUP_TITLE
|
||||
value: "{{ .Values.email.templates.deviceGroupTitle | default "[Device] Several cameras changed status" }}"
|
||||
# Base URL for the link in grouped device notifications.
|
||||
- name: PUBLIC_URL
|
||||
value: "{{ .Values.kerberoshub.frontend.schema }}://{{ .Values.kerberoshub.frontend.url }}"
|
||||
|
||||
# - Plain SMTP
|
||||
- name: SMTP_SERVER
|
||||
|
||||
@@ -50,6 +50,10 @@ spec:
|
||||
labels:
|
||||
app: hub-{{ $name }}
|
||||
spec:
|
||||
{{- with $svc.podSecurityContext }}
|
||||
securityContext:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- if $root.Values.kerberoshub.serviceAccount.create }}
|
||||
serviceAccountName: {{ default (printf "%s-%s-sa" $root.Release.Name $root.Chart.Name | trunc 63 | trimSuffix "-") $root.Values.kerberoshub.serviceAccount.name }}
|
||||
{{- end }}
|
||||
@@ -69,6 +73,10 @@ spec:
|
||||
- name: hub-{{ $name }}
|
||||
image: "{{ $root.Values.global.imageRegistry }}{{ $svc.repository }}:{{ $svc.tag }}"
|
||||
imagePullPolicy: {{ $svc.pullPolicy | default "IfNotPresent" }}
|
||||
{{- with $svc.securityContext }}
|
||||
securityContext:
|
||||
{{- toYaml . | nindent 10 }}
|
||||
{{- end }}
|
||||
{{- with $svc.resources }}
|
||||
resources:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
@@ -117,6 +125,14 @@ spec:
|
||||
- name: KERBEROS_STORAGE_PROVIDER
|
||||
value: {{ . | quote }}
|
||||
{{- end }}
|
||||
{{- if $root.Values.opentelemetry.enabled }}
|
||||
|
||||
# OpenTelemetry tracing: the stage continues the engine's dispatch span
|
||||
# (the run's traceparent) and follows its sampling decision.
|
||||
- name: OTEL_EXPORTER_OTLP_ENDPOINT
|
||||
value: "{{ $root.Values.opentelemetry.collector.endpoint }}"
|
||||
{{- include "hub.opentelemetry.samplerEnv" $root | trim | nindent 8 }}
|
||||
{{- end }}
|
||||
|
||||
# Per-stage tuning knobs. Any key/value under services.<name>.env is
|
||||
# rendered verbatim as container env, so a worker can be tuned from
|
||||
|
||||
@@ -23,6 +23,10 @@ spec:
|
||||
labels:
|
||||
app: hub-workflows
|
||||
spec:
|
||||
{{- with .Values.kerberoshub.services.workflows.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 }}
|
||||
@@ -48,6 +52,10 @@ spec:
|
||||
- name: hub-workflows
|
||||
image: "{{ .Values.global.imageRegistry }}{{ .Values.kerberoshub.services.workflows.repository }}:{{ .Values.kerberoshub.services.workflows.tag }}"
|
||||
imagePullPolicy: {{ .Values.kerberoshub.services.workflows.pullPolicy }}
|
||||
{{- with .Values.kerberoshub.services.workflows.securityContext }}
|
||||
securityContext:
|
||||
{{- toYaml . | nindent 10 }}
|
||||
{{- end }}
|
||||
{{- with .Values.kerberoshub.services.workflows.resources }}
|
||||
resources:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
|
||||
@@ -126,6 +126,10 @@ spec:
|
||||
- name: DOMINANTCOLOR_ENABLED
|
||||
value: "{{ .Values.kerberospipeline.dominantColor.enabled }}"
|
||||
|
||||
# Thumbnail regeneration from classified frames (opt-in)
|
||||
- name: THUMBNAIL_REGENERATION_ENABLED
|
||||
value: {{ .Values.kerberospipeline.analysis.thumbnailRegeneration.enabled | default false | quote }}
|
||||
|
||||
# In-memory cache for hot-path MongoDB lookups
|
||||
- name: DB_CACHE_ENABLED
|
||||
value: {{ .Values.kerberospipeline.analysis.dbCache.enabled | default false | quote }}
|
||||
|
||||
@@ -24,6 +24,10 @@ spec:
|
||||
labels:
|
||||
app: pipe-counting
|
||||
spec:
|
||||
{{- with .Values.kerberospipeline.counting.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 }}
|
||||
@@ -43,6 +47,10 @@ spec:
|
||||
- name: pipe-counting
|
||||
image: "{{ .Values.global.imageRegistry }}{{ .Values.kerberospipeline.counting.repository }}:{{ .Values.kerberospipeline.counting.tag }}"
|
||||
imagePullPolicy: {{ .Values.kerberospipeline.counting.pullPolicy }}
|
||||
{{- with .Values.kerberospipeline.counting.securityContext }}
|
||||
securityContext:
|
||||
{{- toYaml . | nindent 10 }}
|
||||
{{- end }}
|
||||
{{- with .Values.kerberospipeline.counting.resources }}
|
||||
resources:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
|
||||
@@ -24,6 +24,10 @@ spec:
|
||||
labels:
|
||||
app: pipe-dominantcolor
|
||||
spec:
|
||||
{{- with .Values.kerberospipeline.dominantColor.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 }}
|
||||
@@ -43,6 +47,10 @@ spec:
|
||||
- name: pipe-dominantcolor
|
||||
image: "{{ .Values.global.imageRegistry }}{{ .Values.kerberospipeline.dominantColor.repository }}:{{ .Values.kerberospipeline.dominantColor.tag }}"
|
||||
imagePullPolicy: {{ .Values.kerberospipeline.dominantColor.pullPolicy }}
|
||||
{{- with .Values.kerberospipeline.dominantColor.securityContext }}
|
||||
securityContext:
|
||||
{{- toYaml . | nindent 10 }}
|
||||
{{- end }}
|
||||
{{- with .Values.kerberospipeline.dominantColor.resources }}
|
||||
resources:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
|
||||
@@ -26,6 +26,10 @@ spec:
|
||||
labels:
|
||||
app: pipe-notify
|
||||
spec:
|
||||
{{- with .Values.kerberospipeline.notify.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 }}
|
||||
@@ -51,6 +55,10 @@ spec:
|
||||
- name: pipe-notify
|
||||
image: "{{ .Values.global.imageRegistry }}{{ .Values.kerberospipeline.notify.repository }}:{{ .Values.kerberospipeline.notify.tag }}"
|
||||
imagePullPolicy: {{ .Values.kerberospipeline.notify.pullPolicy }}
|
||||
{{- with .Values.kerberospipeline.notify.securityContext }}
|
||||
securityContext:
|
||||
{{- toYaml . | nindent 10 }}
|
||||
{{- end }}
|
||||
{{- with .Values.kerberospipeline.notify.resources }}
|
||||
resources:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
|
||||
@@ -69,6 +69,8 @@ spec:
|
||||
name: mongodb-config
|
||||
env:
|
||||
{{- include "hub.mongodb.appUriEnv" (dict "root" $ "app" "hub-pipeline-sequence") | nindent 8 }}
|
||||
- name: LOG_LEVEL
|
||||
value: "{{ .Values.kerberospipeline.sequence.logLevel }}"
|
||||
- name: CLOUD_PROVIDER
|
||||
value: "{{ .Values.cloudProvider }}"
|
||||
- name: QUEUE_SYSTEM
|
||||
|
||||
@@ -26,6 +26,10 @@ spec:
|
||||
labels:
|
||||
app: pipe-throttler
|
||||
spec:
|
||||
{{- with .Values.kerberospipeline.throttler.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 }}
|
||||
@@ -51,6 +55,10 @@ spec:
|
||||
- name: pipe-throttler
|
||||
image: "{{ .Values.global.imageRegistry }}{{ .Values.kerberospipeline.throttler.repository }}:{{ .Values.kerberospipeline.throttler.tag }}"
|
||||
imagePullPolicy: {{ .Values.kerberospipeline.throttler.pullPolicy }}
|
||||
{{- with .Values.kerberospipeline.throttler.securityContext }}
|
||||
securityContext:
|
||||
{{- toYaml . | nindent 10 }}
|
||||
{{- end }}
|
||||
{{- with .Values.kerberospipeline.throttler.resources }}
|
||||
resources:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
|
||||
@@ -24,6 +24,10 @@ spec:
|
||||
labels:
|
||||
app: pipe-thumbnail
|
||||
spec:
|
||||
{{- with .Values.kerberospipeline.thumbnail.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 }}
|
||||
@@ -43,6 +47,10 @@ spec:
|
||||
- name: pipe-thumbnail
|
||||
image: "{{ .Values.global.imageRegistry }}{{ .Values.kerberospipeline.thumbnail.repository }}:{{ .Values.kerberospipeline.thumbnail.tag }}"
|
||||
imagePullPolicy: {{ .Values.kerberospipeline.thumbnail.pullPolicy }}
|
||||
{{- with .Values.kerberospipeline.thumbnail.securityContext }}
|
||||
securityContext:
|
||||
{{- toYaml . | nindent 10 }}
|
||||
{{- end }}
|
||||
{{- with .Values.kerberospipeline.thumbnail.resources }}
|
||||
resources:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
@@ -99,6 +107,8 @@ spec:
|
||||
value: "{{ .Values.kerberospipeline.thumbnail.width }}"
|
||||
- name: THUMBNAIL_HEIGHT
|
||||
value: "{{ .Values.kerberospipeline.thumbnail.height }}"
|
||||
- name: THUMBNAIL_SKIP_NON_KEYFRAMES
|
||||
value: "{{ .Values.kerberospipeline.thumbnail.skipNonKeyframes }}"
|
||||
|
||||
# There is an option to store the thumbnails in a storage provider in Kerberos Vault.
|
||||
# This will lower storage in mongodb.
|
||||
|
||||
@@ -8,8 +8,19 @@
|
||||
"pattern": "^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$"
|
||||
},
|
||||
"classificationCatalog": {
|
||||
"not": {},
|
||||
"description": "Removed in 0.155.0. Store the complete catalog in a ConfigMap and use workflowConfiguration.configMaps with replace: true at catalogs/classifications.json."
|
||||
"type": ["array", "null"],
|
||||
"minItems": 1,
|
||||
"description": "Optional inline classification catalog in the Hub shared choice catalog format; replaces the bundled defaults in the chart's classification-catalog ConfigMap. Omit (or null) for the bundled defaults. Keys must be unique (checked when rendering).",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["key", "label"],
|
||||
"properties": {
|
||||
"key": {"type": "string", "minLength": 1},
|
||||
"label": {"type": "string", "minLength": 1},
|
||||
"icon": {"type": "string", "minLength": 1}
|
||||
}
|
||||
}
|
||||
},
|
||||
"deviceTags": {
|
||||
"type": "object",
|
||||
@@ -51,12 +62,51 @@
|
||||
"api": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"passkeys": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"enabled": {"type": "boolean"},
|
||||
"rpId": {
|
||||
"type": "string",
|
||||
"description": "Stable WebAuthn relying-party hostname, without scheme, port or path. Must match each frontend origin's hostname or be its parent domain."
|
||||
},
|
||||
"origins": {
|
||||
"type": "array",
|
||||
"uniqueItems": true,
|
||||
"items": {
|
||||
"type": "string",
|
||||
"pattern": "^https://[^/?#]+$|^http://localhost(:[0-9]+)?$"
|
||||
},
|
||||
"description": "Exact trusted frontend origins, with no path or trailing slash. HTTPS is required except for HTTP localhost development."
|
||||
},
|
||||
"rpName": {"type": "string"}
|
||||
},
|
||||
"if": {
|
||||
"required": ["enabled"],
|
||||
"properties": {"enabled": {"const": true}}
|
||||
},
|
||||
"then": {
|
||||
"required": ["rpId", "origins"],
|
||||
"properties": {
|
||||
"rpId": {"minLength": 1},
|
||||
"origins": {"minItems": 1}
|
||||
}
|
||||
}
|
||||
},
|
||||
"corsAllowedOrigins": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"pattern": "^https?://[^/?#]+$"
|
||||
}
|
||||
},
|
||||
"auditExportMaxEventsPerFile": {
|
||||
"type": ["integer", "string"],
|
||||
"minimum": 1,
|
||||
"maximum": 1000000,
|
||||
"pattern": "^([1-9][0-9]{0,5}|1000000)?$",
|
||||
"description": "Most audit events one export file holds (AUDIT_EXPORT_MAX_EVENTS_PER_FILE). Empty uses the API default of 100000."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+190
-38
@@ -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.
|
||||
@@ -82,8 +80,15 @@ classificationCatalogEnabled: true
|
||||
# When set, do not create a catalog ConfigMap; this replaces bundled defaults.
|
||||
classificationCatalogExistingConfigMap: ""
|
||||
# Defaults live in templates/configMaps/catalogs/classifications.yaml.
|
||||
# Inline classificationCatalog values are no longer accepted (including null/[]).
|
||||
# Use a ConfigMap item with path: catalogs/classifications.json and replace: true.
|
||||
# Optionally define the complete catalog inline instead; it replaces the
|
||||
# defaults in the chart's classification-catalog ConfigMap (and so the bundled
|
||||
# catalogs/classifications.json). Entries are {key, label, icon?} with unique
|
||||
# keys. Do not combine it with classificationCatalogExistingConfigMap or a
|
||||
# catalogs/classifications.json replacement in workflowConfiguration.
|
||||
# classificationCatalog:
|
||||
# - key: car
|
||||
# label: Car
|
||||
# icon: car
|
||||
|
||||
# Device tags, grouped by type. System tags are defined here and assigned by
|
||||
# owners (a multi-select on the device page); global workflows select devices
|
||||
@@ -108,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:
|
||||
@@ -121,7 +125,7 @@ workflowConfiguration:
|
||||
# Replacement requires includeDefaults: true and a known bundled destination path.
|
||||
# Unmarked collisions and repeated custom destinations are rejected.
|
||||
# Replacing catalogs/classifications.json also supplies the classification API:
|
||||
# do not combine it with classificationCatalogExistingConfigMap.
|
||||
# do not combine it with classificationCatalogExistingConfigMap or classificationCatalog.
|
||||
# Legacy existingConfigMap/items/extraConfigMaps remain supported, but cannot
|
||||
# be mixed with includeDefaults/configMaps. Both interfaces are omitted here
|
||||
# so Helm's default merging does not introduce a false mixed configuration.
|
||||
@@ -202,17 +206,27 @@ opentelemetry:
|
||||
# the collector terminates TLS. A scheme-less value defaults to TLS and
|
||||
# fails against a plaintext collector ("server gave HTTP response to HTTPS client").
|
||||
endpoint: "http://otel-collector:4318"
|
||||
# Trace sampling, passed to the services as OTEL_TRACES_SAMPLER and
|
||||
# OTEL_TRACES_SAMPLER_ARG. Leave empty to keep the SDK default, which records
|
||||
# every trace. Example: keep ~10% of traces, and follow the parent's decision
|
||||
# for spans that continue an upstream trace:
|
||||
# type: "parentbased_traceidratio"
|
||||
# arg: "0.1"
|
||||
# Supported types: always_on, always_off, traceidratio, parentbased_always_on,
|
||||
# parentbased_always_off, parentbased_traceidratio.
|
||||
sampler:
|
||||
type: ""
|
||||
arg: ""
|
||||
# Trace sampling is decided where a trace starts:
|
||||
# - Uploads start at the Vault, whose deployment sets their rate
|
||||
# (TRACE_SAMPLER_RATIO_UPLOAD / TRACE_SAMPLER_RATIO_OTHER).
|
||||
# - hub-api starts its own traces; apiSamplingRatios below sets its rates.
|
||||
# hub-workflows, the pipe-* services and the workflow stages only continue
|
||||
# traces started elsewhere: they follow that decision and never start traces
|
||||
# of their own (OTEL_TRACES_SAMPLER=parentbased_always_off, not configurable).
|
||||
#
|
||||
# apiSamplingRatios: hub-api's sampling ratio (0 to 1) per kind of work,
|
||||
# rendered as TRACE_SAMPLER_RATIO_<CLASS> (hub-api on trace library v1.2.6+).
|
||||
# Empty values are not rendered; with none set, hub-api records every trace.
|
||||
# operator: requests from users and API clients (UI, API tokens).
|
||||
# other: everything else hub-api starts: device heartbeats, agent uploads
|
||||
# and live segments, background work, queries outside a request.
|
||||
# Unset with operator set: records everything.
|
||||
# Example: ~10% of user requests, 1% of everything else:
|
||||
# operator: "0.1"
|
||||
# other: "0.01"
|
||||
apiSamplingRatios:
|
||||
operator: ""
|
||||
other: ""
|
||||
############################################
|
||||
# OpenAI configuration (semantic search)
|
||||
#
|
||||
@@ -251,6 +265,11 @@ kerberosvault:
|
||||
###########################################################################
|
||||
admin:
|
||||
repository: uugai/admin
|
||||
# Opt in to 8080 and a securityContext only with a compatible hardened image.
|
||||
containerPort: 80
|
||||
securityContext: {}
|
||||
readinessProbe: {}
|
||||
livenessProbe: {}
|
||||
pullPolicy: IfNotPresent
|
||||
tag: "v1.3.0"
|
||||
replicas: 2
|
||||
@@ -317,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).
|
||||
@@ -353,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
|
||||
@@ -375,6 +414,13 @@ kerberoshub:
|
||||
|
||||
# MFA issuer name
|
||||
mfaIssuer: "yourdomain.com"
|
||||
# WebAuthn passkeys for users and sub-users; requires compatible API/frontend images.
|
||||
# Use the public frontend hostname/origins, not the API URL. Keep rpId stable.
|
||||
passkeys:
|
||||
enabled: false
|
||||
rpId: "" # e.g. "hub.example.com", without scheme, port or path.
|
||||
origins: [] # e.g. ["https://hub.example.com"]; exact origins, no trailing slash.
|
||||
rpName: "Kerberos Hub"
|
||||
# Admin API's are made available for automation of Kerberos Hub.
|
||||
# To access those API's (e.g. creation of owner users), an API key needs to be provided.
|
||||
apiKey: "a-random-admin-api-key"
|
||||
@@ -390,6 +436,11 @@ kerberoshub:
|
||||
# beyond this cap, the value is clamped down to `now + casesMaxRetentionDays`.
|
||||
# Set to "0" (or a negative value) to disable the cap (no maximum).
|
||||
casesMaxRetentionDays: "0"
|
||||
# Most audit events one export file holds (AUDIT_EXPORT_MAX_EVENTS_PER_FILE).
|
||||
# It does not refuse larger exports: the Audit page downloads them as
|
||||
# several files of at most this size. An integer from 1 to 1000000; leave
|
||||
# empty to use the API default of 100000.
|
||||
auditExportMaxEventsPerFile: ""
|
||||
## Certificates
|
||||
tls:
|
||||
- hosts:
|
||||
@@ -448,8 +499,28 @@ kerberoshub:
|
||||
frontend:
|
||||
repository: ghcr.io/uug-ai/hub-frontend
|
||||
pullPolicy: IfNotPresent
|
||||
tag: "v1.13.9"
|
||||
tag: "v1.19.28"
|
||||
replicas: 2
|
||||
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).
|
||||
@@ -623,6 +694,13 @@ kerberoshub:
|
||||
sitesFullscreenEnabled: "true" # Enable or disable the fullscreen Sites map 'true' or 'false'
|
||||
# Media page
|
||||
media:
|
||||
# Bulk "Forward media" action in the Recordings Actions menu (on-demand forwarding of metadata-only recordings).
|
||||
forward:
|
||||
enabled: "true" # Enable or disable the Forward media action in media 'true' or 'false'
|
||||
# Analysis block (dominant colors, thumbnail, sprite, motion) on the media detail page.
|
||||
detail:
|
||||
analysis:
|
||||
enabled: "true" # Enable or disable the Analysis block on the media detail page 'true' or 'false'
|
||||
filter:
|
||||
date:
|
||||
enabled: "true" # Enable or disable date filter in media 'true' or 'false'
|
||||
@@ -727,9 +805,12 @@ kerberoshub:
|
||||
organization: "github-organization"
|
||||
team: "github-team"
|
||||
cleanup:
|
||||
# Explicit opt-in for a selected hardened image; legacy defaults are unchanged.
|
||||
securityContext: {}
|
||||
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).
|
||||
@@ -777,12 +858,13 @@ kerberoshub:
|
||||
requests:
|
||||
memory: 10Mi
|
||||
cpu: 10m
|
||||
# Dispatches canonical audit_events to one or more external webhook sinks.
|
||||
# Dispatches canonical audit_events to one or more destinations: webhook
|
||||
# sinks, or S3 Object Lock buckets for immutable storage.
|
||||
# Credentials are always read from existing Secrets and never stored here.
|
||||
audit:
|
||||
repository: ghcr.io/uug-ai/hub-audit
|
||||
pullPolicy: IfNotPresent
|
||||
tag: "v1.0.1"
|
||||
tag: "v1.0.10"
|
||||
replicas: 1
|
||||
dispatchInterval: "5s"
|
||||
leaseDuration: "2m"
|
||||
@@ -791,6 +873,16 @@ kerberoshub:
|
||||
volumes: []
|
||||
volumeMounts: []
|
||||
extraEnv: []
|
||||
# Optional ServiceAccount for hub-audit alone, for example to grant S3 access
|
||||
# through IRSA without giving it to every Hub workload. When create is false
|
||||
# and name is set, that existing ServiceAccount is used; otherwise the pod
|
||||
# falls back to kerberoshub.serviceAccount.
|
||||
serviceAccount:
|
||||
create: false
|
||||
name: ""
|
||||
annotations: {}
|
||||
# eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/hub-audit-archive
|
||||
labels: {}
|
||||
serviceMonitor:
|
||||
enabled: true
|
||||
interval: 15s
|
||||
@@ -818,6 +910,35 @@ kerberoshub:
|
||||
# actions: []
|
||||
# categories: []
|
||||
# outcomes: [] # success | failure
|
||||
# S3 Object Lock destination (hub-audit v1.0.9 or later). The bucket needs
|
||||
# versioning, Object Lock and a COMPLIANCE default retention; hub-audit
|
||||
# refuses to deliver otherwise. Each batch is written as
|
||||
# <prefix>/<id>/yyyy/mm/dd/<firstId>-<lastId>.ndjson.gz. Per-provider
|
||||
# region/endpoint values (AWS, Wasabi, B2, Ceph RGW, MinIO, Garage):
|
||||
# https://learn.uug.ai/docs/hub/audit-events/#s3-providers
|
||||
# - id: immutable-archive
|
||||
# type: s3
|
||||
# enabled: true
|
||||
# timeoutSeconds: 30
|
||||
# batchSize: 500
|
||||
# maxAttempts: 100 # keep high: exhausted batches are dead-lettered, not archived
|
||||
# startAt: beginning
|
||||
# s3:
|
||||
# bucket: hub-audit-archive
|
||||
# region: eu-west-1
|
||||
# prefix: hub
|
||||
# endpoint: https://s3.example.com # S3-compatible stores only; omit for AWS S3
|
||||
# forcePathStyle: true # usually needed for Ceph RGW, MinIO and Garage
|
||||
# # Only for stores without Object Lock (e.g. Garage): skips the lock
|
||||
# # check, so archived batches are deletable. Needs hub-audit v1.0.10.
|
||||
# allowUnlockedBucket: false
|
||||
# # Omit credentialsSecret to use the pod's AWS identity (IRSA via
|
||||
# # kerberoshub.audit.serviceAccount, or an instance profile). That only
|
||||
# # works for AWS S3; every other store needs credentialsSecret.
|
||||
# credentialsSecret:
|
||||
# name: audit-immutable-archive
|
||||
# accessKeyIdKey: access-key-id
|
||||
# secretAccessKeyKey: secret-access-key
|
||||
# resources:
|
||||
# requests:
|
||||
# memory: 32Mi
|
||||
@@ -940,6 +1061,8 @@ kerberoshub:
|
||||
# `enabled` here.
|
||||
workflows:
|
||||
repository: ghcr.io/uug-ai/hub-workflows
|
||||
securityContext: {}
|
||||
podSecurityContext: {}
|
||||
pullPolicy: IfNotPresent
|
||||
tag: "v1.0.0"
|
||||
replicas: 1 # Number of pods for the service.
|
||||
@@ -1048,12 +1171,19 @@ kerberoshub:
|
||||
# memory: 128Mi
|
||||
# cpu: 100m
|
||||
monitordevice:
|
||||
securityContext: {}
|
||||
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
|
||||
# Seconds between device status scans. Sets how quickly a camera going
|
||||
# offline/online is detected. With "Group notifications" on, hub-monitor-device
|
||||
# v1.4.10+ holds a change 20 seconds (DEVICE_GROUPING_HOLD_SECONDS) to collect
|
||||
# other cameras that change at the same time; older releases hold one scan.
|
||||
scanIntervalSeconds: 60
|
||||
resources:
|
||||
requests:
|
||||
memory: 10Mi
|
||||
@@ -1133,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).
|
||||
@@ -1154,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).
|
||||
@@ -1175,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).
|
||||
@@ -1185,17 +1315,20 @@ kerberospipeline:
|
||||
#volumeMounts:
|
||||
# - name: extra
|
||||
# mountPath: /data
|
||||
logLevel: "info" # possible values: debug, info, warn, error
|
||||
resources:
|
||||
requests:
|
||||
memory: 10Mi
|
||||
cpu: 10m
|
||||
throttler:
|
||||
repository: uugai/hub-pipeline-throttler
|
||||
securityContext: {}
|
||||
podSecurityContext: {}
|
||||
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).
|
||||
@@ -1211,12 +1344,14 @@ kerberospipeline:
|
||||
memory: 10Mi
|
||||
cpu: 10m
|
||||
notify:
|
||||
securityContext: {}
|
||||
podSecurityContext: {}
|
||||
repository: ghcr.io/uug-ai/hub-pipeline-notification
|
||||
deploymentAnnotations: {}
|
||||
# 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
|
||||
@@ -1236,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:
|
||||
@@ -1259,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).
|
||||
@@ -1270,6 +1405,12 @@ kerberospipeline:
|
||||
# - name: extra
|
||||
# mountPath: /data
|
||||
logLevel: "info" # possible values: trace, debug, info, warn, error
|
||||
# Opt-in: after classification, re-enqueue the recording on the thumbnail
|
||||
# queue to regenerate its thumbnail from the first frame of a tracked moving
|
||||
# object (THUMBNAIL_REGENERATION_ENABLED). Adds one thumbnail job per
|
||||
# classified recording; size pipe-thumbnail accordingly before enabling.
|
||||
thumbnailRegeneration:
|
||||
enabled: false
|
||||
# In-memory cache for hot-path MongoDB lookups (site vault per device).
|
||||
# Site changes apply after at most one TTL; each replica caches separately.
|
||||
dbCache:
|
||||
@@ -1280,6 +1421,8 @@ kerberospipeline:
|
||||
memory: 10Mi
|
||||
cpu: 10m
|
||||
dominantColor:
|
||||
securityContext: {}
|
||||
podSecurityContext: {}
|
||||
# Opt-in: deploys pipe-dominantcolor and tells the analysis stage to schedule
|
||||
# it (DOMINANTCOLOR_ENABLED). Requires an analysis image that reads the flag;
|
||||
# older images always publish to the dominantcolor queue.
|
||||
@@ -1289,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).
|
||||
@@ -1308,12 +1451,14 @@ kerberospipeline:
|
||||
memory: 2Gi
|
||||
cpu: 1000m
|
||||
thumbnail:
|
||||
securityContext: {}
|
||||
podSecurityContext: {}
|
||||
repository: ghcr.io/uug-ai/hub-pipeline-thumbnail
|
||||
deploymentAnnotations: {}
|
||||
# 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).
|
||||
@@ -1328,6 +1473,7 @@ kerberospipeline:
|
||||
quality: "1" # 1 (best) - 31 (worst)
|
||||
width: "600"
|
||||
height: "-1"
|
||||
skipNonKeyframes: true # Decode keyframes only for faster extraction; disable when exact-frame output is required.
|
||||
kerberosvault:
|
||||
enabled: true # If you want to use Kerberos Vault to store the thumbnails
|
||||
resources:
|
||||
@@ -1338,12 +1484,14 @@ kerberospipeline:
|
||||
memory: 2Gi
|
||||
cpu: 1000m
|
||||
counting:
|
||||
repository: uugai/hub-pipeline-counting
|
||||
securityContext: {}
|
||||
podSecurityContext: {}
|
||||
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).
|
||||
@@ -1365,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).
|
||||
@@ -1392,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
|
||||
@@ -1445,5 +1593,9 @@ email:
|
||||
disabled: "disable"
|
||||
highupload: "highupload"
|
||||
device: "device"
|
||||
# Sent instead of "device" when several cameras of an account change status
|
||||
# in the same scan and the account groups device notifications.
|
||||
deviceGroup: "device_group"
|
||||
alertTitle: "[Alert] Kerberos Hub detected something an event"
|
||||
deviceTitle: "[Device] A Kerberos Agent's status has been changed"
|
||||
deviceGroupTitle: "[Device] Several cameras changed status"
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
CHART_DIR="${1:-charts/hub}"
|
||||
|
||||
resource() {
|
||||
local source="$1"
|
||||
awk -v source="# Source: hub/templates/admin/$source.yaml" '
|
||||
/^---/ { selected=0 }
|
||||
/^# Source:/ { selected=($0 == source); next }
|
||||
selected { print }
|
||||
'
|
||||
}
|
||||
|
||||
assert_contains() {
|
||||
local rendered="$1" pattern="$2"
|
||||
if ! grep -Eq -- "$pattern" <<< "$rendered"; then
|
||||
printf 'Missing expected Admin setting: %s\n' "$pattern" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
assert_absent() {
|
||||
local rendered="$1" pattern="$2"
|
||||
if grep -Eq -- "$pattern" <<< "$rendered"; then
|
||||
printf 'Unexpected Admin setting: %s\n' "$pattern" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
HARDENED_FLAGS=(
|
||||
--set admin.containerPort=8080
|
||||
--set-json 'admin.securityContext={"runAsNonRoot":true,"runAsUser":65532,"runAsGroup":65532,"readOnlyRootFilesystem":true,"allowPrivilegeEscalation":false,"capabilities":{"drop":["ALL"]},"seccompProfile":{"type":"RuntimeDefault"}}'
|
||||
--set-json 'admin.volumes=[{"name":"admin-tmp","emptyDir":{"medium":"Memory","sizeLimit":"16Mi"}}]'
|
||||
--set-json 'admin.volumeMounts=[{"name":"admin-tmp","mountPath":"/tmp"}]'
|
||||
--set-json 'admin.readinessProbe={"httpGet":{"path":"/healthz","port":"http"},"periodSeconds":10}'
|
||||
--set-json 'admin.livenessProbe={"httpGet":{"path":"/healthz","port":"http"},"initialDelaySeconds":10,"periodSeconds":10}'
|
||||
)
|
||||
|
||||
for mode in all ui; do
|
||||
default_render="$(helm template hub "$CHART_DIR" --set "mode=$mode" --set admin.oauth2Proxy.enabled=true)"
|
||||
default_deployment="$(resource deployment <<< "$default_render")"
|
||||
default_service="$(resource service <<< "$default_render")"
|
||||
default_ingress="$(resource ingress <<< "$default_render")"
|
||||
default_oauth="$(resource oauth2-proxy <<< "$default_render")"
|
||||
assert_contains "$default_deployment" 'containerPort: 80$'
|
||||
assert_absent "$default_deployment" 'securityContext:|readinessProbe:|livenessProbe:'
|
||||
assert_contains "$default_service" 'port: 80$'
|
||||
assert_contains "$default_service" 'targetPort: http$'
|
||||
|
||||
hardened_render="$(helm template hub "$CHART_DIR" --set "mode=$mode" --set admin.oauth2Proxy.enabled=true "${HARDENED_FLAGS[@]}")"
|
||||
hardened_deployment="$(resource deployment <<< "$hardened_render")"
|
||||
assert_contains "$hardened_deployment" 'containerPort: 8080$'
|
||||
assert_absent "$hardened_deployment" 'containerPort: 80$'
|
||||
for setting in 'runAsNonRoot: true' 'runAsUser: 65532' 'runAsGroup: 65532' \
|
||||
'readOnlyRootFilesystem: true' 'allowPrivilegeEscalation: false' \
|
||||
'type: RuntimeDefault' 'drop:' '- ALL' 'medium: Memory' \
|
||||
'sizeLimit: 16Mi' 'mountPath: /tmp' 'name: admin-tmp'; do
|
||||
assert_contains "$hardened_deployment" "^[[:space:]]*(- )?$setting$"
|
||||
done
|
||||
for probe in readinessProbe livenessProbe; do
|
||||
probe_render="$(awk -v probe="$probe:" '
|
||||
/^ [a-zA-Z]/ { selected=($1 == probe) }
|
||||
selected { print }
|
||||
' <<< "$hardened_deployment")"
|
||||
assert_contains "$probe_render" 'path: /healthz$'
|
||||
assert_contains "$probe_render" 'port: http$'
|
||||
done
|
||||
|
||||
[[ "$(resource service <<< "$hardened_render")" == "$default_service" ]]
|
||||
[[ "$(resource ingress <<< "$hardened_render")" == "$default_ingress" ]]
|
||||
[[ "$(resource oauth2-proxy <<< "$hardened_render")" == "$default_oauth" ]]
|
||||
assert_contains "$default_ingress" 'nginx.ingress.kubernetes.io/auth-url:'
|
||||
assert_contains "$default_ingress" 'number: 80$'
|
||||
|
||||
echo "OK ($mode): legacy defaults; hardened runtime/probes; stable Service, ingress and OAuth2"
|
||||
done
|
||||
|
||||
pipeline_render="$(helm template hub "$CHART_DIR" --set mode=pipeline "${HARDENED_FLAGS[@]}")"
|
||||
assert_absent "$pipeline_render" '^# Source: hub/templates/admin/'
|
||||
|
||||
echo "All Admin runtime checks passed."
|
||||
@@ -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."
|
||||
@@ -0,0 +1,194 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
CHART_DIR="${1:-charts/hub}"
|
||||
components=(
|
||||
kerberospipeline.counting kerberospipeline.throttler
|
||||
kerberospipeline.notify
|
||||
kerberospipeline.thumbnail kerberospipeline.dominantColor
|
||||
kerberoshub.cleanup kerberoshub.monitordevice
|
||||
kerberoshub.services.workflows kerberoshub.services.loitering
|
||||
)
|
||||
fixtures=(
|
||||
--set kerberospipeline.dominantColor.enabled=true
|
||||
--set kerberoshub.workflows.enabled=true
|
||||
--set kerberoshub.services.loitering.enabled=true
|
||||
--set kerberoshub.services.loitering.repository=example/loitering
|
||||
--set kerberoshub.services.loitering.tag=test
|
||||
--set kerberoshub.services.loitering.queue=loitering
|
||||
--set mongodb.tls.enabled=true
|
||||
--set mongodb.tls.existingSecret=mongodb-ca
|
||||
--set mongodb.tls.caFileName=ca.crt
|
||||
)
|
||||
security=()
|
||||
for component in "${components[@]}"; do
|
||||
security+=(
|
||||
--set "$component.securityContext.runAsNonRoot=true"
|
||||
--set "$component.securityContext.runAsUser=65532"
|
||||
--set "$component.securityContext.runAsGroup=65532"
|
||||
--set "$component.securityContext.readOnlyRootFilesystem=true"
|
||||
--set "$component.securityContext.allowPrivilegeEscalation=false"
|
||||
--set "$component.securityContext.capabilities.drop[0]=ALL"
|
||||
--set "$component.securityContext.seccompProfile.type=RuntimeDefault"
|
||||
--set "$component.podSecurityContext.fsGroup=65532"
|
||||
--set "$component.volumes[0].name=scratch"
|
||||
--set "$component.volumes[0].emptyDir.sizeLimit=4Gi"
|
||||
--set "$component.volumeMounts[0].name=scratch"
|
||||
--set "$component.volumeMounts[0].mountPath=/tmp"
|
||||
--set "$component.resources.requests.ephemeral-storage=4Gi"
|
||||
--set "$component.resources.limits.ephemeral-storage=6Gi"
|
||||
)
|
||||
done
|
||||
|
||||
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() {
|
||||
local block="$1" pattern="$2"
|
||||
if ! grep -Eq "$pattern" <<< "$block"; then
|
||||
printf 'Missing expected pattern: %s\n%s\n' "$pattern" "$block" >&2
|
||||
exit 1
|
||||
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")"
|
||||
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
|
||||
|
||||
legacy="$(helm template hub "$CHART_DIR" --set "mode=$mode" "${fixtures[@]}")"
|
||||
hardened="$(helm template hub "$CHART_DIR" --set "mode=$mode" "${fixtures[@]}" "${security[@]}")"
|
||||
helm lint "$CHART_DIR" --quiet --set "mode=$mode" "${fixtures[@]}" "${security[@]}"
|
||||
names=()
|
||||
if [[ "$mode" != ui ]]; then
|
||||
names+=(pipe-counting pipe-throttler pipe-notify pipe-thumbnail pipe-dominantcolor hub-workflows hub-loitering)
|
||||
fi
|
||||
if [[ "$mode" != pipeline ]]; then
|
||||
names+=(hub-cleanup hub-monitor-device)
|
||||
fi
|
||||
for name in "${names[@]}"; do
|
||||
block="$(resource Deployment "$name" <<< "$hardened")"
|
||||
for pattern in \
|
||||
'^[ ]{6}securityContext:$' '^[ ]{8}fsGroup: 65532$' \
|
||||
'^[ ]+(runAsUser|runAsGroup): 65532$' \
|
||||
'^[ ]+runAsNonRoot: true$' '^[ ]+readOnlyRootFilesystem: true$' \
|
||||
'^[ ]+allowPrivilegeEscalation: false$' '^[ ]+capabilities:$' \
|
||||
'^[ ]+drop:$' '^[ ]+- ALL$' '^[ ]+seccompProfile:$' \
|
||||
'^[ ]+type: RuntimeDefault$' '^[ ]+(- )?mountPath: /tmp$' \
|
||||
'^[ ]+sizeLimit: 4Gi$' '^[ ]+ephemeral-storage: 6Gi$' \
|
||||
'^[ ]+ephemeral-storage: 4Gi$'; do
|
||||
assert_line "$block" "$pattern"
|
||||
done
|
||||
# Verify both identity axes independently.
|
||||
assert_line "$block" '^[ ]+runAsUser: 65532$'
|
||||
assert_line "$block" '^[ ]+runAsGroup: 65532$'
|
||||
assert_line "$block" '^[ ]{8,10}securityContext:$'
|
||||
if [[ "$name" == hub-cleanup || "$name" == hub-monitor-device || "$name" == hub-workflows || "$name" == pipe-notify ]]; then
|
||||
assert_line "$block" '^[ ]+secretName: mongodb-ca$'
|
||||
assert_line "$block" '^[ ]+mountPath: /etc/mongodb/tls$'
|
||||
assert_line "$block" '^[ ]+readOnly: true$'
|
||||
fi
|
||||
if [[ "$name" == pipe-notify ]]; then
|
||||
# Provider, queue, FFmpeg and all other worker settings are unchanged.
|
||||
old_block="$(resource Deployment pipe-notify <<< "$legacy")"
|
||||
[[ "$(sed -n '/^ env:/,$p' <<< "$old_block")" == "$(sed -n '/^ env:/,$p' <<< "$block")" ]]
|
||||
fi
|
||||
if [[ "$name" != hub-monitor-device ]]; then
|
||||
service="$(resource Service "$name" <<< "$hardened")"
|
||||
assert_line "$service" '^[ ]+port: 8080$'
|
||||
assert_line "$service" '^[ ]+targetPort: 8080$'
|
||||
fi
|
||||
done
|
||||
expected="${#names[@]}"
|
||||
[[ "$(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
|
||||
[[ "$(grep -E "$pattern" <<< "$legacy")" == "$(grep -E "$pattern" <<< "$hardened")" ]]
|
||||
done
|
||||
echo "OK ($mode): defaults unchanged; $expected independent hardened workers preserve images, ports and MongoDB TLS"
|
||||
done
|
||||
|
||||
# Hardening one worker must not opt in another, even on the same pod mode.
|
||||
isolated="$(helm template hub "$CHART_DIR" --set mode=pipeline \
|
||||
--set kerberospipeline.counting.securityContext.runAsUser=65532)"
|
||||
[[ "$(grep -c 'runAsUser: 65532' <<< "$isolated")" == 1 ]]
|
||||
assert_line "$(resource Deployment pipe-counting <<< "$isolated")" '^[ ]+runAsUser: 65532$'
|
||||
echo "All hardened worker checks passed."
|
||||
|
||||
admin_flags=(
|
||||
--set admin.containerPort=8080
|
||||
--set admin.securityContext.runAsNonRoot=true
|
||||
--set admin.securityContext.runAsUser=65532
|
||||
--set admin.securityContext.runAsGroup=65532
|
||||
--set admin.securityContext.readOnlyRootFilesystem=true
|
||||
--set admin.securityContext.allowPrivilegeEscalation=false
|
||||
--set admin.securityContext.capabilities.drop[0]=ALL
|
||||
--set admin.securityContext.seccompProfile.type=RuntimeDefault
|
||||
--set admin.volumes[0].name=admin-tmp
|
||||
--set admin.volumes[0].emptyDir.medium=Memory
|
||||
--set admin.volumes[0].emptyDir.sizeLimit=16Mi
|
||||
--set admin.volumeMounts[0].name=admin-tmp
|
||||
--set admin.volumeMounts[0].mountPath=/tmp
|
||||
--set admin.oauth2Proxy.enabled=true
|
||||
)
|
||||
for mode in all ui pipeline; do
|
||||
legacy="$(helm template hub "$CHART_DIR" --set "mode=$mode" --set admin.oauth2Proxy.enabled=true)"
|
||||
configured="$(helm template hub "$CHART_DIR" --set "mode=$mode" "${admin_flags[@]}")"
|
||||
helm lint "$CHART_DIR" --quiet --set "mode=$mode" "${admin_flags[@]}"
|
||||
if [[ "$mode" == pipeline ]]; then
|
||||
[[ -z "$(resource Deployment admin <<< "$configured")" ]]
|
||||
[[ -z "$(resource Service admin-svc <<< "$configured")" ]]
|
||||
[[ "$legacy" == "$configured" ]]
|
||||
echo "OK (pipeline): Admin settings render no Admin resources or unrelated changes"
|
||||
continue
|
||||
fi
|
||||
old="$(resource Deployment admin <<< "$legacy")"
|
||||
block="$(resource Deployment admin <<< "$configured")"
|
||||
assert_line "$old" '^[ ]+containerPort: 80$'
|
||||
[[ "$old" != *securityContext:* && "$old" != *admin-tmp* ]]
|
||||
for pattern in \
|
||||
'^[ ]+containerPort: 8080$' '^[ ]{10}securityContext:$' \
|
||||
'^[ ]+runAsUser: 65532$' '^[ ]+runAsGroup: 65532$' \
|
||||
'^[ ]+runAsNonRoot: true$' '^[ ]+readOnlyRootFilesystem: true$' \
|
||||
'^[ ]+allowPrivilegeEscalation: false$' '^[ ]+capabilities:$' \
|
||||
'^[ ]+drop:$' '^[ ]+- ALL$' '^[ ]+seccompProfile:$' \
|
||||
'^[ ]+type: RuntimeDefault$' '^[ ]+medium: Memory$' \
|
||||
'^[ ]+sizeLimit: 16Mi$' '^[ ]+(- )?mountPath: /tmp$'; do
|
||||
assert_line "$block" "$pattern"
|
||||
done
|
||||
old_service="$(resource Service admin-svc <<< "$legacy")"
|
||||
service="$(resource Service admin-svc <<< "$configured")"
|
||||
assert_line "$old_service" '^[ ]+targetPort: http$'
|
||||
assert_line "$service" '^[ ]+port: 80$'
|
||||
assert_line "$service" '^[ ]+targetPort: http$'
|
||||
[[ "$(resource Ingress admin-ingress <<< "$legacy")" == "$(resource Ingress admin-ingress <<< "$configured")" ]]
|
||||
[[ "$(resource Deployment oauth2-proxy-admin <<< "$legacy")" == "$(resource Deployment oauth2-proxy-admin <<< "$configured")" ]]
|
||||
[[ "$(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.
|
||||
[[ "$(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
|
||||
Executable
+130
@@ -0,0 +1,130 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
CHART_DIR="${1:-charts/hub}"
|
||||
|
||||
extract_passkeys() {
|
||||
awk '
|
||||
/^---/ { deployment=0; name=""; variable="" }
|
||||
/^kind: Deployment$/ { deployment=1 }
|
||||
deployment && /^ name:/ { name=$2 }
|
||||
deployment && /- name: PASSKEY_/ { variable=$3; next }
|
||||
variable != "" && /value:/ {
|
||||
value=$0
|
||||
sub(/^[[:space:]]*value: /, "", value)
|
||||
print name, variable, value
|
||||
variable=""
|
||||
}
|
||||
' | sort
|
||||
}
|
||||
|
||||
assert_passkeys() {
|
||||
local rendered="$1" expected="$2" actual count expected_count
|
||||
actual="$(printf '%s\n' "$rendered" | extract_passkeys)"
|
||||
if [[ "$actual" != "$expected" ]]; then
|
||||
printf 'Unexpected passkey environment:\n%s\nExpected:\n%s\n' "$actual" "$expected" >&2
|
||||
return 1
|
||||
fi
|
||||
count="$(printf '%s\n' "$rendered" | awk '/- name: PASSKEY_/ { count++ } END { print count+0 }')"
|
||||
expected_count="$(printf '%s\n' "$expected" | awk 'NF { count++ } END { print count+0 }')"
|
||||
if [[ "$count" != "$expected_count" ]]; then
|
||||
echo "Passkey variables leaked outside the expected Deployment or were duplicated" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
extract_feature() {
|
||||
awk '
|
||||
/^---/ { deployment=0; name=""; flag=0 }
|
||||
/^kind: Deployment$/ { deployment=1 }
|
||||
deployment && /^ name:/ { name=$2 }
|
||||
deployment && /- name: FEATURE_PASSKEYS_ENABLED$/ { flag=1; next }
|
||||
flag && /value:/ {
|
||||
value=$0
|
||||
sub(/^[[:space:]]*value: /, "", value)
|
||||
print name, value
|
||||
flag=0
|
||||
}
|
||||
' | sort
|
||||
}
|
||||
|
||||
assert_feature() {
|
||||
local rendered="$1" expected="$2" actual
|
||||
actual="$(printf '%s\n' "$rendered" | extract_feature)"
|
||||
if [[ "$actual" != "$expected" ]]; then
|
||||
printf 'Unexpected FEATURE_PASSKEYS_ENABLED:\n%s\nExpected:\n%s\n' "$actual" "$expected" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
CONFIGURED_FLAGS=(
|
||||
--set-json kerberoshub.api.passkeys.enabled=true
|
||||
--set-json 'kerberoshub.api.passkeys.rpId="example.com"'
|
||||
--set-json 'kerberoshub.api.passkeys.origins=["https://hub.example.com","https://legacy.example.com:8443"]'
|
||||
--set-json 'kerberoshub.api.passkeys.rpName="Example Hub"'
|
||||
)
|
||||
expected="$(printf '%s\n' \
|
||||
'hub-api PASSKEY_ORIGINS "https://hub.example.com,https://legacy.example.com:8443"' \
|
||||
'hub-api PASSKEY_RP_ID "example.com"' \
|
||||
'hub-api PASSKEY_RP_NAME "Example Hub"' | sort)"
|
||||
|
||||
for mode in all ui pipeline; do
|
||||
default_render="$(helm template hub "$CHART_DIR" --set "mode=$mode")"
|
||||
assert_passkeys "$default_render" ""
|
||||
configured_render="$(helm template hub "$CHART_DIR" --set "mode=$mode" "${CONFIGURED_FLAGS[@]}")"
|
||||
if [[ "$mode" == pipeline ]]; then
|
||||
assert_passkeys "$configured_render" ""
|
||||
assert_feature "$default_render" ""
|
||||
assert_feature "$configured_render" ""
|
||||
else
|
||||
assert_passkeys "$configured_render" "$expected"
|
||||
assert_feature "$default_render" 'hub-frontend "false"'
|
||||
assert_feature "$configured_render" "$(printf '%s\n' 'hub-api "true"' 'hub-frontend "true"')"
|
||||
fi
|
||||
echo "OK ($mode): defaults opt out; enabled settings configure Hub API and flag the frontend"
|
||||
done
|
||||
|
||||
disabled_render="$(helm template hub "$CHART_DIR" "${CONFIGURED_FLAGS[@]}" --set kerberoshub.api.passkeys.enabled=false)"
|
||||
assert_passkeys "$disabled_render" ""
|
||||
assert_feature "$disabled_render" 'hub-frontend "false"'
|
||||
|
||||
demo_render="$(helm template hub "$CHART_DIR" "${CONFIGURED_FLAGS[@]}" --set kerberoshub.frontend.demoEnabled=true)"
|
||||
assert_feature "$demo_render" "$(printf '%s\n' 'hub-api "true"' 'hub-frontend "true"' 'hub-frontend-demo "true"')"
|
||||
|
||||
localhost_render="$(helm template hub "$CHART_DIR" --set mode=ui \
|
||||
--set kerberoshub.api.passkeys.enabled=true \
|
||||
--set-string kerberoshub.api.passkeys.rpId=localhost \
|
||||
--set-json 'kerberoshub.api.passkeys.origins=["http://localhost:4200"]')"
|
||||
assert_passkeys "$localhost_render" "$(printf '%s\n' \
|
||||
'hub-api PASSKEY_ORIGINS "http://localhost:4200"' \
|
||||
'hub-api PASSKEY_RP_ID "localhost"' \
|
||||
'hub-api PASSKEY_RP_NAME "Kerberos Hub"' | sort)"
|
||||
|
||||
legacy_render="$(helm template hub "$CHART_DIR" --set mode=ui \
|
||||
--set-json 'kerberoshub.extraEnv=[{"name":"PASSKEY_RP_ID","value":"example.com"}]')"
|
||||
printf '%s\n' "$legacy_render" | grep -q -- '- name: PASSKEY_RP_ID'
|
||||
|
||||
expect_invalid() {
|
||||
local output
|
||||
if output="$(helm template hub "$CHART_DIR" "${CONFIGURED_FLAGS[@]}" "$@" 2>&1)"; then
|
||||
echo "Invalid passkey settings unexpectedly rendered: $*" >&2
|
||||
return 1
|
||||
fi
|
||||
if [[ "$output" != *"passkeys"* ]]; then
|
||||
printf 'Rendering failed for an unexpected reason:\n%s\n' "$output" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
expect_invalid --set-string kerberoshub.api.passkeys.rpId=
|
||||
expect_invalid --set-json 'kerberoshub.api.passkeys.origins=[]'
|
||||
expect_invalid --set-json 'kerberoshub.api.passkeys.origins=["https://hub.example.com/"]'
|
||||
expect_invalid --set-json 'kerberoshub.api.passkeys.origins=["http://hub.example.com"]'
|
||||
expect_invalid --set-json 'kerberoshub.api.passkeys.origins=["https://hub.example.com","https://hub.example.com"]'
|
||||
expect_invalid --set-json 'kerberoshub.api.passkeys.origins="https://hub.example.com"'
|
||||
expect_invalid --set-json 'kerberoshub.api.passkeys.enabled="true"'
|
||||
expect_invalid --set-json 'kerberoshub.api.passkeys.rpId=123'
|
||||
expect_invalid --set-json 'kerberoshub.api.passkeys.rpName=123'
|
||||
expect_invalid --set-string kerberoshub.api.passkeys.origin=https://hub.example.com
|
||||
|
||||
echo "All Hub passkey configuration checks passed."
|
||||
@@ -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