mirror of
https://github.com/kerberos-io/helm-charts.git
synced 2026-10-30 19:28:28 +00:00
Compare commits
20
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1288266666 | ||
|
|
9c1e37aba4 | ||
|
|
b186ce94ba | ||
|
|
584ea33ce6 | ||
|
|
4442c9c201 | ||
|
|
c1e15368c7 | ||
|
|
8d86eefba5 | ||
|
|
78d83e368f | ||
|
|
e899f070f8 | ||
|
|
4a643aa5c2 | ||
|
|
fdca7b1196 | ||
|
|
5a130b7301 | ||
|
|
6f1dcf8f00 | ||
|
|
371408e5eb | ||
|
|
0eff4bf2ee | ||
|
|
18e456d490 | ||
|
|
374d40aa41 | ||
|
|
073c12f8eb | ||
|
|
001c835068 | ||
|
|
e228fd36fb |
@@ -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.167.1
|
||||
version: 0.168.4
|
||||
|
||||
# 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
|
||||
|
||||
+200
-11
@@ -37,6 +37,8 @@ New shell-free frontend images run as `65532:65532` and listen on port `8080`.
|
||||
Before selecting such an image tag, set the following values together. Defaults
|
||||
remain compatible with legacy images on port `80`; this chart does not upgrade
|
||||
the frontend image automatically. The Service and ingress remain on port `80`.
|
||||
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:
|
||||
@@ -66,8 +68,172 @@ 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. This setting applies to the
|
||||
primary frontend, not the separately configured legacy demo deployment.
|
||||
`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.
|
||||
|
||||
### 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.
|
||||
|
||||
### 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
|
||||
|
||||
@@ -94,7 +260,7 @@ kerberoshub:
|
||||
|
||||
| Value | Default | Purpose |
|
||||
| --- | --- | --- |
|
||||
| `kerberoshub.api.passkeys.enabled` | `false` | Inject passkey configuration into Hub API pods only |
|
||||
| `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` |
|
||||
@@ -112,7 +278,14 @@ HTTPS and a compatible browser are required. Local development may use
|
||||
The frontend displays translated cancellation, verification and unavailable
|
||||
messages when a ceremony cannot complete.
|
||||
|
||||
Apply values with a Helm upgrade or GitOps sync; changes trigger an API rollout.
|
||||
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
|
||||
@@ -550,6 +723,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"` |
|
||||
@@ -620,12 +795,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. | `[]` |
|
||||
@@ -636,7 +815,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 and, for accounts with "Group notifications" on, how long notifications are held to collect other cameras that change status at the same time. | `60` |
|
||||
| `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"` |
|
||||
@@ -838,22 +1017,32 @@ Sent by `hub-monitor-device` when a camera goes online, offline or idle. The sub
|
||||
|---|---|
|
||||
| `{{user}}` | Username of the account the camera belongs to. |
|
||||
| `{{title}}` | The email subject. |
|
||||
| `{{text}}` | The status change, for example "`<camera>` is online, but the camera stream is not connected." |
|
||||
| `{{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 in the same scan. A change is held for one scan (`kerberoshub.monitordevice.scanIntervalSeconds`, default 60 seconds) so that cameras of the same outage arrive in one email. 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`).
|
||||
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." |
|
||||
| `{{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). |
|
||||
@@ -861,7 +1050,7 @@ Sent by `hub-monitor-device` instead of `device` when an account has **Group not
|
||||
| `{{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.** Sections are ordered by status (offline, camera stream lost, 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 status (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 · 2 cameras` or `Other cameras · Camera stream lost · 1 camera`; "all" is added when every camera of the site or group is in the section. Each camera line has its name and a detail: `no response for 12 min`, `agent online, camera stream not connected` or `online`.
|
||||
**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.
|
||||
|
||||
|
||||
@@ -276,7 +276,7 @@
|
||||
letter-spacing: 0em;
|
||||
text-align: left;
|
||||
color: #6D6666;
|
||||
margin-top: 12px;">Cameras that go offline together usually share a cause, such as a site losing its internet connection or power. Have a look into your Kerberos Hub account for the latest recordings and the status of your cameras.</p>
|
||||
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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 -}}
|
||||
|
||||
@@ -437,6 +437,8 @@ spec:
|
||||
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
|
||||
|
||||
@@ -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 80 }}
|
||||
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
|
||||
|
||||
@@ -320,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
|
||||
@@ -457,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 }}
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
+74
-4
@@ -268,6 +268,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
|
||||
@@ -650,6 +655,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'
|
||||
@@ -754,6 +766,9 @@ 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"
|
||||
@@ -804,12 +819,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"
|
||||
@@ -818,6 +834,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
|
||||
@@ -845,6 +871,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
|
||||
@@ -967,6 +1022,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.
|
||||
@@ -1075,6 +1132,8 @@ kerberoshub:
|
||||
# memory: 128Mi
|
||||
# cpu: 100m
|
||||
monitordevice:
|
||||
securityContext: {}
|
||||
podSecurityContext: {}
|
||||
repository: ghcr.io/uug-ai/hub-monitor-device
|
||||
pullPolicy: IfNotPresent
|
||||
tag: "v1.4.2"
|
||||
@@ -1082,8 +1141,9 @@ kerberoshub:
|
||||
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 and, with "Group notifications" on, how long
|
||||
# notifications are held to collect other cameras that change at the same time.
|
||||
# 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:
|
||||
@@ -1222,6 +1282,8 @@ kerberospipeline:
|
||||
memory: 10Mi
|
||||
cpu: 10m
|
||||
throttler:
|
||||
securityContext: {}
|
||||
podSecurityContext: {}
|
||||
repository: uugai/hub-pipeline-throttler
|
||||
deploymentAnnotations: {}
|
||||
# deploymentAnnotations:
|
||||
@@ -1243,6 +1305,8 @@ kerberospipeline:
|
||||
memory: 10Mi
|
||||
cpu: 10m
|
||||
notify:
|
||||
securityContext: {}
|
||||
podSecurityContext: {}
|
||||
repository: ghcr.io/uug-ai/hub-pipeline-notification
|
||||
deploymentAnnotations: {}
|
||||
# deploymentAnnotations:
|
||||
@@ -1318,6 +1382,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.
|
||||
@@ -1346,6 +1412,8 @@ kerberospipeline:
|
||||
memory: 2Gi
|
||||
cpu: 1000m
|
||||
thumbnail:
|
||||
securityContext: {}
|
||||
podSecurityContext: {}
|
||||
repository: ghcr.io/uug-ai/hub-pipeline-thumbnail
|
||||
deploymentAnnotations: {}
|
||||
# deploymentAnnotations:
|
||||
@@ -1377,6 +1445,8 @@ kerberospipeline:
|
||||
memory: 2Gi
|
||||
cpu: 1000m
|
||||
counting:
|
||||
securityContext: {}
|
||||
podSecurityContext: {}
|
||||
repository: uugai/hub-pipeline-counting
|
||||
deploymentAnnotations: {}
|
||||
# deploymentAnnotations:
|
||||
|
||||
@@ -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,185 @@
|
||||
#!/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
|
||||
}
|
||||
|
||||
for mode in all pipeline ui; do
|
||||
defaults="$(helm template hub "$CHART_DIR" --set "mode=$mode")"
|
||||
if grep -q 'securityContext:' <<< "$defaults"; then
|
||||
echo "Default $mode mode unexpectedly enables security settings" >&2
|
||||
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[@]}"
|
||||
[[ "$(grep -c 'runAsUser: 65532' <<< "$hardened")" == "$expected" ]]
|
||||
[[ "$(grep -c 'fsGroup: 65532' <<< "$hardened")" == "$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.
|
||||
[[ "$(grep -c 'runAsUser: 65532' <<< "$configured")" == 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."
|
||||
@@ -33,6 +33,30 @@ assert_passkeys() {
|
||||
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"'
|
||||
@@ -50,14 +74,22 @@ for mode in all ui pipeline; do
|
||||
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 affect Hub API only"
|
||||
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 \
|
||||
|
||||
Reference in New Issue
Block a user